mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 12:22:03 +00:00
feat(lists): empty states for measurements, mood, medications, achievements
Wires the EmptyState primitive into the four feature lists. Each path now exposes an icon + localized title + description + (where it applies) a CTA bound to the parent dialog so brand-new accounts get a single-click path to logging their first datapoint. - /measurements — Activity icon, "Add your first measurement" CTA wired to the page's Add dialog. Filter-aware: switches to "no measurements match this filter" with a "Show all types" reset whenever a non-default type filter is active. - /mood — Smile icon, "Log your first mood" CTA, identical filter-aware split for the mood-level filter. - /medications — Refactor inline icon+text+button block to the shared primitive. Pill icon, "Add first medication" CTA → openCreate. - /achievements — Trophy icon. Reuses the existing achievements.noneUnlockedYet string to keep C4 i18n sweep work minimal. New unit test (measurement-list-empty) asserts the primitive mounts, the Add-first CTA respects the optional onAddFirst prop (no orphaned button when the parent doesn't wire it), and the reset-filter CTA stays out of the default-filter render. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
4e85c38c4b
commit
9a74f8e8f2
1 file changed
+91
@@ -0,0 +1,91 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
/**
|
||||
* v1.4.15 phase-C5 — `/measurements` empty state.
|
||||
*
|
||||
* Before this phase the list rendered a single sentence
|
||||
* ("No measurements yet") in a dashed-border box with no CTA. Brand-
|
||||
* new accounts had to scroll back up to find the header "Add" button.
|
||||
* The new EmptyState surface mounts the primitive with an Activity
|
||||
* icon, localized copy, and an "Add your first measurement" CTA wired
|
||||
* to the parent dialog.
|
||||
*
|
||||
* The filter-aware copy switches to "no measurements match this
|
||||
* filter" + a "Show all types" reset CTA when a non-default filter is
|
||||
* active — guarded indirectly by the absence assertion here (the test
|
||||
* runs the default ALL filter; reset CTA must NOT appear).
|
||||
*/
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
|
||||
usePathname: () => "/measurements",
|
||||
}));
|
||||
|
||||
vi.mock("@tanstack/react-query", () => ({
|
||||
useQuery: () => ({
|
||||
data: { measurements: [], meta: { total: 0 } },
|
||||
isLoading: false,
|
||||
}),
|
||||
useQueryClient: () => ({ invalidateQueries: vi.fn() }),
|
||||
useMutation: () => ({
|
||||
mutate: vi.fn(),
|
||||
mutateAsync: vi.fn(),
|
||||
isPending: false,
|
||||
isError: false,
|
||||
error: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/hooks/use-auth", () => ({
|
||||
useAuth: () => ({
|
||||
user: { id: "u1", username: "marc", role: "USER" },
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { MeasurementList } from "../measurement-list";
|
||||
|
||||
function render(props: { onAddFirst?: () => void } = {}) {
|
||||
return renderToStaticMarkup(
|
||||
<I18nProvider initialLocale="en">
|
||||
<MeasurementList {...props} />
|
||||
</I18nProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe("MeasurementList — empty state", () => {
|
||||
it("renders the EmptyState primitive when no measurements exist", () => {
|
||||
const html = render();
|
||||
expect(html).toContain('role="status"');
|
||||
expect(html).toContain('aria-live="polite"');
|
||||
expect(html).toContain("border-dashed");
|
||||
});
|
||||
|
||||
it("renders the localized title and description", () => {
|
||||
const html = render();
|
||||
expect(html).toContain("No measurements yet");
|
||||
expect(html).toContain(
|
||||
"Log your weight, blood pressure, pulse, or other vitals",
|
||||
);
|
||||
});
|
||||
|
||||
it("exposes the Add-first CTA when onAddFirst is wired", () => {
|
||||
const html = render({ onAddFirst: () => {} });
|
||||
expect(html).toContain("Add your first measurement");
|
||||
});
|
||||
|
||||
it("hides the Add-first CTA when no callback is provided", () => {
|
||||
const html = render();
|
||||
// The fallback is the parent's header CTA; the empty card itself
|
||||
// must not render a button that does nothing.
|
||||
expect(html).not.toContain("Add your first measurement");
|
||||
});
|
||||
|
||||
it("does not render the reset-filter CTA on the default filter", () => {
|
||||
const html = render();
|
||||
expect(html).not.toContain("Show all types");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user