# Pattern Test To Integration

> Convert Common Fabric pattern unit tests (`*.test.tsx` driven by actions and assertions) into browser integration tests (`packages/patterns/integration/*.test.ts`) that exercise the rendered UI, scale to a chosen size, and can be recorded with `deno task demo`. Use when asked to promote, mirror, spot-check, browser-test, or make a video demo from an existing pattern test, including multi-user pattern tests.

- Skill: `commontoolsinc/pattern-test-to-integration` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add commontoolsinc/pattern-test-to-integration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/commontoolsinc/pattern-test-to-integration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: commontoolsinc (https://skillmd.com/u/commontoolsinc)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/commontoolsinc/pattern-test-to-integration

---


# Pattern Test to Integration

Treat the pattern test as the behavioral story, not as code to transliterate.
Re-express its meaningful state transitions through the real shell, browser,
rendered controls, and visible outcomes.

## Start from the current map

Read the source test, the pattern it instantiates, and these current references:

- `docs/common/ai/pattern-testing-guide.md` — pattern-test semantics, including
  multi-user participants.
- `packages/patterns/deno.jsonc` — the three test lanes and their CI/coverage
  roles.
- `docs/development/UI_TESTING.md` — shadow DOM, semantic locators, presentation
  behavior, and browser interaction rules.
- `docs/development/TESTING.md` — integration and `deno task demo` commands.
- `docs/development/waiting-in-tests.md` — required reading before introducing
  any polling.
- `packages/patterns/integration/cfc-browser-helpers.ts` — shared interaction,
  settling, effect-wait, and `StepTimer` seams.
- `packages/patterns/integration/pieces-controller.ts` — the local
  `PiecesController` initializer with the shared compile-byte cache.

Use working integration tests as executable examples:

- `packages/patterns/integration/nested-counter.test.ts` for a small
  single-browser interaction.
- `packages/patterns/integration/cfc-render-policy-demo.test.ts` for a concise,
  captioned demo.
- `packages/patterns/integration/lunch-poll-vote.test.ts` for two independent
  identities, browsers, and recordings on one shared timeline.

Prefer the nearest current test with the same topology over copying a generic
fixture. Tests are authoritative when a prose example has drifted.

Before choosing a destination, search `packages/patterns/integration/` for the
pattern name, source path, and intended scenario. Extend current browser
coverage when it already owns the story. If a separate scenario is clearer, give
it a specific filename and selector; never overwrite or duplicate an existing
test merely because the unit test has the shorter name.

## Preserve both test contracts

Keep the original colocated `*.test.tsx`. Pattern tests are the authored-pattern
coverage lane; a browser integration test does not replace that coverage.

Add the browser test under `packages/patterns/integration/` as `*.test.ts`. The
new test covers boundaries the pattern test intentionally omits: shell
navigation, rendering, shadow DOM, real event dispatch, browser identity, and
cross-client propagation.

Do not call the conversion complete if the browser test still performs the
primary scenario by sending the same output streams and reading the same result
fields directly. Direct piece operations remain useful for setup, invisible
invariants, persistence checks, and diagnostics; they are not substitutes for
the user-visible action and outcome.

## Translate intent across the boundary

Use this map as a starting point, then follow the actual UI:

| Pattern-test construct                       | Browser-test counterpart                                                                                                             |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `Pattern({ inputs })`                        | Build the program with `resolveLocalProgram`, then create a piece with `PiecesController.create(..., { input, start: true })`        |
| `instance.action.send(value)`                | Fill, select, or click the rendered control that a user uses to cause that action                                                    |
| `assert(() => instance.field === value)`     | Wait for the corresponding rendered text, control state, list contents, or other observable DOM effect                               |
| `{ label }` / `{ await }` in `multiUserTest` | Coordinate real participant pages with event-driven waits on shared visible state                                                    |
| participant runtime                          | A `ShellIntegration` connected to the shared piece, with identity reuse or separation matching the unit test's user/session topology |

Preserve the scenario's causal ordering and important edge cases, but optimize
for a coherent browser story rather than one `it` block per unit assertion.
Group assertions that describe one visible state. An exhaustive internal
invariant with no UI manifestation should stay in the pattern test unless the
product is meant to expose it.

If the pattern has no rendered path for the behavior, say so explicitly. Add a
product UI seam only when the user's task authorizes changing the product; do
not create a test-only button or browser backdoor merely to force a conversion.

## Use the real integration lifecycle

Build from the closest fixture, retaining the local invariants it demonstrates:

- Bind every `ShellIntegration` lifecycle.
- Generate explicit identities and keep all participants in the intended shared
  space. Reuse one identity across multiple shells when the source test models
  multiple sessions for the same user; use distinct identities for distinct
  users.
- Build the program with `resolveLocalProgram` from
  `@commonfabric/runner/local-program.deno`, passing the harness's own
  `resolve`. Give it `root` when the source imports siblings outside its own
  directory, and `dataFilePaths` when the pattern reads an attached data file
  with `dataFile()`:

  ```ts
  const program = await resolveLocalProgram(
    (resolver) => cc.runtime.harness.resolve(resolver),
    { main: sourcePath, root: rootPath, dataFilePaths: [dataPath] },
  );
  ```

  A pattern reading a data file compiles and type-checks without the attachment,
  so omitting it fails at the read rather than at the build.
  `deno task check-local-program` refuses a `FileSystemProgramResolver` built by
  hand, because a program assembled that way silently carries no data files.
- Pass initial unit-test inputs through `PiecesController.create`'s `input`
  option when they are part of the scenario.
- Keep the result demanded with a result-cell sink when pull-mode reactivity
  requires it, and cancel every sink during cleanup.
- Call `ensureDefaultPattern()` before browser navigation when the selected
  topology would otherwise race or cold-compile the space root; copy this seam
  only from a current fixture that explains why it is needed.
- Navigate with `ShellIntegration.goto(...)` and the generated identity.
- Dispose controllers and let bound shell lifecycles close browsers.

Use the helpers in `packages/patterns/integration/cfc-browser-helpers.ts` where
they already encode shadow traversal, one trusted interaction, view settling,
commit semantics, or an effect wait. Prefer accessible names and stable
product-facing selectors. Do not select by transient DOM structure when the UI
offers a semantic role, label, or explicit stable id.

## Make the scenario's scale a knob

A browser test written at one fixed size answers whether the feature works. The
same test with its size parameterized also answers how the feature behaves as
data grows, which is a different and usually more valuable question — and it
costs one constant to keep both. Read the size from the environment with a small
default, so CI keeps paying the small number while an investigation can ask for
a large one.

Three distinctions are worth building in, because retrofitting them later means
rewriting the scenario:

- **Composed versus seeded.** A couple of items driven through the real composer
  are what make the test a regression test; the rest exist only to establish
  size and should be created through the verb. Keep the two counts separate and
  derive the seeded count from the total. Presentation mode animates every
  keystroke, so a large board built through the composer is also an unwatchable
  recording.
- **Where the bulk is created.** Seeding before the view is ever opened and
  seeding while it is on screen measure different things — the first is the cost
  with nothing rendering, the second is the cost of the live view keeping up.
  Make that a knob too rather than picking one.
- **Extra timed operations after the bulk exists.** What a regression actually
  cares about is what _one more_ costs on a board of that size, which is not
  recoverable by dividing the time it took to build it.

Emit measurements as parseable single lines carrying the size they were taken
at, rather than only as step durations, so a sweep across sizes can be collected
without re-reading logs by eye.

`packages/patterns/integration/topic-board-scale.bench.ts` is the same idea in
the benchmark lane, including how it declares sizes it cannot yet run. When the
task is measurement rather than conversion, `skills/perf-investigation/SKILL.md`
picks up from here.

## Keep correctness timing separate from presentation

Wait for a real state or DOM effect. Do not add `sleep`, `waitForTimeout`, or a
larger correctness timeout to make the video readable. Read
`docs/development/waiting-in-tests.md` before adding a bounded poll, and explain
why no event-driven observation exists if one is still necessary.

Make demo pacing an annotation of the same test:

- Give each shell stable `presentation` metadata; use distinct labels and colors
  for multiple participants.
- Wrap viewer-meaningful scenario boundaries in `StepTimer.run(label, action)`.
  Write labels for the viewer: describe what changes without leaking hidden or
  protected values before the UI reveals them.
- Keep using the existing click, fill, keyboard, and element paths. Presentation
  mode observes those paths; do not invent `demo.click()` or `demo.type()`.
- Preserve one real browser per participant. The demo compositor owns the
  side-by-side or grid layout.

The demo command records the complete selected `*.test.ts` file, including all
of its `it` blocks. Keep the file's ordered scenario meaningful as a whole. Do
not silently select ignored or environment-gated tests as default demos.

## Verify the paired result

Run the original pattern test before conversion to establish a clean baseline,
and again after any source/interface change:

```bash
deno task cf test packages/patterns/<pattern>/<name>.test.tsx
```

If the test imports sibling modules above its own directory, pass their common
source root just as the CLI error suggests, for example:

```bash
deno task cf test packages/patterns/<pattern>/<name>.test.tsx --root packages/patterns
```

Run the browser test through the integration selector, using its exact filename
stem:

```bash
deno task integration patterns <integration-test-name>
```

When demo output is part of the request and FFmpeg is available, record the same
test rather than a demo-only variant:

```bash
deno task demo patterns <integration-test-name>
```

Inspect the video or gallery when presentation quality matters. Passing tests
prove correctness; they do not prove that labels, pacing, framing, and
multi-user composition are understandable to a viewer.

