import { describe, it, expect } from 'vitest'; import { h, Fragment, render, mount, mountCallback, nextTick } from '@/index'; /** * Structural cases found by core-renderMatrix / core-renderFuzz. */ describe('structural update regressions', () => { it('unmounts a component replaced by a fragment with as many children', async () => { // A component's node is a one-child fragment, so <> used to be // taken for the same fragment and updated in place: the DOM was right, // but the component was never unmounted. let unmounted = 0; let show = true; let bump = () => {}; const Child = mount(() => { mountCallback(() => () => { unmounted += 1; }); return () =>

child

; }); const Parent = mount(renew => { bump = renew; return () => show ? ( ) : ( frag ); }); const el = document.createElement('div'); render(, el); expect(el.innerHTML).toBe('

child

'); show = false; bump(); await nextTick(); expect(el.innerHTML).toBe('frag'); expect(unmounted).toBe(1); }); it('keeps the position of a fragment or component replaced inside an unkeyed array', async () => { // The replacement used to be appended after the array's last item. let middle: unknown = ( frag ); let bump = () => {}; const Comp = mount(() => () => comp); const Parent = mount(renew => { bump = renew; return () =>
    {[
  • first
  • , middle,
  • last
  • ]}
; }); const el = document.createElement('div'); render(, el); expect(el.innerHTML).toBe( '
  • first
  • frag
  • last
' ); middle = 'text'; bump(); await nextTick(); expect(el.innerHTML).toBe('
  • first
  • text
  • last
'); middle = ; bump(); await nextTick(); middle = element; bump(); await nextTick(); expect(el.innerHTML).toBe( '
  • first
  • element
  • last
' ); }); it('inserts before the next sibling still in the document, past newly added ones', async () => { // Concurrent core: an added sibling's element is built ahead of the // commit, outside the document. The anchor search used to stop there and // append 'T' at the end instead of skipping on to the old 'T'. let swapped = false; let bump = () => {}; const Comp = mount(() => () => ( c1c2 )); const Parent = mount(renew => { bump = renew; return () => swapped ? (

{'T'} {null} ) : (

{null} {'T'} ); }); const el = document.createElement('div'); render(, el); expect(el.innerHTML).toBe('

c1c2T'); swapped = true; bump(); await nextTick(); expect(el.innerHTML).toBe('

Tc1c2'); }); it('removes every old item when an unkeyed array becomes a keyed one', async () => { // Originals without a key share the key `undefined`; only the first of // them used to be removed, the others stayed in the DOM. let keyed = false; let bump = () => {}; const Parent = mount(renew => { bump = renew; return () => (
    {keyed ? ['a', 'b'].map(k =>
  • {k}
  • ) : [
  • p1
  • , 'p2']}
); }); const el = document.createElement('div'); render(, el); expect(el.innerHTML).toBe('
  • p1
  • p2
'); keyed = true; bump(); await nextTick(); expect(el.innerHTML).toBe('
  • a
  • b
'); }); it('removes items whose key appeared twice in the previous render', async () => { let items = [ { key: 'a', text: 'a1' }, { key: 'a', text: 'a2' }, { key: 'b', text: 'b' }, ]; let bump = () => {}; const Parent = mount(renew => { bump = renew; return () => (
    {items.map(item => (
  • {item.text}
  • ))}
); }); const el = document.createElement('div'); render(, el); items = [{ key: 'b', text: 'b' }]; bump(); await nextTick(); expect(el.innerHTML).toBe('
  • b
'); }); });