import { html, type Displayable } from 'dhtml'
import { assert, assert_eq, test } from '../../../scripts/test/test.ts'
import { setup } from './setup.ts'
test('basic html renders correctly', () => {
const { root, el } = setup()
root.render(html`
Hello, world!
`)
assert_eq(el.innerHTML, 'Hello, world!
')
})
test('inner content renders correctly', () => {
const { root, el } = setup()
root.render(html`${html`Inner content!`}
`)
assert_eq(el.innerHTML, 'Inner content!
')
})
test('template with number renders correctly', () => {
const { root, el } = setup()
const template = (n: number) => html`Hello, ${n}!
`
root.render(template(1))
assert_eq(el.innerHTML, 'Hello, 1!
')
root.render(template(2))
assert_eq(el.innerHTML, 'Hello, 2!
')
})
test('external sibling nodes are not clobbered', () => {
const { root, el } = setup('before
')
root.render(html`Hello, world!
`)
assert_eq(el.innerHTML, 'before
Hello, world!
')
el.appendChild(document.createElement('div')).textContent = 'after'
assert_eq(el.innerHTML, 'before
Hello, world!
after
')
root.render(html`Goodbye, world!
`)
assert_eq(el.innerHTML, 'before
Goodbye, world!
after
')
root.render(html``)
assert_eq(el.innerHTML, 'before
after
')
root.render(html`Hello, world!
`)
assert_eq(el.innerHTML, 'before
Hello, world!
after
')
})
test('identity is updated correctly', () => {
const { root, el } = setup()
const template = (n: Displayable) => html`Hello, ${n}!
`
const template2 = (n: Displayable) => html`Hello, ${n}!
`
root.render(template(1))
assert_eq(el.innerHTML, 'Hello, 1!
')
let h1 = el.children[0]
const text = [...h1.childNodes].find((node): node is Text => node instanceof Text && node.data === '1')
assert(text)
root.render(template(2))
assert_eq(el.innerHTML, 'Hello, 2!
')
assert_eq(el.children[0], h1)
assert_eq(text.data, '2')
assert([...h1.childNodes].includes(text))
root.render(template2(3))
assert_eq(el.innerHTML, 'Hello, 3!
')
assert(el.children[0] !== h1)
h1 = el.children[0]
root.render(template2(template(template('inner'))))
assert_eq(el.innerHTML, 'Hello, Hello, Hello, inner!
!
!
')
assert_eq(el.children[0], h1)
})
test('basic children render correctly', () => {
const { root, el } = setup()
root.render(html`${'This is a'} ${html`test`} ${html`test`} ${html`test`}`)
assert_eq(el.innerHTML, 'This is a test test test')
})
test('nodes can be embedded', () => {
const { root, el } = setup()
let node: ParentNode = document.createElement('span')
root.render(html`${node}
`)
assert_eq(el.innerHTML, '
')
assert_eq(el.children[0].children[0], node)
node = document.createDocumentFragment()
node.append(document.createElement('h1'), document.createElement('h2'), document.createElement('h3'))
root.render(html`${node}
`)
assert_eq(el.innerHTML, '')
assert_eq(node.children.length, 0)
})
if (false)
test('extra empty text nodes are not added', () => {
const { root, el } = setup()
root.render(html`${'abc'}`)
assert_eq(el.childNodes.length, 1)
assert(el.firstChild instanceof Text)
assert_eq((el.firstChild as Text).data, 'abc')
})
test('ChildPart index shifts correctly', () => {
const { root, el } = setup()
root.render(html`${html`A`}B${'C'}`)
assert_eq(el.innerHTML, 'ABC')
})
test('errors are thrown cleanly', () => {
const { root, el } = setup()
const oops = new Error('oops')
let thrown
try {
root.render(
html`${{
render() {
throw oops
},
}}`,
)
} catch (error) {
thrown = error
}
assert_eq(thrown, oops)
// on an error, don't leave any visible artifacts
assert_eq(el.innerHTML, '')
})
if (__DEV__) {
test('invalid part placement raises error', () => {
const { root, el } = setup()
try {
root.render(html`<${'div'}>${'text'}${'div'}>`)
assert(false)
} catch (error) {
assert(error instanceof Error)
assert_eq(
error.message,
'expected the same number of dynamics as parts. do you have a ${...} in an unsupported place?',
)
}
assert_eq(el.innerHTML, '')
})
test('manually specifying internal template syntax throws', () => {
const { root, el } = setup()
try {
root.render(
html`${1}
`,
)
assert(false)
} catch (error) {
assert(error instanceof Error)
assert_eq(error.message, 'got more parts than expected')
}
assert_eq(el.innerHTML, '')
})
}
test('syntax close but not exact does not throw', () => {
const { root, el } = setup()
root.render(html`dyn-$${0}1$`)
assert_eq(el.innerHTML, 'dyn-$01$')
})
{
const values = {
text: 'text',
number: 1234,
null: null,
iterable: ['iterable', 'of', 'things'],
html: html`html`,
html_element: html`element`,
renderable: {
render() {
return 'hello'
},
},
}
for (const [a_name, a_value] of Object.entries(values)) {
for (const [b_name, b_value] of Object.entries(values)) {
test(`updating across value kinds: ${a_name} -> ${b_name}`, () => {
const { root, el } = setup()
root.render(a_value)
root.render(b_value)
const { root: root2, el: el2 } = setup()
root2.render(b_value)
assert_eq(el.innerHTML, el2.innerHTML)
})
}
}
}