`;
window.state = NewState({ msg: 'hello' });
RefreshState(document.documentElement);
const node = $('#test-node');
if (node.textContent !== 'hello') throw new Error('$text binding failed');
state.msg = 'world';
if (node.textContent !== 'world') throw new Error('$text update failed');
// 2. $if directive
document.body.innerHTML = `
Visible
`;
state.show = false;
RefreshState(document.documentElement);
if ($('#if-content')) throw new Error('$if failed: should be hidden');
const wait = () => new Promise(r => setTimeout(r, 10));
state.show = true;
await wait();
if (!$('#if-content') || $('#if-content').textContent !== 'Visible') throw new Error('$if failed: should be visible');
state.show = false;
await wait();
if ($('#if-content')) throw new Error('$if failed: should be hidden again');
// 3. $each directive (Index-based reuse)
document.body.innerHTML = `
`;
state.items = [{ name: 'A' }, { name: 'B' }];
RefreshState(document.documentElement);
await wait(); // Wait for MutationObserver
let items = $$('#list-test li');
console.log('$each items length:', items.length);
if (items.length > 0) console.log('$each first item text:', items[0].textContent);
if (items.length !== 2 || items[0].textContent !== 'A' || items[1].textContent !== 'B') throw new Error('$each initialization failed');
const firstNode = items[0];
state.items = [{ name: 'A-mod' }, { name: 'B' }, { name: 'C' }];
await wait();
items = $$('#list-test li');
if (items.length !== 3 || items[0].textContent !== 'A-mod') throw new Error('$each update failed');
if (items[0] !== firstNode) throw new Error('$each reuse failed: should reuse existing DOM nodes');
state.items = [{ name: 'C' }];
await wait();
items = $$('#list-test li');
if (items.length !== 1 || items[0].textContent !== 'C') throw new Error('$each removal failed');
// 4. Two-way binding (bind)
console.log('Testing $bind...');
document.body.innerHTML = ``;
window.state = NewState({ val: 'initial' });
RefreshState(document.documentElement);
await wait();
const input = $('#input-test');
if (input.value !== 'initial') throw new Error('$bind initial value failed');
input.value = 'changed';
input.dispatchEvent(new Event('input', { bubbles: true }));
if (state.val !== 'changed') throw new Error('$bind write-back failed');
// 5. Unbinding cleanup (mock check)
// _unbindTree is called when nodes are removed via $if or $each.
// We verify that after $if=false, the state mappings for that node are cleared.
// This is hard to test directly without exposing internal Map, but we can check if it crashes.
state.show = true;
const ifNode = $('#if-content');
state.show = false; // Trigger _unbindTree via _clearRenderedNodes -> remove() -> MutationObserver (or manual in some cases)
// 6. Double evaluation ($$ prefix)
console.log('Testing double evaluation ($$)...');
document.body.innerHTML = `
Dynamic Visible
`;
const doubleState = NewState({
innerExp: 'state.innerShow',
innerShow: false
});
window.state = doubleState;
document.documentElement._thisObj = doubleState;
RefreshState(document.documentElement);
await wait();
if ($('#inner-node')) throw new Error('$$if failed: should be hidden initially');
console.log('Enabling inner node...');
doubleState.innerShow = true;
RefreshState(document.documentElement);
await wait();
const inner = $('#inner-node');
if (!inner) throw new Error('$$if failed: should be visible after innerShow=true');
console.log('Inner node visible:', inner.textContent);
// 7. Nested $$if
console.log('Testing nested $$if...');
document.body.innerHTML = `
Nested Visible
`;
doubleState.outer = true;
doubleState.innerShow = false;
RefreshState(document.documentElement);
await wait();
if ($('#nested-inner')) throw new Error('nested $$if failed: should be hidden initially');
doubleState.innerShow = true;
RefreshState(document.documentElement);
await wait();
if (!$('#nested-inner')) throw new Error('nested $$if failed: should be visible after update');
console.log('dom.js tests passed');
return true;
}