wcb/test/utils/create-element.test.mjs
Ayo 1b3e251a9d
Some checks are pending
Tests / Unit (push) Waiting to run
Tests / E2E (push) Waiting to run
feat!: reflect boolean props as bare attributes
Boolean props follow the HTML presence/absence semantics.
- We stick close to standard HTML behavior: flag="false" is treated as
true in most cases. Standard bare attributes disabled and required for
form fields are interpreted as true with non-existence as false.
- The use-cases for aria-*="false" and contenteditable="false" are also
supported by typing them in the JS class as string ("true" | "false").
This "true" | "false" opt-in is types-only. At runtime it's just a
string, and strings already serialize literally and are never removed —
so the enumerated/aria path needs no runtime code.
2026-07-20 19:57:23 +02:00

96 lines
2.7 KiB
JavaScript

import { describe, expect, it, vi } from 'vitest'
import { createElement } from '../../src/utils/create-element.mjs'
import { html } from '../../src/html.js'
describe('createElement', () => {
it('turns a string leaf into a text node', () => {
const node = createElement('hello')
expect(node.nodeType).toBe(Node.TEXT_NODE)
expect(node.textContent).toBe('hello')
})
it('builds an element from a vnode', () => {
const el = createElement({ type: 'p', props: null, children: ['hi'] })
expect(el.tagName).toBe('P')
expect(el.textContent).toBe('hi')
})
it('assigns known DOM properties directly', () => {
const el = createElement({
type: 'div',
props: { id: 'foo' },
children: [],
})
expect(el.id).toBe('foo')
})
it('falls back to setAttribute for unknown props', () => {
const el = createElement({
type: 'div',
props: { 'data-x': 'y' },
children: [],
})
expect(el.getAttribute('data-x')).toBe('y')
})
it('applies an unknown boolean prop as a bare/absent attribute', () => {
// no DOM property to take the value, so it follows the HTML boolean
// convention — stamping "false" would read back as *true*
const on = createElement({
type: 'x-el',
props: { flag: true },
children: [],
})
expect(on.getAttribute('flag')).toBe('')
const off = createElement({
type: 'x-el',
props: { flag: false },
children: [],
})
expect(off.hasAttribute('flag')).toBe(false)
})
it('applies style objects', () => {
const el = createElement(
html`<div style=${{ color: 'red', padding: '1em' }}>x</div>`
)
expect(el.style.color).toBe('red')
expect(el.style.padding).toBe('1em')
})
it('attaches event handlers passed as props', () => {
const onClick = vi.fn()
const btn = createElement(html`<button onClick=${onClick}>hey</button>`)
btn.click()
expect(onClick).toHaveBeenCalledOnce()
})
it('appends nested element and text children', () => {
const el = createElement(
html`<ul>
<li>a</li>
<li>b</li>
</ul>`
)
expect(el.tagName).toBe('UL')
expect(el.querySelectorAll('li')).toHaveLength(2)
expect(el.textContent).toBe('ab')
})
it('wraps an array of vnodes in a document fragment', () => {
const frag = createElement([
{ type: 'p', props: null, children: ['a'] },
{ type: 'p', props: null, children: ['b'] },
])
expect(frag.nodeType).toBe(Node.DOCUMENT_FRAGMENT_NODE)
expect(frag.childNodes).toHaveLength(2)
})
it('handles a multi-root html template as a fragment', () => {
const frag = createElement(
html`<p>a</p>
<p>b</p>`
)
expect(frag.querySelectorAll('p')).toHaveLength(2)
})
})