a post-component library for building user-interfaces on the web.
at main 245 lines 5.6 kB view raw
1import { html } from 'dhtml' 2import { attr, on, type Directive } from 'dhtml/client' 3import { assert, assert_deep_eq, assert_eq, test } from '../../../scripts/test/test.ts' 4import { setup } from './setup.ts' 5 6test('directive functions work correctly', () => { 7 const { root, el } = setup() 8 9 const redifier: Directive = node => { 10 if (!(node instanceof HTMLElement)) throw new Error('expected HTMLElement') 11 node.style.color = 'red' 12 return () => { 13 node.style.color = '' 14 } 15 } 16 const flipper: Directive = node => { 17 if (!(node instanceof HTMLElement)) throw new Error('expected HTMLElement') 18 node.style.transform = 'scaleX(-1)' 19 return () => { 20 node.style.transform = '' 21 } 22 } 23 24 const template = (d: Directive | null) => html`<div ${d}>Hello, world!</div>` 25 26 root.render(template(redifier)) 27 const div = el.querySelector('div') 28 assert(div) 29 assert_eq(div.style.cssText, 'color: red;') 30 31 root.render(template(flipper)) 32 assert_eq(div.style.cssText, 'transform: scaleX(-1);') 33 34 root.render(template(null)) 35 assert_eq(div.style.cssText, '') 36 37 root.render(null) 38}) 39 40test('directive functions with values work correctly', () => { 41 const { root, el } = setup() 42 43 function classes(value: string[]): Directive { 44 const values = value.filter(Boolean) 45 return node => { 46 node.classList.add(...values) 47 return () => { 48 node.classList.remove(...values) 49 } 50 } 51 } 52 53 const template = (c: string[]) => html`<div class="foo" ${classes(c)}>Hello, world!</div>` 54 55 root.render(template(['a', 'b'])) 56 const div = el.querySelector('div') 57 assert(div) 58 assert_eq(div.className, 'foo a b') 59 60 root.render(template(['c', 'd'])) 61 assert_eq(div.className, 'foo c d') 62 63 root.render(template([])) 64 assert_eq(div.className, 'foo') 65}) 66 67test('attr directive works correctly', () => { 68 const { root, el } = setup() 69 70 const template = (value: string | null) => html` 71 <input id="attr-works-input"></input> 72 <label ${attr('for', value)}>Hello, world!</label> 73 ` 74 75 root.render(template('attr-works-input')) 76 assert_eq(el.querySelector('label')!.htmlFor, 'attr-works-input') 77 78 root.render(template('updated')) 79 assert_eq(el.querySelector('label')!.htmlFor, 'updated') 80 81 root.render(template(null)) 82 assert_eq(el.querySelector('label')!.htmlFor, '') 83}) 84 85test('attr directive supports booleans', () => { 86 const { root, el } = setup() 87 88 const template = (value: boolean) => html`<input ${attr('disabled', value)} />` 89 90 root.render(template(true)) 91 assert_eq(el.querySelector('input')!.disabled, true) 92 93 root.render(template(false)) 94 assert_eq(el.querySelector('input')!.disabled, false) 95}) 96 97test('on directive works correctly', () => { 98 const { root, el } = setup() 99 let count = 0 100 let event: Event 101 102 const template = (handler: EventListener | null) => html` 103 <button ${handler ? on('click', handler) : null}>Click me</button> 104 ` 105 106 root.render( 107 template(e => { 108 count++ 109 event = e 110 }), 111 ) 112 const button = el.querySelector('button')! 113 114 button.click() 115 assert_eq(count, 1) 116 assert(event! instanceof Event) 117 assert_eq(event.type, 'click') 118 119 button.click() 120 assert_eq(count, 2) 121 122 root.render(template(null)) 123 button.click() 124 assert_eq(count, 2) 125}) 126 127test('on directive handles event listener updates', () => { 128 const { root, el } = setup() 129 let count1 = 0 130 let count2 = 0 131 132 const template = (handler: EventListener) => html`<button ${on('click', handler)}>Click me</button>` 133 134 root.render( 135 template(() => { 136 count1++ 137 }), 138 ) 139 const button = el.querySelector('button')! 140 141 button.click() 142 assert_eq(count1, 1) 143 assert_eq(count2, 0) 144 145 root.render( 146 template(() => { 147 count2++ 148 }), 149 ) 150 button.click() 151 assert_eq(count1, 1) 152 assert_eq(count2, 1) 153}) 154 155test('on directive supports different event types', () => { 156 const { root, el } = setup() 157 let enter_count = 0 158 let leave_count = 0 159 160 const template = () => html` 161 <div 162 ${on('mouseenter', () => { 163 enter_count++ 164 })} 165 ${on('mouseleave', () => { 166 leave_count++ 167 })} 168 > 169 Hover me 170 </div> 171 ` 172 173 root.render(template()) 174 const div = el.querySelector('div')! 175 176 div.dispatchEvent(new MouseEvent('mouseenter')) 177 assert_eq(enter_count, 1) 178 assert_eq(leave_count, 0) 179 180 div.dispatchEvent(new MouseEvent('mouseleave')) 181 assert_eq(enter_count, 1) 182 assert_eq(leave_count, 1) 183}) 184 185test('on directive supports event listener options', () => { 186 const { root, el } = setup() 187 let count = 0 188 189 const template = (options?: AddEventListenerOptions) => html` 190 <button 191 ${on( 192 'click', 193 () => { 194 count++ 195 }, 196 options, 197 )} 198 > 199 Click me 200 </button> 201 ` 202 203 root.render(template({ once: true })) 204 const button = el.querySelector('button')! 205 206 button.click() 207 assert_eq(count, 1) 208 209 button.click() 210 assert_eq(count, 1) 211 212 root.render(template()) 213 button.click() 214 assert_eq(count, 2) 215 216 button.click() 217 assert_eq(count, 3) 218}) 219 220test('same directive function is not re-invoked or cleaned up', () => { 221 const { root } = setup() 222 223 const sequence: string[] = [] 224 const stable = () => { 225 sequence.push('stable create') 226 return () => sequence.push('stable cleanup') 227 } 228 const unstable = () => () => { 229 sequence.push('unstable create') 230 return () => sequence.push('unstable cleanup') 231 } 232 233 const template = (d1: Directive | null, d2: Directive | null) => html`<div ${d1} ${d2}></div>` 234 235 root.render(template(stable, unstable())) 236 assert_deep_eq(sequence, ['stable create', 'unstable create']) 237 sequence.length = 0 238 239 root.render(template(stable, unstable())) 240 assert_deep_eq(sequence, ['unstable cleanup', 'unstable create']) 241 sequence.length = 0 242 243 root.render(template(null, null)) 244 assert_deep_eq(sequence, ['stable cleanup', 'unstable cleanup']) 245})