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 () => ;
});
const el = document.createElement('div');
render(, el);
expect(el.innerHTML).toBe(
''
);
middle = 'text';
bump();
await nextTick();
expect(el.innerHTML).toBe('');
middle = ;
bump();
await nextTick();
middle = element;
bump();
await nextTick();
expect(el.innerHTML).toBe(
''
);
});
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('');
keyed = true;
bump();
await nextTick();
expect(el.innerHTML).toBe('');
});
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('');
});
});