From 6916ae32c31c230d37aaaf6ac0dda1fca6481d7c Mon Sep 17 00:00:00 2001 From: Molecule AI App-FE Date: Mon, 11 May 2026 21:11:04 +0000 Subject: [PATCH] test(canvas/mobile): add palette-context coverage (9 cases) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers MobileAccentProvider + usePalette hook: - Renders children - usePalette(dark=false) → MOL_LIGHT - usePalette(dark=true) → MOL_DARK - accent=null returns base palette unchanged - accent=base.accent returns base palette unchanged (identity guard) - accent=#custom → accent + online overridden - MOL_LIGHT/MOL_DARK singletons never mutated The pure functions (getPalette, normalizeStatus, tierCode) are already covered by palette.test.ts — only the React context/hook is new here. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .../mobile/__tests__/palette-context.test.tsx | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 canvas/src/components/mobile/__tests__/palette-context.test.tsx diff --git a/canvas/src/components/mobile/__tests__/palette-context.test.tsx b/canvas/src/components/mobile/__tests__/palette-context.test.tsx new file mode 100644 index 00000000..4dd5c09e --- /dev/null +++ b/canvas/src/components/mobile/__tests__/palette-context.test.tsx @@ -0,0 +1,131 @@ +// @vitest-environment jsdom +/** + * palette-context: MobileAccentProvider + usePalette hook coverage. + * + * Covers: + * - usePalette(dark=false) without provider → MOL_LIGHT + * - usePalette(dark=true) without provider → MOL_DARK + * - usePalette with provider accent=null → base palette unchanged + * - usePalette with provider accent=base.accent → base palette unchanged (identity guard) + * - usePalette with provider accent="#ff0000" → accent + online overridden + * - MobileAccentProvider renders children + * - Never mutates the static MOL_LIGHT/MOL_DARK singletons + * + * The pure functions (getPalette, normalizeStatus, tierCode) are covered + * in palette.test.ts — only the React context/hook is tested here. + */ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { cleanup, render } from "@testing-library/react"; +import React from "react"; + +import { MobileAccentProvider, usePalette } from "../palette-context"; +import { MOL_DARK, MOL_LIGHT } from "../palette"; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); + +// ─── Test helpers ────────────────────────────────────────────────────────────── +// Each helper renders exactly one usePalette value as a testid element. +// Using unique testids per scenario avoids "multiple elements" DOM pollution +// when tests run in the same jsdom worker without strict cleanup timing. + +function AccentDump({ dark }: { dark: boolean }) { + const palette = usePalette(dark); + return {palette.accent}; +} + +function OnlineDump({ dark }: { dark: boolean }) { + const palette = usePalette(dark); + return {palette.online}; +} + +// ─── MobileAccentProvider ────────────────────────────────────────────────────── +describe("MobileAccentProvider", () => { + it("renders children", () => { + const { getByText } = render( + + child content + , + ); + expect(getByText("child content").textContent).toBe("child content"); + }); +}); + +// ─── usePalette — no provider ───────────────────────────────────────────────── +describe("usePalette without MobileAccentProvider", () => { + it("returns MOL_LIGHT when dark=false", () => { + const { getByTestId } = render(); + expect(getByTestId("accent-val").textContent).toBe(MOL_LIGHT.accent); + }); + + it("returns MOL_DARK when dark=true", () => { + const { getByTestId } = render(); + expect(getByTestId("accent-val").textContent).toBe(MOL_DARK.accent); + }); +}); + +// ─── usePalette — with MobileAccentProvider ──────────────────────────────────── +describe("usePalette with MobileAccentProvider", () => { + it("returns base palette unchanged when accent=null", () => { + const { getByTestId } = render( + + + , + ); + expect(getByTestId("accent-val").textContent).toBe(MOL_LIGHT.accent); + }); + + it("returns base palette unchanged when accent matches base.accent (identity guard)", () => { + const { getByTestId } = render( + + + , + ); + expect(getByTestId("accent-val").textContent).toBe(MOL_LIGHT.accent); + }); + + it("overrides accent when provider supplies a different colour", () => { + const CUSTOM = "#ff0000"; + const { getByTestId } = render( + + + , + ); + expect(getByTestId("accent-val").textContent).toBe(CUSTOM); + }); + + it("also overrides online when accent is overridden", () => { + const CUSTOM = "#ff8800"; + const { getByTestId } = render( + + + , + ); + expect(getByTestId("online-val").textContent).toBe(CUSTOM); + }); +}); + +// ─── Immutability ───────────────────────────────────────────────────────────── +describe("MOL_LIGHT and MOL_DARK singletons are never mutated", () => { + it("MOL_LIGHT.accent unchanged after custom-accent render", () => { + const before = MOL_LIGHT.accent; + render( + + + , + ); + expect(MOL_LIGHT.accent).toBe(before); + }); + + it("MOL_DARK.accent unchanged after custom-accent render", () => { + const before = MOL_DARK.accent; + render( + + + , + ); + expect(MOL_DARK.accent).toBe(before); + }); +});