import { html } from 'dhtml' import { attr, on, type Directive } from 'dhtml/client' import { assert, assert_deep_eq, assert_eq, test } from '../../../scripts/test/test.ts' import { setup } from './setup.ts' test('directive functions work correctly', () => { const { root, el } = setup() const redifier: Directive = node => { if (!(node instanceof HTMLElement)) throw new Error('expected HTMLElement') node.style.color = 'red' return () => { node.style.color = '' } } const flipper: Directive = node => { if (!(node instanceof HTMLElement)) throw new Error('expected HTMLElement') node.style.transform = 'scaleX(-1)' return () => { node.style.transform = '' } } const template = (d: Directive | null) => html`
Hello, world!
` root.render(template(redifier)) const div = el.querySelector('div') assert(div) assert_eq(div.style.cssText, 'color: red;') root.render(template(flipper)) assert_eq(div.style.cssText, 'transform: scaleX(-1);') root.render(template(null)) assert_eq(div.style.cssText, '') root.render(null) }) test('directive functions with values work correctly', () => { const { root, el } = setup() function classes(value: string[]): Directive { const values = value.filter(Boolean) return node => { node.classList.add(...values) return () => { node.classList.remove(...values) } } } const template = (c: string[]) => html`
Hello, world!
` root.render(template(['a', 'b'])) const div = el.querySelector('div') assert(div) assert_eq(div.className, 'foo a b') root.render(template(['c', 'd'])) assert_eq(div.className, 'foo c d') root.render(template([])) assert_eq(div.className, 'foo') }) test('attr directive works correctly', () => { const { root, el } = setup() const template = (value: string | null) => html` ` root.render(template('attr-works-input')) assert_eq(el.querySelector('label')!.htmlFor, 'attr-works-input') root.render(template('updated')) assert_eq(el.querySelector('label')!.htmlFor, 'updated') root.render(template(null)) assert_eq(el.querySelector('label')!.htmlFor, '') }) test('attr directive supports booleans', () => { const { root, el } = setup() const template = (value: boolean) => html`` root.render(template(true)) assert_eq(el.querySelector('input')!.disabled, true) root.render(template(false)) assert_eq(el.querySelector('input')!.disabled, false) }) test('on directive works correctly', () => { const { root, el } = setup() let count = 0 let event: Event const template = (handler: EventListener | null) => html` ` root.render( template(e => { count++ event = e }), ) const button = el.querySelector('button')! button.click() assert_eq(count, 1) assert(event! instanceof Event) assert_eq(event.type, 'click') button.click() assert_eq(count, 2) root.render(template(null)) button.click() assert_eq(count, 2) }) test('on directive handles event listener updates', () => { const { root, el } = setup() let count1 = 0 let count2 = 0 const template = (handler: EventListener) => html`` root.render( template(() => { count1++ }), ) const button = el.querySelector('button')! button.click() assert_eq(count1, 1) assert_eq(count2, 0) root.render( template(() => { count2++ }), ) button.click() assert_eq(count1, 1) assert_eq(count2, 1) }) test('on directive supports different event types', () => { const { root, el } = setup() let enter_count = 0 let leave_count = 0 const template = () => html`
{ enter_count++ })} ${on('mouseleave', () => { leave_count++ })} > Hover me
` root.render(template()) const div = el.querySelector('div')! div.dispatchEvent(new MouseEvent('mouseenter')) assert_eq(enter_count, 1) assert_eq(leave_count, 0) div.dispatchEvent(new MouseEvent('mouseleave')) assert_eq(enter_count, 1) assert_eq(leave_count, 1) }) test('on directive supports event listener options', () => { const { root, el } = setup() let count = 0 const template = (options?: AddEventListenerOptions) => html` ` root.render(template({ once: true })) const button = el.querySelector('button')! button.click() assert_eq(count, 1) button.click() assert_eq(count, 1) root.render(template()) button.click() assert_eq(count, 2) button.click() assert_eq(count, 3) }) test('same directive function is not re-invoked or cleaned up', () => { const { root } = setup() const sequence: string[] = [] const stable = () => { sequence.push('stable create') return () => sequence.push('stable cleanup') } const unstable = () => () => { sequence.push('unstable create') return () => sequence.push('unstable cleanup') } const template = (d1: Directive | null, d2: Directive | null) => html`
` root.render(template(stable, unstable())) assert_deep_eq(sequence, ['stable create', 'unstable create']) sequence.length = 0 root.render(template(stable, unstable())) assert_deep_eq(sequence, ['unstable cleanup', 'unstable create']) sequence.length = 0 root.render(template(null, null)) assert_deep_eq(sequence, ['stable cleanup', 'unstable cleanup']) })