import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { link } from '../index.ts'
import { createRoot } from '../lib/vdom.ts'
import { renderToString } from '../lib/stream.ts'
import { invariant } from '../lib/invariant.ts'
describe('hydration', () => {
let container: HTMLDivElement
beforeEach(() => {
container = document.createElement('div')
document.body.appendChild(container)
})
afterEach(() => {
document.body.innerHTML = ''
for (let node of Array.from(document.head.childNodes)) {
document.head.removeChild(node)
}
})
describe('special case props to HTML attributes', () => {
it('hydrates className as class attribute', async () => {
let html = await renderToString(
Hello
)
container.innerHTML = html
let existingDiv = container.querySelector('div')
invariant(existingDiv)
expect(existingDiv.getAttribute('class')).toBe('my-class')
let root = createRoot(container)
root.render(
Hello
)
root.flush()
// Same DOM node should be adopted
expect(container.querySelector('div')).toBe(existingDiv)
expect(existingDiv.getAttribute('class')).toBe('my-class')
})
it('hydrates htmlFor as for attribute', async () => {
let html = await renderToString(
,
)
container.innerHTML = html
let existingLabel = container.querySelector('label')
invariant(existingLabel)
expect(existingLabel.getAttribute('for')).toBe('my-input')
let root = createRoot(container)
root.render(
,
)
root.flush()
expect(container.querySelector('label')).toBe(existingLabel)
expect(existingLabel.getAttribute('for')).toBe('my-input')
})
it('hydrates tabIndex as tabindex attribute', async () => {
let html = await renderToString()
container.innerHTML = html
let existingButton = container.querySelector('button')
invariant(existingButton)
let root = createRoot(container)
root.render()
root.flush()
expect(container.querySelector('button')).toBe(existingButton)
expect(existingButton.getAttribute('tabindex')).toBe('0')
})
it('hydrates role and tabIndex added by link mixins', async () => {
let html = await renderToString(
Docs
)
container.innerHTML = html
let existingItem = container.querySelector('li')
invariant(existingItem)
let root = createRoot(container)
root.render(
Docs
)
root.flush()
expect(container.querySelector('li')).toBe(existingItem)
expect(existingItem.getAttribute('role')).toBe('link')
expect(existingItem.getAttribute('tabindex')).toBe('0')
})
it('hydrates acceptCharset as accept-charset attribute', async () => {
let html = await renderToString()
container.innerHTML = html
let existingForm = container.querySelector('form')
invariant(existingForm)
let root = createRoot(container)
root.render()
root.flush()
expect(container.querySelector('form')).toBe(existingForm)
expect(existingForm.getAttribute('accept-charset')).toBe('UTF-8')
})
it('hydrates httpEquiv as http-equiv attribute', async () => {
let html = await renderToString()
container.innerHTML = html
let existingMeta = container.querySelector('meta')
invariant(existingMeta)
let root = createRoot(container)
root.render()
root.flush()
expect(container.querySelector('meta')).toBe(existingMeta)
expect(document.head.querySelector('meta')).toBeNull()
expect(existingMeta.getAttribute('http-equiv')).toBe('refresh')
})
it('hydrates aria-* attributes unchanged', async () => {
let html = await renderToString(
,
)
container.innerHTML = html
let existingButton = container.querySelector('button')
invariant(existingButton)
let root = createRoot(container)
root.render(
,
)
root.flush()
expect(container.querySelector('button')).toBe(existingButton)
expect(existingButton.getAttribute('aria-label')).toBe('Close')
expect(existingButton.getAttribute('aria-expanded')).toBe('false')
})
it('hydrates data-* attributes unchanged', async () => {
let html = await renderToString()
container.innerHTML = html
let existingDiv = container.querySelector('div')
invariant(existingDiv)
let root = createRoot(container)
root.render()
root.flush()
expect(container.querySelector('div')).toBe(existingDiv)
expect(existingDiv.getAttribute('data-testid')).toBe('my-div')
expect(existingDiv.getAttribute('data-value')).toBe('42')
})
it('hydrates SVG xlinkHref as xlink:href', async () => {
let html = await renderToString(
,
)
container.innerHTML = html
let existingUse = container.querySelector('use')
invariant(existingUse)
let root = createRoot(container)
root.render(
,
)
root.flush()
expect(container.querySelector('use')).toBe(existingUse)
expect(existingUse.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe('#icon-star')
})
it('hydrates SVG viewBox with preserved case', async () => {
let html = await renderToString()
container.innerHTML = html
let existingSvg = container.querySelector('svg')
invariant(existingSvg)
let root = createRoot(container)
root.render()
root.flush()
expect(container.querySelector('svg')).toBe(existingSvg)
expect(existingSvg.getAttribute('viewBox')).toBe('0 0 24 24')
})
it('hydrates SVG preserveAspectRatio with preserved case', async () => {
let html = await renderToString()
container.innerHTML = html
let existingSvg = container.querySelector('svg')
invariant(existingSvg)
let root = createRoot(container)
root.render()
root.flush()
expect(container.querySelector('svg')).toBe(existingSvg)
expect(existingSvg.getAttribute('preserveAspectRatio')).toBe('xMidYMid meet')
})
it('hydrates SVG filterUnits with canonical case and semantics', async () => {
let html = await renderToString(
,
)
container.innerHTML = html
let existingFilter = container.querySelector('#f')
invariant(existingFilter instanceof SVGFilterElement)
let root = createRoot(container)
root.render(
,
)
root.flush()
expect(container.querySelector('#f')).toBe(existingFilter)
expect(existingFilter.getAttribute('filterUnits')).toBe('userSpaceOnUse')
expect(existingFilter.getAttribute('filter-units')).toBe(null)
expect(existingFilter.filterUnits.baseVal).toBe(1)
})
})
})