Mixins / foldkit-mixins

Testing and static output

SlotView.inertBuilder<Message>() is an HtmlBuilder with no runtime behind it. Rendering a view with it builds the same tagged attributes and elements, but nothing mounts and handlers are never dispatched. Use it in tests, demos and static output, not as a renderer.

Attributes reads a resolved bundle, such as the result of slots.x.attrs(base), by tag. Attributes.find(bundle, tag) returns the first match typed as that variant, so value, message or action need no cast; Attributes.filter returns every match in bundle order; Attributes.tagOf returns an entry's tag. Opaque ChildAttribute entries have no readable tag and are skipped.

ts
import { Attributes, type SlotAttributes } from 'foldkit-mixins'

let input: SlotAttributes<Message> = []
const Probe = SlotView.forMessages<Message>()
  .define(FieldSlots, (field: FieldInput, slots, h) => {
    input = slots.input.attrs([h.Value(field.value)])
    return h.input(input)
  })
  .pipe(Style.attach(FieldStyle), Behavior.attach(Validation))

Probe({ value: 'Ada', invalid: true }, SlotView.inertBuilder<Message>())

Attributes.find(input, 'Class')?.value // 'field-input'
Attributes.find(input, 'AriaInvalid')?.value // true

What a whole view draws is read with Inert from foldkit-mixins/testing: Inert.all, children, byTag, byRole, byLabel (by aria-label, a <label for>, or the innermost element with that text), text, value (an attribute or a property, whichever the builder wrote, so title and value need no special case), classes, style and pressed (an Option, none for a button that is not a toggle).

ts
import { Inert } from 'foldkit-mixins/testing'

const root = Probe({ value: 'Ada', invalid: false }, SlotView.inertBuilder<Message>())
Inert.value(Inert.byTag(root, 'input')[0], 'value') // 'Ada'

Inert.draw(view, input) draws a SlotView with every element a Slot draws marked with the Slot's name, however deeply its views nest; a view drawn for real is never marked. It draws under a Foldkit Scene's render frame, so a view that embeds a Submodel with h.submodel (every @foldkit/ui component page) draws whole, childAttributes included; nothing is dispatched, and no Command or Mount it declares runs or is checked. On that tree, On the real runtime, Frames.track() from foldkit-mixins/testing tracks the page's animation frames (create it after any stub of requestAnimationFrame, and dispose() it after). await frames.settle() resolves once every frame asked for has run and none followed, so a test reads the page drawn instead of sleeping and hoping; pass Sync.mount's handle (frames.settle(mounted)) to wait for its replica first. settle does not wait for I/O a Command started: wait for that result (vi.waitFor), then settle. hold() keeps every frame back until release(), so events meet the view as last drawn, as input faster than a frame does; see Messages carry intent.

Inert.bySlot(root, 'input') finds a Slot's elements, Inert.unslotted(root, { inside }) lists every element no Slot drew (skipping what a Slot named in inside holds, such as a canvas drawing the application's page), and Inert.fixedInline(root, { inside }) every inline declaration that is not a custom property. A package view's test expects both lists to be empty.

Inert.css(nodes) is the compiled CSS behind the nodes' classes, and Inert.missingTokens(root, stylesheet) lists the --fk-* tokens the drawn Styles read with no fallback that nothing defines (not the sheet, not a drawn rule, not an inline style), each of which makes its declaration invalid in a browser. The two checks every application view test repeats are then:

ts
const tree = Inert.draw(Counter, 0)
expect(Inert.unslotted(tree)).toEqual([])
expect(Inert.missingTokens(tree, stylesheet)).toEqual([])

A tree whose Styles read no token passes the second vacuously; assert that Inert.css(Inert.all(tree)) contains var(--fk- where that would be a mistake.