Skip to main content
@craft-ts
LearnLearn with EffectAI agentsGuideReference
@craft-ts/core@craft-ts/component@craft-ts/dev-tools@craft-ts/effect@craft-ts/style@craft-ts/style-testing@craft-ts/i18n@craft-ts/i18n-effect@craft-ts/mcp
ExamplesMigrationEffect compatibility & maturityAdopting CraftTS progressivelyPress kitRoadmapBacklog
Beta
  • Learn
  • Learn with Effect
  • AI agents
  • Guide
  • Reference
  • Packages
  • Resources
  • Guide overview
  • Create a project
i.Core concepts
  • The mental model
  • Which primitive should I use?
  • Anatomy of a primitive
  • Generators and yield*
  • Insertions
  • Typed insertion pipes
  • Exceptions as values
ii.Managing state
  • Local state
  • State machines
  • query
  • Mutations
  • queryParams
  • asyncProcess
  • Selecting
  • Reacting to mutations
  • Collections
  • Collection utilities
  • Persistence
  • GlobalPersisterHandler
  • Pagination placeholders
  • Schema validation
iii.Recommended approaches
  • Inject at the point of use
iv.Structuring the app
  • craftService
  • Service scopes
  • Shaping the public API
  • Abstract services
  • App start
  • Lazy services
  • Server functions
  • craftRegisterFor
  • Target wrapper
v.Routing & type-safe DI
  • Setup
  • CLI automation
  • ESLint rules
  • Route providers
  • Route guards
  • Route exception handling
  • Non-blocking navigation
  • Global error component
  • Route load errors
  • Scaling routes
vi.Components & templates
  • Components
  • Activating the style system
  • Typed styles
  • Defining a design system
  • Tokens and variables
  • Global foundation and fonts
  • Pseudo-elements and animations
  • Axes and the matrix
  • Context obligations
  • Testing visual states
  • Text contrast
  • Visual attestation
  • Template obligations
  • Typed CSS variables
  • Fine-grained reactivity
  • Progressive rendering
  • Directives and .pipe(...)
  • settledValue & pendingNode
  • Accessibility
  • Customization
  • Content projection
  • Styling a component
  • Template migrator
vii.Forms
  • Overview
  • Validators
  • Submitting
  • Nested forms
  • Exception handling
  • Complete examples
viii.Testing
  • Testing services
  • Testing components
  • Type-level tests
  • Browser boundaries
  • Page stability
  • Architecture rules
    • Overview
    • Declarative baseline
    • Unique identities
    • HTTP endpoint ownership
    • Computed purity
    • Dependency cycles
    • Mutation reactions
    • Route DI proofs
    • Route component files
    • Path boundaries
    • Exclusive links
    • Persisted identities
    • insertSelect keys
    • craftEffect and network
    • craftEffect and imperative sync
    • Interactive element names
    • Metric thresholds
    • Server-state loaders
    • Primitive loader requirements
  • Extensible architecture graph
  • Graph insights
  • Craft graph vs Nx
ix.Reactivity utilities
  • craftComputed
  • craftEffect
  • craftMethod
  • source$
  • on$
  • fromEventToSource$
  • sourceFromEvent
  • afterRecomputation
x.Internationalisation
  • Type-safe i18n
  • The catalogue
  • Tokens
  • The runtime
  • With Effect
xi.Deployment (experimental)
  • Overview
  • Manifest reference
  • Diagnostics
  • Providers
  • Alchemy provider
xii.Going further
  • SSR and hydration
  • Effect integration
  • Program operators
  • Pattern matching
  • Observability
  • AI agents
  • Temporal runtime
  1. Components & templates
  2. Testing visual states

Testing what you built#

The matrix says what the states are. This page is how you look at them.

Drivers#

Every axis point carries the driver that reaches it. An axis without one would be worse than a missing axis: the matrix would enumerate scenarios nothing can produce and render identical captures — false coverage rather than none.

ts
import { applyScenario, visualMatrix } from '@craft-ts/style-testing';

for (const scenario of visualMatrix(card)) {
  await applyScenario(page, scenario);
  await expect(page).toHaveScreenshot(`${scenario.id}.png`);
}

page is described structurally, so Playwright is not a dependency — a Playwright Page matches the shape and is passed unchanged.

Application order is fixed in one place (orderedDrivers): emulation and viewport first because they relayout, container width next, DOM state after, scrolling last. Applying them in declaration order instead would make a capture depend on which axis someone wrote first.

Exhaustiveness#

ts
import {
  assertExhaustiveVisualMatrix,
  baselinesIn,
  visualMatrix,
} from '@craft-ts/style-testing';

assertExhaustiveVisualMatrix(visualMatrix(card), baselinesIn(files));

It fails in both directions. A baseline nothing produces any more matters as much as a missing one: it is a state the component used to have, and whoever opens the folder still counts it as covered.

The check is post-inference on purpose. A self-referential constraint on the component's own declaration resolves the union to never and passes while checking nothing — the same shape as assertExhaustiveRouteExceptions.

Content cases#

The matrix covers conditions, not data — and the eighty-character title, the empty list and the seven-figure price are what break layouts most often. No type can derive them, so they are declared:

ts
import { contentCases, visualMatrix } from '@craft-ts/style-testing';

contentCases(visualMatrix(card), { longTitle: 'x'.repeat(80), empty: '' });

A data case is rendered at one point of each axis, except on the axes that change the space available — viewport and container — where the crossing is complete. A long title behaves differently at two widths; it does not behave differently in two colour schemes.

What the graph adds#

The style dump joins the dependency graph, so the questions that cross layers have answers. graph is the dependency graph the dev tools build; the dump half of it is what craftStyle({ dumpPath }) writes, so these queries return nothing useful until that option is set.

ts
import {
  danglingVars,
  impactedClasses,
  matrixSizeByComponent,
  unproven,
  varsWrittenBy,
} from '@craft-ts/dev-tools';

matrixSizeByComponent(graph); // what a component costs to capture
impactedClasses(graph, ['--ds-accent']); // what one token change can be seen in
varsWrittenBy(graph); // proves a colour axis only repaints
danglingVars(graph); // declared and never read
unproven(graph); // every escape hatch, with its reason

impactedClasses is the one that pays for the visual CI: changing a colour should recapture what reaches it, not the whole suite.

The same questions, from an agent#

Three of these are exposed as MCP tools by @craft-ts/mcp, so an agent can ask them without writing a script:

MCP toolanswers
style_impactwhich classes and components one token or variable change is visible in
style_matrixhow many scenarios each component costs to capture
style_debtevery escape hatch — unsafeLength, unsafeAssume — with its reason

They read the same dump. A style_matrix that answers with nothing is the signature of a missing dumpPath, and the server says so.

←Context obligationsText contrast→

On this page

  • Drivers
  • Exhaustiveness
  • Content cases
  • What the graph adds
  • The same questions, from an agent

@craft-ts · MIT licensed

GitHubnpm

Search