import { describe, it, expect } from 'vitest' import { createRoot } from '../lib/vdom.ts' import { invariant } from '../lib/invariant.ts' describe('vnode rendering', () => { describe('type<-->type updates', () => { it('updates a text node', () => { let container = document.createElement('div') let { render } = createRoot(container) render('Hello, world!') expect(container.innerHTML).toBe('Hello, world!') render('Hello, world! 2') expect(container.innerHTML).toBe('Hello, world! 2') }) it('updates an element', () => { let container = document.createElement('div') let { render } = createRoot(container) render(
Hello, world!
) expect(container.innerHTML).toBe('
Hello, world!
') let div = container.querySelector('div') render(
Hello, world! 2
) expect(container.innerHTML).toBe('
Hello, world! 2
') expect(container.querySelector('div')).toBe(div) }) it('updates an element with attributes', () => { let container = document.createElement('div') let { render } = createRoot(container) render() let input = container.querySelector('input') invariant(input) expect(input.getAttribute('id')).toBe('hello') expect(input.value).toBe('world') render() expect(container.querySelector('input')).toBe(input) expect(input.getAttribute('id')).toBe('hello') expect(input.value).toBe('world 2') }) it('updates a fragment', () => { let container = document.createElement('div') let { render } = createRoot(container) render( <>

Hello

world!

, ) let pTags = container.querySelectorAll('p') invariant(pTags.length === 2) expect(container.innerHTML).toBe('

Hello

world!

') render( <>

Goodbye

Universe

, ) expect(container.innerHTML).toBe('

Goodbye

Universe

') let newPTags = container.querySelectorAll('p') expect(newPTags.length).toBe(2) expect(newPTags[0]).toBe(pTags[0]) expect(newPTags[1]).toBe(pTags[1]) }) it('updates a component', () => { let container = document.createElement('div') let setupCalls = 0 function App() { let state = ++setupCalls return ({ title }: { title: string }) => (
{title} {state}
) } let root = createRoot(container) root.render() expect(container.innerHTML).toBe('
Hello 1
') root.render() expect(container.innerHTML).toBe('
Goodbye 1
') }) it('updates a component with a fragment', () => { let container = document.createElement('div') let setupCalls = 0 function App() { let state = ++setupCalls return ({ title }: { title: string }) => ( <> {title} {state} ) } let root = createRoot(container) root.render() expect(container.innerHTML).toBe('Hello1') root.render() expect(container.innerHTML).toBe('Goodbye1') }) }) describe('simple replacement', () => { it('replaces element -> text', () => { let container = document.createElement('div') let root = createRoot(container) root.render(
Hello, world!
) expect(container.innerHTML).toBe('
Hello, world!
') root.render('Goodbye, element!') expect(container.innerHTML).toBe('Goodbye, element!') }) it('replaces text -> element', () => { let container = document.createElement('div') let root = createRoot(container) root.render('Hello, world!') expect(container.innerHTML).toBe('Hello, world!') root.render(
Goodbye, world!
) expect(container.innerHTML).toBe('
Goodbye, world!
') }) it('replaces element -> component', () => { let container = document.createElement('div') let root = createRoot(container) root.render(
Hello, world!
) expect(container.innerHTML).toBe('
Hello, world!
') function App() { return () =>
Goodbye, world!
} root.render() expect(container.innerHTML).toBe('
Goodbye, world!
') }) it('replaces component -> element', () => { let container = document.createElement('div') let root = createRoot(container) function App() { return () =>
Hello, world!
} root.render() expect(container.innerHTML).toBe('
Hello, world!
') root.render(
Goodbye, world!
) expect(container.innerHTML).toBe('
Goodbye, world!
') }) it('replaces element -> element', () => { let container = document.createElement('div') let root = createRoot(container) root.render(
Hello, world!
) expect(container.innerHTML).toBe('
Hello, world!
') root.render() expect(container.innerHTML).toBe('') }) it('replaces component -> component', () => { let container = document.createElement('div') let root = createRoot(container) function App() { return () =>
Hello, world!
} root.render() expect(container.innerHTML).toBe('
Hello, world!
') function App2() { return () =>
Goodbye, world!
} root.render() expect(container.innerHTML).toBe('
Goodbye, world!
') }) it('replaces component -> fragment', () => { let container = document.createElement('div') let root = createRoot(container) function App() { return () =>
Hello, world!
} root.render() expect(container.innerHTML).toBe('
Hello, world!
') root.render( <>

Goodbye

world!

, ) expect(container.innerHTML).toBe('

Goodbye

world!

') }) it('replaces fragment -> component', () => { let container = document.createElement('div') let root = createRoot(container) root.render( <>
Hello, world!
, ) expect(container.innerHTML).toBe('
Hello, world!
') function App() { return () =>
Goodbye, world!
} root.render() expect(container.innerHTML).toBe('
Goodbye, world!
') }) it('replaces fragment -> element', () => { let container = document.createElement('div') let root = createRoot(container) root.render( <>
Hello, world!
, ) expect(container.innerHTML).toBe('
Hello, world!
') root.render(
Goodbye, world!
) expect(container.innerHTML).toBe('
Goodbye, world!
') }) it('replaces fragment -> text', () => { let container = document.createElement('div') let root = createRoot(container) root.render( <>
Hello, world!
, ) expect(container.innerHTML).toBe('
Hello, world!
') root.render('Goodbye, world!') expect(container.innerHTML).toBe('Goodbye, world!') }) it('replaces text -> component', () => { let container = document.createElement('div') let root = createRoot(container) root.render('Hello, world!') expect(container.innerHTML).toBe('Hello, world!') function App() { return () =>
Goodbye, world!
} root.render() expect(container.innerHTML).toBe('
Goodbye, world!
') }) }) describe('complex replacements', () => { it('preserves siblings', () => { let container = document.createElement('div') let root = createRoot(container) root.render(
div
span

p

, ) expect(container.innerHTML).toBe('
div
span

p

') let div = container.querySelector('div') let p = container.querySelector('p') invariant(div && p) root.render(
div

p

, ) expect(container.innerHTML).toBe('
div

p

') expect(container.querySelector('div')).toBe(div) expect(container.querySelector('p')).toBe(p) }) it('replaces null children', () => { let container = document.createElement('div') let root = createRoot(container) root.render(
div
{null}

p

, ) expect(container.innerHTML).toBe('
div

p

') let div = container.querySelector('div') let p = container.querySelector('p') invariant(div && p) root.render(
div
span

p

, ) expect(container.innerHTML).toBe('
div
span

p

') expect(container.querySelector('div')).toBe(div) expect(container.querySelector('p')).toBe(p) }) it('replaces fragment components', () => { let container = document.createElement('div') let root = createRoot(container) function Frag() { return () => ( <> A B ) } root.render(
main
, ) expect(container.innerHTML).toBe('
AB
main
') let main = container.querySelector('main') invariant(main) root.render(
one
main
, ) expect(container.innerHTML).toBe('
one
main
') expect(container.querySelector('main')).toBe(main) }) it('replaces components within elements', () => { let container = document.createElement('div') let root = createRoot(container) function App() { return () =>
Hello, world!
} root.render(
, ) expect(container.innerHTML).toBe('
Hello, world!
') function App2() { return () =>
Goodbye, world!
} root.render(
, ) expect(container.innerHTML).toBe('
Goodbye, world!
') }) }) })