Testing
@hapio/calendar-react/testing and @hapio/calendar-core/testing: conformance, the contract checker, rendering and clocks.
See Testing for how to use them.
Conformance
function runConformance(render: () => ReactNode, o?: ConformanceOptions): Promise<ConformanceReport>;Mounts render() (renderCalendar), then checks the markup against the accessibility
contract in every view: the contract checker's rules (one tab stop per composite, role
nesting, resolving aria-* references, the live region, chronological event order, unique
ids, the slot cursor, the selection state and the editing parts), accessible names (events
time first; day cells; background events with labels), the keyboard maps of the composites
(dispatched keydown events; focus follows; the slot cursor is re-keyed per slot), at most
three tab stops per view, keyboard selection where it is on (Space starts, an arrow key
extends, Escape cancels; each step is drawn, exposed and announced; nothing is confirmed),
keyboard editing where an event can be edited (Space grabs it without a click or a scroll and
keeps focus, an arrow key draws its mirror, Escape cancels; each step is announced; nothing is
dropped), a mouse drag of an event in the time grids (its mirror and the drag preview appear,
Escape cancels before the release; nothing is dropped), the event actions where a view renders
them (Shift+F10 on an event opens a named menu of menu items with focus on the first, Escape
closes it with focus back on the event; "Move to…" starts placing with focus on the slot cursor
or the day cell, an arrow key moves it and draws the event's mirror, Escape cancels with focus
back on the event; announced; nothing is committed), and axe with zero disabled rules when
o.axe is given. Unmounts afterwards. Rejects with an Error listing every failure; resolves
with the report otherwise.
Each failure names its suite, rule and view and links
https://calendar.hapio.io/docs/a11y-contract#<rule>. The
"+N more" popover is opened (its first trigger of each variant per view): it must be a named
dialog that takes focus, closes on Escape and on its close button, and returns focus to its
trigger. Keys are dispatched as events, so nothing here depends on a test runner; run it in a
real browser (Vitest browser mode), since names, visibility and layout come from the DOM. The
selection suite reads announcements from the live region, so render it with the default
announcer ('auto' speaks through ariaNotify where the browser has it, and the region
stays empty).
import axe from 'axe-core'
it('my calendar conforms', () => runConformance(() => <MyCalendar />, { axe: (el) => axe.run(el) }))Options of runConformance.
Prop
Type
The result of a passing runConformance.
Prop
Type
One failed check.
Prop
Type
type ConformanceSuite = "roles" | "names" | "focus" | "keyboard" | "selection" | "grab" | "drag" | "actions" | "axe";type AxeRunner = (context: Element) => Promise<AxeResultsLike>;Runs axe on an element, e.g. (element) => axe.run(element) with axe-core's default export.
function expectNoAxeViolations(container: Element, axe: AxeRunner): Promise<void>;Runs axe on container and rejects with an Error listing every violation (rule id, help
and node count). No rule is disabled.
await expectNoAxeViolations(calendar.container, (el) => axe.run(el))The contract checker
function checkContract(root: Element, o?: CheckContractOptions): readonly ContractViolation[];Checks the markup under root against the v0 rules and returns every violation (empty when
clean). Reads the DOM only (and getComputedStyle for live-region); never mutates it.
What checkContract checks against.
Prop
Type
One finding.
Prop
Type
type ContractRule = "composite-tab-stop" | "role-nesting" | "aria-reference" | "live-region" | "chronological-order" | "duplicate-id" | "time-surface-padding" | "slot-cursor" | "selection-state" | "edit-parts" | "event-actions";The v0 rules. Each warning links to https://calendar.hapio.io/docs/a11y-contract#<rule>.
Rendering
function renderCalendar(ui: ReactNode, o?: RenderCalendarOptions): Promise<RenderedCalendar>;Mounts ui with react-dom/client inside act (so the store has connected, the first
sync has run and the clock is known when it resolves) and returns the container. Works with
the registry block, a CalendarProvider tree, or custom markup on the hooks; Vitest browser
mode is the supported runner.
Resolves once the calendar has settled (its root is rendered and not pending, and a day grid's
'auto' capacity is measured), or after three seconds with whatever rendered, so a test can
report what it finds (root is null without a calendar root).
const calendar = await renderCalendar(<HapioCalendar events={events} timeZone="UTC" />)
expect(calendar.root?.getAttribute('data-view')).toBe('week')
calendar.unmount()Options of renderCalendar.
Prop
Type
A mounted calendar (renderCalendar).
Prop
Type
Clocks and data (@hapio/calendar-core/testing)
function fixedClock(at: DateInput): ClockSource;A clock stopped at at (an instant, or a floating/all-day input read as UTC). Its
subscriptions never tick.
function manualClock(at: DateInput): ManualClock;A manual clock starting at at.
function generateEvents(o: GenerateEventsOptions): CalendarEventInput[];Deterministic events (same options, same output) for benches and property tests. Ids are
e0, e1, …
function loadDstVectors(json: unknown): readonly DstVector[];Validates parsed JSON against DstVectorFile and returns its vectors. Throws a TypeError
naming the first invalid path.