mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
Tell a first-run what Apple Health is actually doing
Refs #778. Two honesty gaps from the first-run report: the web onboarding presented Apple Health like a web connector although the connection only completes in the iPhone app, and the initial backfill gave no sign of progress, so a working first sync looked broken. The onboarding source step now says plainly that Apple Health is set up in the iPhone app after onboarding, without dead-ending the flow. The integration card gains a server-authoritative progress summary — rows accepted, oldest reading reached, and an honest flowing/waiting line derived from the last accepted batch. Nothing the server does not know is shown: no percentage, no ETA, no invented throttle state; a quiet stretch reads as waiting for the phone, not as an error.
This commit is contained in:
1 parent
b1be67bfa7
commit
a3561b4600
10 files changed
+486
-4
No files matched your search
@@ -20,6 +20,10 @@ type Payload = {
|
||||
lastSeenAt: string;
|
||||
stale: boolean;
|
||||
}>;
|
||||
syncProgress?: {
|
||||
recordsAccepted: number;
|
||||
oldestMeasuredAt: string | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
let statusPayload: Payload | undefined;
|
||||
@@ -295,3 +299,108 @@ describe("<AppleHealthCard> — delivery diagnostic (#586)", () => {
|
||||
expect(html).not.toContain("Last background sync");
|
||||
});
|
||||
});
|
||||
|
||||
// #778 — the first-run backfill used to be invisible: a user watching the web
|
||||
// app had no way to tell "still flowing" from "looks dead but is working".
|
||||
describe("<AppleHealthCard> — sync progress (#778)", () => {
|
||||
beforeEach(() => {
|
||||
statusPayload = undefined;
|
||||
statusLoading = false;
|
||||
statusError = false;
|
||||
});
|
||||
|
||||
it("shows the accepted-row count and the oldest reading reached", () => {
|
||||
statusPayload = {
|
||||
lastSyncedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
||||
syncHealth: { verdict: "fresh", since: null },
|
||||
syncProgress: {
|
||||
recordsAccepted: 12345,
|
||||
oldestMeasuredAt: "2019-05-04T06:00:00.000Z",
|
||||
},
|
||||
};
|
||||
|
||||
const html = render();
|
||||
|
||||
expect(html).toContain('data-testid="apple-health-progress"');
|
||||
expect(html).toContain('data-testid="apple-health-progress-received"');
|
||||
// Locale-grouped integer, so the count reads as a number, not a code.
|
||||
expect(html).toContain("12,345");
|
||||
expect(html).toContain('data-testid="apple-health-progress-oldest"');
|
||||
// The 2019 date renders with its year — the whole point is showing how
|
||||
// far back the backfill has reached.
|
||||
expect(html).toContain("2019");
|
||||
});
|
||||
|
||||
it("reads as flowing while batches keep arriving", () => {
|
||||
statusPayload = {
|
||||
lastSyncedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
||||
syncHealth: { verdict: "fresh", since: null },
|
||||
syncProgress: { recordsAccepted: 100, oldestMeasuredAt: null },
|
||||
};
|
||||
|
||||
const html = render();
|
||||
|
||||
expect(html).toContain('data-testid="apple-health-progress-state"');
|
||||
expect(html).toContain('data-state="flowing"');
|
||||
expect(html).toContain("still delivering");
|
||||
});
|
||||
|
||||
it("waits honestly instead of inventing throttle state when batches pause", () => {
|
||||
statusPayload = {
|
||||
lastSyncedAt: new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(),
|
||||
syncHealth: { verdict: "fresh", since: null },
|
||||
syncProgress: { recordsAccepted: 100, oldestMeasuredAt: null },
|
||||
};
|
||||
|
||||
const html = render();
|
||||
|
||||
expect(html).toContain('data-state="waiting"');
|
||||
// The server does not track the phone's throttle/queue state, so the copy
|
||||
// says "waiting for the iPhone app", never a fabricated percentage or ETA.
|
||||
expect(html).toContain("Waiting for the iPhone app to send more data");
|
||||
expect(html).not.toContain("%");
|
||||
});
|
||||
|
||||
it("renders no progress section before anything has arrived", () => {
|
||||
statusPayload = {
|
||||
lastSyncedAt: null,
|
||||
syncHealth: { verdict: "pending_first_sync", since: null },
|
||||
syncProgress: { recordsAccepted: 0, oldestMeasuredAt: null },
|
||||
};
|
||||
|
||||
const html = render();
|
||||
|
||||
// The delivery section's "no sync yet" line already owns the blank state.
|
||||
expect(html).not.toContain('data-testid="apple-health-progress"');
|
||||
});
|
||||
|
||||
it("renders no progress section when the server read failed", () => {
|
||||
statusPayload = {
|
||||
lastSyncedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
||||
syncHealth: { verdict: "fresh", since: null },
|
||||
syncProgress: null,
|
||||
};
|
||||
|
||||
const html = render();
|
||||
|
||||
expect(html).not.toContain('data-testid="apple-health-progress"');
|
||||
});
|
||||
|
||||
it("defines every progress key in all six locale catalogs", () => {
|
||||
const requiredKeys = [
|
||||
"title",
|
||||
"receivedLabel",
|
||||
"oldestLabel",
|
||||
"flowing",
|
||||
"waiting",
|
||||
] as const;
|
||||
|
||||
for (const catalog of [en, de, es, fr, itMessages, pl]) {
|
||||
const progress = catalog.settings.appleHealth.progress;
|
||||
for (const key of requiredKeys) {
|
||||
expect(progress[key]).toBeTypeOf("string");
|
||||
expect(progress[key].length).toBeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user