", () => {
/]*data-slot="trend-annotation-empty"[^>]*class="[^"]*line-clamp-3[^"]*"[^>]*>/,
);
});
+
+ // ── v1.4.36 W2 T3 — render-state contract ──────────────────────────
+ // Pre-fix the component derived "empty" purely from
+ // `annotation == null`, which painted "Mehr Daten nötig" on every
+ // cold mount and every regenerate-in-flight even though the advisor
+ // was about to deliver an annotation. The tri-state `status` prop
+ // distinguishes pending / needs_data / generated.
+
+ it("status='pending' renders a shimmer block (not the empty hint)", () => {
+ const html = render(
+ ,
+ );
+ expect(html).toMatch(/data-slot="trend-annotation-pending"/);
+ expect(html).toContain("animate-pulse");
+ // Empty hint and filled prose MUST be absent during pending.
+ expect(html).not.toContain('data-slot="trend-annotation-empty"');
+ expect(html).not.toContain('data-slot="trend-annotation"');
+ expect(html).not.toContain("Awaiting more data");
+ });
+
+ it("status='pending' announces a busy state for assistive tech", () => {
+ const html = render(
+ ,
+ );
+ expect(html).toMatch(/role="status"/);
+ expect(html).toMatch(/aria-busy="true"/);
+ });
+
+ it("status='pending' keeps painting the shimmer even when an annotation is supplied (regenerate-in-flight)", () => {
+ // During a forced regenerate the previous annotation is still in
+ // cache. We surface the pending state so the user doesn't read
+ // stale prose alongside a freshly-fired generation.
+ const html = render(
+ ,
+ );
+ expect(html).toMatch(/data-slot="trend-annotation-pending"/);
+ expect(html).not.toContain("stale annotation");
+ });
+
+ it("status='needs_data' renders the empty hint regardless of annotation presence", () => {
+ const html = render(
+ ,
+ );
+ expect(html).toMatch(/data-slot="trend-annotation-empty"/);
+ expect(html).toContain("Awaiting more data");
+ expect(html).not.toContain("this should be ignored");
+ });
+
+ it("status='generated' renders the prose + confidence chip", () => {
+ const html = render(
+ ,
+ );
+ expect(html).toMatch(/data-slot="trend-annotation"/);
+ expect(html).toContain("BP trending down 4 mmHg");
+ expect(html).toMatch(/data-slot="trend-annotation-confidence"/);
+ expect(html).not.toContain("Awaiting more data");
+ });
+
+ it("omitting status falls back to the legacy annotation==null mapping (back-compat)", () => {
+ // Old call sites that don't pass `status` must still see the
+ // previous two-state behaviour: filled prose when supplied, empty
+ // hint when null.
+ const filled = render(
+ ,
+ );
+ expect(filled).toMatch(/data-slot="trend-annotation"/);
+ expect(filled).toContain("BP trending down");
+
+ const empty = render();
+ expect(empty).toMatch(/data-slot="trend-annotation-empty"/);
+ });
});
diff --git a/src/components/insights/__tests__/trends-row.test.tsx b/src/components/insights/__tests__/trends-row.test.tsx
index e3ccf086b..f5ed5daf1 100644
--- a/src/components/insights/__tests__/trends-row.test.tsx
+++ b/src/components/insights/__tests__/trends-row.test.tsx
@@ -128,6 +128,37 @@ describe("", () => {
expect(matches.length).toBe(3);
});
+ it("loading=true paints the pending shimmer for every metric (no 'mehr Daten' flash)", () => {
+ // v1.4.36 W2 T3 — pre-fix the row painted "Awaiting more data"
+ // across all three metrics whenever the advisor query was in
+ // flight. The loading flag now propagates to each annotation
+ // slot so the user reads a generating-state shimmer instead.
+ const html = render();
+ const pending =
+ html.match(/data-slot="trend-annotation-pending"/g) ?? [];
+ expect(pending.length).toBe(3);
+ expect(html).not.toContain("Awaiting more data");
+ });
+
+ it("loading=true keeps the shimmer even when annotations are supplied (regenerate-in-flight)", () => {
+ const html = render(
+ ,
+ );
+ expect(html).not.toContain("stale BP");
+ expect(html).not.toContain("stale weight");
+ expect(html).not.toContain("stale mood");
+ const pending =
+ html.match(/data-slot="trend-annotation-pending"/g) ?? [];
+ expect(pending.length).toBe(3);
+ });
+
it("propagates per-metric confidence chips", () => {
const html = render(
= {
@@ -48,10 +75,36 @@ export function TrendAnnotation({
metric,
annotation,
confidence,
+ status,
}: TrendAnnotationProps) {
const { t } = useTranslations();
- if (!annotation) {
+ // v1.4.36 W2 T3 — render-state contract. When `status` is supplied
+ // it drives the branch directly; otherwise we fall back on the
+ // legacy `annotation == null → empty` mapping so existing callers
+ // (tests, isolated mounts) keep their previous behaviour.
+ const resolvedStatus: TrendAnnotationStatus =
+ status ?? (annotation ? "generated" : "needs_data");
+
+ if (resolvedStatus === "pending") {
+ return (
+
+ );
+ }
+
+ if (resolvedStatus === "needs_data" || !annotation) {
return (
{
+ if (loading) return "pending";
+ return annotation ? "generated" : "needs_data";
+ };
return (