mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
test(01-10): add red dashboard and score presentation contracts
- preserve useful Today narrative without duplicate score prose - remove stored-document regeneration from the detail surface - retain score identity while rejecting redundant method copy
This commit is contained in:
1 parent
44b5d8da17
commit
8e944dbcd3
3 files changed
+104
-12
No files matched your search
@@ -76,6 +76,13 @@ function digest(over: Partial<DailyDigest> = {}): DailyDigest {
|
||||
};
|
||||
}
|
||||
|
||||
function visibleText(html: string): string {
|
||||
return html
|
||||
.replace(/<[^>]+>/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
describe("<TodayHero>", () => {
|
||||
it("renders the score, the lead read, and the worth-a-look rail", () => {
|
||||
const html = render(<TodayHero digest={digest()} />);
|
||||
@@ -150,6 +157,45 @@ describe("<TodayHero>", () => {
|
||||
expect(html).not.toContain('data-slot="today-hero-briefing-link"');
|
||||
});
|
||||
|
||||
it("keeps the useful loaded narrative without repeating the ring's numeric score", () => {
|
||||
const html = render(
|
||||
<TodayHero
|
||||
digest={digest({
|
||||
briefingLead:
|
||||
"Your health score is 82. Your week is trending steady.",
|
||||
line: "Your health score is 82. Your week is trending steady.",
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
const text = visibleText(html);
|
||||
|
||||
expect(text).toContain("Your week is trending steady.");
|
||||
expect(text.match(/\b82\b/g)).toHaveLength(1);
|
||||
expect(html).toContain('data-slot="today-hero-score"');
|
||||
expect(html).toContain('data-slot="today-hero-lead"');
|
||||
});
|
||||
|
||||
it("does not invent a score narrative while the digest is provisional", () => {
|
||||
const html = render(
|
||||
<TodayHero
|
||||
digest={digest({
|
||||
phase: "provisional",
|
||||
sleepPending: true,
|
||||
score: null,
|
||||
briefingLead: null,
|
||||
reactionLine: null,
|
||||
line: "Your health score today is 82.",
|
||||
worthALook: [doseItem],
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain('data-slot="today-hero-sleep-pending"');
|
||||
expect(html).toContain('data-provisional="true"');
|
||||
expect(html).not.toContain("Your health score today is 82.");
|
||||
expect(html).not.toContain('data-slot="today-hero-lead"');
|
||||
});
|
||||
|
||||
it("degrades to nothing on a genuinely empty account", () => {
|
||||
const html = render(
|
||||
<TodayHero
|
||||
|
||||
@@ -55,10 +55,13 @@ describe("DocumentSummaryBlock — a stored summary", () => {
|
||||
expect(html).toContain("A discharge letter from a city hospital.");
|
||||
expect(html).toContain("14 Feb 2026");
|
||||
expect(html).toContain('data-slot="document-detail-summary"');
|
||||
// A stored result stays visible and can be explicitly regenerated.
|
||||
// Opening a stored result is read-only: the detail surface no longer
|
||||
// exposes the redundant regenerate action.
|
||||
expect(html).not.toContain("being prepared");
|
||||
expect(html).toContain('data-slot="document-detail-summary-regenerate"');
|
||||
expect(html).toContain("Generate again");
|
||||
expect(html).not.toContain(
|
||||
'data-slot="document-detail-summary-regenerate"',
|
||||
);
|
||||
expect(html).not.toContain("Generate again");
|
||||
const header = html.match(
|
||||
/<div[^>]*data-slot="document-detail-summary-header"[^>]*>/,
|
||||
);
|
||||
@@ -67,18 +70,18 @@ describe("DocumentSummaryBlock — a stored summary", () => {
|
||||
expect(html).not.toContain("document-detail-summary-generate");
|
||||
});
|
||||
|
||||
it("disables regeneration while a replacement is being generated", () => {
|
||||
it("keeps stored prose visible without surfacing replacement state", () => {
|
||||
const html = base({
|
||||
summary: "Stored prose.",
|
||||
summaryState: "READY",
|
||||
isGenerating: true,
|
||||
});
|
||||
|
||||
const action = html.match(
|
||||
/<button[^>]*data-slot="document-detail-summary-regenerate"[^>]*>/,
|
||||
expect(html).toContain("Stored prose.");
|
||||
expect(html).not.toContain(
|
||||
'data-slot="document-detail-summary-regenerate"',
|
||||
);
|
||||
expect(action?.[0]).toMatch(/\sdisabled(=""|\s|>)/);
|
||||
expect(html).toContain("Generating");
|
||||
expect(html).not.toContain("Generating");
|
||||
});
|
||||
|
||||
it("shows a stored summary even if the state disagrees", () => {
|
||||
|
||||
@@ -126,6 +126,38 @@ function report(overrides: Partial<HealthScoreReport> = {}): HealthScoreReport {
|
||||
};
|
||||
}
|
||||
|
||||
function scoredReport(
|
||||
overrides: Partial<HealthScoreReport> = {},
|
||||
): HealthScoreReport {
|
||||
return report({
|
||||
composite: {
|
||||
status: "ok",
|
||||
value: {
|
||||
score: 86,
|
||||
band: "green",
|
||||
bandSetter: null,
|
||||
composition: ["BLOOD_PRESSURE", "ACTIVITY", "SLEEP"],
|
||||
noiseFloor: 3,
|
||||
scoreVersion: 2,
|
||||
},
|
||||
coverage: {
|
||||
requiredInputs: 3,
|
||||
presentInputs: 3,
|
||||
historyDays: 28,
|
||||
missing: [],
|
||||
},
|
||||
confidence: { score: 100, band: "high" },
|
||||
provenance: {
|
||||
...provenance,
|
||||
inputs: ["BLOOD_PRESSURE", "ACTIVITY", "SLEEP"],
|
||||
},
|
||||
},
|
||||
delta: 2,
|
||||
deltaReason: null,
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockAuthUser = { unitPreference: "metric", glucoseUnit: "mg/dL" };
|
||||
});
|
||||
@@ -253,17 +285,28 @@ describe("<HealthScoreCard>", () => {
|
||||
expect(html).not.toContain("70 to 99 mg/dL");
|
||||
});
|
||||
|
||||
it("renders the one-time method note and every score identity field", () => {
|
||||
it("shows one headline score without algorithm, method, or composition copy", () => {
|
||||
const html = render(
|
||||
report({
|
||||
scoredReport({
|
||||
algorithmNotice: {
|
||||
itemKey: "health_score_algorithm:2",
|
||||
dismissed: false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(html).toContain('data-slot="health-score-algorithm-notice"');
|
||||
expect(html).toContain("cardiometabolic reference ranges");
|
||||
|
||||
expect(html.match(/>86</g)).toHaveLength(1);
|
||||
expect(html).not.toContain('data-slot="health-score-algorithm-notice"');
|
||||
expect(html).not.toContain("cardiometabolic reference ranges");
|
||||
expect(html).not.toContain("Equal-weight average of eligible pillars");
|
||||
expect(html).not.toContain("The lowest pillar sets the overall band");
|
||||
expect(html).not.toContain("Included:");
|
||||
|
||||
// Presentation cleanup must not sever score identity or source detail.
|
||||
expect(html).toContain("Method version 2");
|
||||
expect(html).toContain('data-slot="health-score-pillars"');
|
||||
expect(html).toContain("ESH 2023");
|
||||
expect(html).toContain('data-slot="health-score-delta"');
|
||||
});
|
||||
|
||||
it("localises structured activity labels, references, and reasons", () => {
|
||||
|
||||
Reference in new issue
Block a user