You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/morfo/components/toast.test.ts

97 lines
2.8 KiB

import { describe, expect, it } from 'vitest'
import { validateMorfo, MorfoInvariantError } from '../schema'
import type { Morfo } from '../types'
import { toastMorfo } from './toast'
function cloneMorfo(m: typeof toastMorfo): Morfo {
return structuredClone(m as Morfo) as Morfo
}
describe('toastMorfo semantic contract', () => {
it('passes shape + invariant validation', () => {
expect(() => validateMorfo(toastMorfo)).not.toThrow()
})
it('declares announce semantics and supported intents in morfo', () => {
const announce = toastMorfo.events?.find((event) => event.name === 'announce')
expect(announce).toBeDefined()
expect(announce?.semantic).toMatchObject({
family: 'alert',
intent: {
fromProp: 'intent',
default: 'neutral',
supported: ['neutral', 'affirm', 'fulfill', 'risk', 'threat']
}
})
})
it('declares intent-driven role and aria-live on the item contract', () => {
const item = toastMorfo.parts.find((part) => part.kebab === 'item')
const role = item?.aria.find((entry) => entry.attr === 'role')
const live = item?.aria.find((entry) => entry.attr === 'aria-live')
expect(role).toMatchObject({
attr: 'role',
value: {
kind: 'mapRef',
source: { kind: 'propRef', prop: 'intent' },
map: {
neutral: 'status',
affirm: 'status',
fulfill: 'status',
risk: 'alert',
threat: 'alert'
}
}
})
expect(live).toMatchObject({
attr: 'aria-live',
value: {
kind: 'mapRef',
source: { kind: 'propRef', prop: 'intent' },
map: {
neutral: 'polite',
affirm: 'polite',
fulfill: 'polite',
risk: 'assertive',
threat: 'assertive'
}
}
})
})
it('fails when the default intent is not in the supported list', () => {
const broken = cloneMorfo(toastMorfo)
const announce = broken.events?.find((event) => event.name === 'announce')
if (!announce || !('intent' in announce.semantic) || typeof announce.semantic.intent === 'string') {
throw new Error('announce semantic intent binding missing in fixture')
}
announce.semantic.intent.default = 'neutral'
announce.semantic.intent.supported = ['affirm', 'risk']
expect(() => validateMorfo(broken)).toThrow(MorfoInvariantError)
expect(() => validateMorfo(broken)).toThrow(/default intent "neutral" must be included/)
})
it('fails when an intent map references an unknown local state', () => {
const broken = cloneMorfo(toastMorfo)
const item = broken.parts.find((part) => part.kebab === 'item')
if (!item) throw new Error('item part missing in fixture')
item.aria[0] = {
attr: 'role',
value: {
kind: 'mapRef',
source: { kind: 'stateRef', state: 'missing' },
map: { true: 'alert', false: 'status' }
}
}
expect(() => validateMorfo(broken)).toThrow(MorfoInvariantError)
expect(() => validateMorfo(broken)).toThrow(/stateRef "missing"/)
})
})

Powered by TurnKey Linux.