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/soma/morfo/runtime.svelte.test.ts

172 lines
4.9 KiB

// @vitest-environment jsdom
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { flushSync } from 'svelte'
import { createActiveDom } from '$uix/adom'
import { state } from '../reactive'
import { toggleMorfo } from '../../morfo/components/toggle'
import { createMorfoRuntime } from './runtime.svelte'
/**
* Effect roots are normally provided by Svelte components. For unit tests we
* wrap the runtime construction in `$effect.root` so `$effect` calls inside
* `registerPart` have a parent to attach to.
*/
function withEffectRoot<T>(fn: () => T): { result: T; cleanup: () => void } {
let result!: T
const cleanup = $effect.root(() => {
result = fn()
})
return { result, cleanup }
}
describe('createMorfoRuntime', () => {
let target: HTMLElement
let dom: ReturnType<typeof createActiveDom>
beforeEach(() => {
document.body.innerHTML = ''
target = document.createElement('button')
target.id = 'tog-1'
document.body.appendChild(target)
dom = createActiveDom()
})
afterEach(() => {
document.body.innerHTML = ''
})
it('partProps returns identity (id + marker) without mutable attrs', () => {
const { result: runtime, cleanup } = withEffectRoot(() => {
const id = state('tog-1')
const ref = state<HTMLElement | null>(null)
const r = createMorfoRuntime(toggleMorfo, {
dom,
states: { on: () => false }
})
r.registerPart('provider', { id, ref })
return r
})
const props = runtime.partProps('provider')
expect(props.id).toBe('tog-1')
expect(props['data-toggle']).toBe('')
// Mutable attrs are NOT in partProps; they're written via dom.apply by the effect.
expect(props['data-state']).toBeUndefined()
expect(props['aria-pressed']).toBeUndefined()
cleanup()
})
it('writes morfo-derived attrs to DOM when ref attaches', () => {
const ref = state<HTMLElement | null>(null)
const { cleanup } = withEffectRoot(() => {
const id = state('tog-1')
const r = createMorfoRuntime(toggleMorfo, {
dom,
states: { on: () => false },
props: {
disabled: () => false,
readonly: () => false,
required: () => false,
invalid: () => false,
ariaLabel: () => undefined
}
})
r.registerPart('provider', { id, ref })
})
// Simulate Svelte's attachment firing — the runtime registered a setter
// on the ref via attachRef, but in tests we mutate the ref directly.
ref.current = target
flushSync()
expect(target.getAttribute('data-state')).toBe('off')
expect(target.getAttribute('aria-pressed')).toBe('false')
expect(target.getAttribute('type')).toBe('button')
expect(target.hasAttribute('data-disabled')).toBe(false)
expect(target.hasAttribute('aria-disabled')).toBe(false)
cleanup()
})
it('reapplies attrs when sources change', () => {
const ref = state<HTMLElement | null>(null)
const pressed = state(false)
const disabled = state(false)
const { cleanup } = withEffectRoot(() => {
const id = state('tog-1')
const r = createMorfoRuntime(toggleMorfo, {
dom,
states: { on: () => pressed.current },
props: {
disabled: () => disabled.current,
readonly: () => false,
required: () => false,
invalid: () => false,
ariaLabel: () => undefined
}
})
r.registerPart('provider', { id, ref })
})
ref.current = target
flushSync()
expect(target.getAttribute('data-state')).toBe('off')
pressed.current = true
flushSync()
expect(target.getAttribute('data-state')).toBe('on')
expect(target.getAttribute('aria-pressed')).toBe('true')
disabled.current = true
flushSync()
expect(target.getAttribute('aria-disabled')).toBe('true')
cleanup()
})
it('removes conditional attrs when their condition flips back to false', () => {
const ref = state<HTMLElement | null>(null)
const invalid = state(false)
const { cleanup } = withEffectRoot(() => {
const id = state('tog-1')
const r = createMorfoRuntime(toggleMorfo, {
dom,
states: { on: () => false },
props: {
disabled: () => false,
readonly: () => false,
required: () => false,
invalid: () => invalid.current,
ariaLabel: () => undefined
}
})
r.registerPart('provider', { id, ref })
})
ref.current = target
flushSync()
expect(target.hasAttribute('data-invalid')).toBe(false)
expect(target.hasAttribute('aria-invalid')).toBe(false)
invalid.current = true
flushSync()
expect(target.getAttribute('data-invalid')).toBe('')
expect(target.getAttribute('aria-invalid')).toBe('true')
invalid.current = false
flushSync()
expect(target.hasAttribute('data-invalid')).toBe(false)
expect(target.hasAttribute('aria-invalid')).toBe(false)
cleanup()
})
it('throws when registering an unknown part', () => {
const { cleanup } = withEffectRoot(() => {
const r = createMorfoRuntime(toggleMorfo, { dom })
expect(() => r.registerPart('nonexistent', { id: state('x') })).toThrow(/not declared/)
})
cleanup()
})
})

Powered by TurnKey Linux.