mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
feat(integrations): tell the Apple Health card how the last sync arrived
The batch route has accepted a `syncTrigger` since v1.32.8 and put it on the wide event, where no human reads it. Half of what was promised got built: the value is written and appears in three places in the codebase, none of which is a screen anyone looks at. Persist it on the account, alongside the instant of the most recent batch that arrived without the app being opened, and publish both from `GET /api/integrations/healthkit`. The card now names how the last sync arrived and when data last came in on its own. `healthKitLastSyncedAt` alone reads identically whether the phone delivers by itself or only while someone has the app open, which is why "is background delivery working" could not be settled from either side. A batch that declares no trigger writes null rather than leaving the previous one standing — the field describes this sync, and an older client's silence is not evidence about it. A foreground batch leaves the background timestamp alone; overwriting it would erase the only answer the card has. Also here: WHOOP, Fitbit and Google Health join the workout-freshness table. All three write workouts, and without an entry their cards could report every measurement type fresh while the workout leg had been silent for weeks. And the status pill drops a generic `aria-label` that replaced its own accessible name, so a screen reader announced the same words for a healthy connection as for a broken one.
This commit is contained in:
1 parent
06935256b0
commit
4e95df9e8d
7 files changed
+507
-16
No files matched your search
@@ -230,3 +230,27 @@ describe("IntegrationStatusPill — a failing provider states its age", () => {
|
||||
expect(html).toContain("Sync fehlgeschlagen");
|
||||
});
|
||||
});
|
||||
|
||||
describe("IntegrationStatusPill — accessible name", () => {
|
||||
/**
|
||||
* A generic `aria-label` used to sit on the badge. Being a label, it
|
||||
* REPLACED the accessible name with itself, so a screen reader announced the
|
||||
* same words for a healthy connection as for a broken one — erasing the one
|
||||
* distinction the pill exists to draw.
|
||||
*/
|
||||
it.each([
|
||||
["connected", "Connected"],
|
||||
["error", "Error"],
|
||||
["parked", "Paused"],
|
||||
] as const)("lets the %s label be the accessible name", (state, label) => {
|
||||
const html = render(
|
||||
<IntegrationStatusPill
|
||||
state={state}
|
||||
lastSyncAt={new Date(Date.now() - 12 * 60 * 1000)}
|
||||
now={new Date()}
|
||||
/>,
|
||||
);
|
||||
expect(html).not.toContain("aria-label=");
|
||||
expect(html).toContain(label);
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,8 @@ import pl from "../../../../../messages/pl.json";
|
||||
|
||||
type Payload = {
|
||||
lastSyncedAt: string | null;
|
||||
lastSyncTrigger?: "foreground" | "background" | "push" | null;
|
||||
lastBackgroundSyncAt?: string | null;
|
||||
syncHealth?: { verdict: string; since: string | null };
|
||||
metricFreshness?: Array<{
|
||||
type: string;
|
||||
@@ -227,3 +229,69 @@ describe("<AppleHealthCard>", () => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("<AppleHealthCard> — delivery diagnostic (#586)", () => {
|
||||
beforeEach(() => {
|
||||
statusPayload = undefined;
|
||||
statusLoading = false;
|
||||
statusError = false;
|
||||
});
|
||||
|
||||
it("names how the last sync arrived and when data last came in on its own", () => {
|
||||
statusPayload = {
|
||||
lastSyncedAt: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
|
||||
lastSyncTrigger: "background",
|
||||
lastBackgroundSyncAt: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
|
||||
syncHealth: { verdict: "fresh", since: null },
|
||||
};
|
||||
|
||||
const html = render();
|
||||
|
||||
expect(html).toContain('data-testid="apple-health-delivery"');
|
||||
expect(html).toContain("in the background");
|
||||
expect(html).toContain("Last background sync");
|
||||
});
|
||||
|
||||
it("says plainly that background delivery has never happened", () => {
|
||||
statusPayload = {
|
||||
lastSyncedAt: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
|
||||
lastSyncTrigger: "foreground",
|
||||
lastBackgroundSyncAt: null,
|
||||
syncHealth: { verdict: "fresh", since: null },
|
||||
};
|
||||
|
||||
const html = render();
|
||||
|
||||
// The reporter's question, answered rather than left to inference: every
|
||||
// sync so far arrived while the app was open.
|
||||
expect(html).toContain("with the app open");
|
||||
expect(html).toContain("every sync so far arrived with the app open");
|
||||
});
|
||||
|
||||
it("reports a missing trigger as unreported instead of guessing one", () => {
|
||||
statusPayload = {
|
||||
lastSyncedAt: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
|
||||
lastSyncTrigger: null,
|
||||
lastBackgroundSyncAt: null,
|
||||
syncHealth: { verdict: "fresh", since: null },
|
||||
};
|
||||
|
||||
const html = render();
|
||||
|
||||
expect(html).toContain(
|
||||
'<dd data-testid="apple-health-last-trigger">trigger not reported by the app</dd>',
|
||||
);
|
||||
});
|
||||
|
||||
it("says there is nothing to report when no sync has ever arrived", () => {
|
||||
statusPayload = {
|
||||
lastSyncedAt: null,
|
||||
syncHealth: { verdict: "pending_first_sync", since: null },
|
||||
};
|
||||
|
||||
const html = render();
|
||||
|
||||
expect(html).toContain("No sync has arrived yet");
|
||||
expect(html).not.toContain("Last background sync");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user