opsinjs
HandbookCorrectness and cost

Testing your integration

What to assert about a component you did not write. The stable contracts, the accessibility assertions worth copying, and the tests that break on any refactor.

The short version

NOT IMPLEMENTED. This component does not exist in any released version of opsinjs. There is no package to install, no module to import and no props interface to generate code against. Everything on this page is a specification of intended behaviour and may change without notice. Do not write code against it.

This component is not built yet

There is nothing to render because there is nothing to install. What you can read on this page is the specification the implementation will have to satisfy.

PlannedRoadmapWhat “planned” means

opsinjs has no test suite of its own. There is none, automated or manual, for the design system itself; Accessibility for testing records the same gap. What runs on every change is static: check:generated, check:ia, check:a11y over component source, check:llms, eslint and tsc. None of those mounts a component. The nightly adds a browser half against the built /view routes, checking hit area and survival at 1.3x and at 200%. That is a real measurement, but it arrives the morning after a merge rather than before it.

So assume nothing underneath your composition is covered, and assert what matters to you. The list below is what you would want to assert about your composition regardless of who owns the layer beneath it, and until opsinjs has a suite it is also the only thing standing between a regression in a component and a reader.

Assert against contracts that are covered by semver:

  • Roles and accessible names, queried with getByRole("button", { name: "Save reading" })
  • Data attributes, matched with [data-status="urgent"] and [data-open]
  • Visible text is what the reader actually reads

Do not assert against class names, DOM structure, or the internal nesting of a compound component. Those change without a major version and your test suite becomes a change-detector rather than a safety net.

How it works

What is stable

Stable, safe to assertNot stable, do not assert
ARIA roles and accessible namesGenerated class names
data-* attributes (the vocabulary)Element nesting depth
Visible text contentInternal element order
Keyboard behaviourInline style values
Focus position after an interactionPortal container identity

The accessibility assertions worth copying

These are the ones that catch real regressions in a health product, and they are cheap:

  • Every interactive element has an accessible name. A whole-page sweep catches the icon button someone added last week.
  • Focus goes where you said it goes. After opening a sheet, after a failed submit, after closing a dialog. Focus bugs are invisible until somebody without a mouse finds them.
  • The error is associated with its control. The control's aria-describedby resolves to the message element, not merely a message somewhere on the page.
  • aria-invalid clears when the error clears.
  • A status change is announced, i.e. it lands in a live region, rather than only appearing.
  • The status word is present as text, not only as a colour. This is colour independence as a unit test.
  • An automated accessibility scan on the rendered output. It finds a meaningful minority of issues; it does not find focus order, and it never finds wording.

Do this

  • Query by role and name first, by text second, by test id last. The order reflects how close the query is to what a reader experiences.
  • Test the flow, not the frame. For a pattern, assert the sequence: submit fails, summary appears, focus is on the summary, activating an entry focuses the field.
  • Assert on the honest states. A stale value must not carry a status; an empty state must not render a zero. Those are the assertions that protect the reader rather than the code.
  • Use fake timers deliberately for debounced validation, and assert on the final state rather than on intermediate ones.
  • Test with prefers-reduced-motion set. If a test fails only when motion is off, motion was carrying meaning.
  • Keep one end-to-end test per critical flow, such as logging a reading or disclosing a result. Let unit tests cover the rest.

Not this

  • Do not snapshot markup. A DOM snapshot of a component you do not own turns every upstream improvement into a failing test and trains the team to update snapshots without reading them.
  • Do not assert on class names. They are not a contract; data-* attributes are.
  • Do not test that a library works. That a Dialog traps focus is Base UI's test to run, not yours.
  • Do not use container.querySelector to reach into a component's internals. If you need it, the component is missing an accessible affordance and that is the actual bug.
  • Do not mock the component under test. Mocking a ResultCard to test a page tests nothing about the page's real behaviour.
  • Do not rely on an automated scan alone. It will pass a page whose reading order is nonsense.

Gotchas

  • Portals render outside the container returned by your render helper. Queries scoped to it will not find a dialog's contents; query the document.
  • jsdom does not do layout. Anything about size, position, overflow, visibility-by-clipping or target size cannot be tested there. Those need a real browser.
  • jsdom does not implement matchMedia by default, so a component reading prefers-reduced-motion needs it stubbed or it silently takes one branch.
  • Exit transitions keep an element mounted. A "closed" assertion immediately after a close will fail until the transition finishes; wait for removal.
  • Fake timers and user-event interact badly unless the timer configuration is passed through; the symptom is a test that hangs rather than fails.
  • An accessible name computed from aria-labelledby needs the target to exist. A test that renders a fragment without it will report a missing name that is present in the real page.

On this page