Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/mosaic-feature-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
8 changes: 4 additions & 4 deletions .claude/skills/mosaic/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: >-
tokens, `themeProps`), or building a flow — writing the model (the Clerk
adapter), the controller (local state, in React state or a state machine:
`setup`, states/guards/`invoke`, wired to React with `useMachine`/`useActor`/
`useSelector`), or the view (rendering), testing any of those layers, or
`useSelector`), or the view (rendering), testing a feature, or
migrating a legacy / pre-Mosaic component into the model / controller / view
split. Use when building, styling, debugging, testing, or migrating anything
Mosaic. `references/mosaic-architecture.md` (repo root) holds the design-system
Expand Down Expand Up @@ -57,8 +57,8 @@ architecture" section that defines the split. Read it for the _what_; this skill
is the _how-to_.

`packages/mosaic/src/features/user-button/` is the fullest worked example of the split
in the repo — model, controller, view, wrapper, types, messages, and a test per
layer. Copy from it.
in the repo — model, controller, view, wrapper, types, messages, and a feature
test. Copy from it.

## Which reference to read

Expand All @@ -71,7 +71,7 @@ layer. Copy from it.
| Writing the controller (local state, pending, action wrapping) | `references/controllers.md` |
| Authoring or debugging a state machine, or wiring one to React | `references/machines.md` → in-tree `machine/README.md` |
| Writing the view (rendering plain props) | `references/views.md` |
| Testing a model, controller, or view | `references/testing.md` |
| Testing a feature (feature tests, unit tests) | `references/testing.md` |
| Migrating a legacy component into Mosaic (the end-to-end workflow) | `references/migration.md` |
| Running the parity audit that guards a migration | `references/parity-audit.md` |

Expand Down
12 changes: 6 additions & 6 deletions .claude/skills/mosaic/references/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ the model's actions so the surface can report and survive them.

It does **not** touch Clerk. Its effects arrive as injected plain functions:
from a model (`models.md`) when a wrapper composes the two, or as a prop when a
leaf view calls its own controller. That is what lets a controller test run
against a fake object instead of a mocked Clerk.
leaf view calls its own controller. That keeps the controller independent of
where its effects come from.

Worked examples:

Expand Down Expand Up @@ -122,7 +122,7 @@ have a machine.

## Testing

Feed the controller a **fake model object** — a plain literal with
`status: 'ready'` and `vi.fn()` callbacks — and render a tiny harness that
surfaces what it returns. No Clerk mocking. Assert the pending key, what closes
the surface, and that an absent model callback stays absent. See `testing.md`.
The feature test covers the controller by default. Hold the
FAPI request an action makes to assert its in-flight state (the spinner, the rows
stood down), then release or fail it to assert what closes the surface. Timing
that is awkward to drive through FAPI can get a smaller test. See `testing.md`.
15 changes: 8 additions & 7 deletions .claude/skills/mosaic/references/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Two rows deserve extra care because they have no obvious home:
- **Pure derivation** (slot layout, ordering a consumer's list) belongs in
`*.layout.ts` / `*.utils.ts` beside the view, where it gets its own test.

## Phase 3 — Implement and test per layer
## Phase 3 — Implement and test

File shape: `<feature>.model.tsx` · `<feature>.controller.tsx` ·
`<feature>.view.tsx` · `<feature>.tsx` (composition wrapper), plus
Expand All @@ -78,15 +78,16 @@ whether the interaction has the async lifecycle and mutually-constraining values
that earn a machine, or whether it is `useState` (`controllers.md` → "Which one
holds the state").

Each layer is testable in isolation — that isolation is what makes the migration
verifiable. Follow the recipes in `testing.md`: the model against a mocked Clerk,
the controller against a fake model object, the view against plain props. The
**model** is the highest-risk, least-covered layer — concentrate scrutiny there.
Finish with one `*.integration.test.tsx` proving the layers compose.
Test the feature with one `*.feature.test.tsx` that uses it the way a user
would, against a real Clerk with FAPI faked (`testing.md`). Turn each inventory
row into a test there by default, and reach for a smaller test only where
`testing.md` says it is the better tool. The **model** is the
highest-risk layer, so make sure its rows (revalidation, permission gates,
empty states) each have one.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Phase 4 — Verify parity (the confidence step)

Machine and view tests only cover branches you remembered to write. To catch the
Tests only cover branches you remembered to write. To catch the
ones you didn't, run an automated diff of legacy against new.

Launch an **Explore subagent** with the prompt in `parity-audit.md`. Give it the
Expand Down
12 changes: 6 additions & 6 deletions .claude/skills/mosaic/references/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ drops the fallback instead of holding the space open. Keep the two apart.

## Testing

Mock `@clerk/shared/react` with mutable module-level vars reset in `beforeEach`,
then `renderHook` the model and assert its output. This is the **highest-risk,
least-covered layer**: it holds the Clerk resource semantics no other test can
reach. When a migration loses behavior, it is usually a model responsibility
(revalidate timing, a permission gate, an empty-state rule) that quietly went
missing — concentrate scrutiny here. See `testing.md`.
The feature test covers the model by default, running it against a real Clerk
with FAPI faked. It is the
**highest-risk layer**: when a migration loses behavior, it is usually a model
responsibility (revalidate timing, a permission gate, an empty-state rule) that
quietly went missing, so give those cases a feature test each. A rule with many
combinations can move into a pure function with a unit test. See `testing.md`.
Loading
Loading