'use strict'; const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const test = require('node:test'); const root = path.resolve(__dirname, '..', '..'); const source = fs.readFileSync(path.join(root, 'assets/js/rehydrate.js'), 'utf8'); function createRuntime() { const events = []; const document = { dispatchEvent(event) { events.push(event); return true; }, }; function CustomEvent(type, options) { this.type = type; this.bubbles = Boolean(options && options.bubbles); this.detail = options && options.detail; } const errors = []; const context = { window: {}, document, CustomEvent, console: { error(...args) { errors.push(args); }, }, }; context.window.window = context.window; context.window.document = document; vm.runInNewContext(source, context, { filename: 'rehydrate.js' }); return { api: context.window.AksrtFrontend, document, events, errors }; } test('global DOM boot runs registered components once with init reason', () => { const runtime = createRuntime(); const calls = []; runtime.api.register('first', (rootNode, reason) => calls.push([rootNode, reason])); runtime.api.boot(runtime.document); runtime.api.boot(runtime.document); assert.equal(runtime.api.isBooted(), true); assert.deepEqual(calls, [[runtime.document, 'init']]); assert.deepEqual(runtime.events.map((event) => event.type), ['aksrt:rehydrated', 'aksrt:ready']); assert.equal(runtime.events[0].detail.reason, 'init'); assert.equal(runtime.events[1].detail.reason, 'init'); }); test('late registrations initialize immediately after global boot', () => { const runtime = createRuntime(); const calls = []; runtime.api.boot(runtime.document); runtime.api.register('late', (rootNode, reason) => calls.push([rootNode, reason])); assert.deepEqual(calls, [[runtime.document, 'init']]); assert.deepEqual(Array.from(runtime.api.getRegistered()), ['late']); }); test('same-name registration replaces the owner without duplicating it', () => { const runtime = createRuntime(); const calls = []; runtime.api.register('owner', () => calls.push('old')); runtime.api.register('owner', () => calls.push('new')); runtime.api.boot(runtime.document); assert.deepEqual(Array.from(runtime.api.getRegistered()), ['owner']); assert.deepEqual(calls, ['new']); }); test('rehydrate preserves root and reason while isolating component failures', () => { const runtime = createRuntime(); const fragment = { dispatchEvent(event) { runtime.events.push(event); return true; }, }; const calls = []; runtime.api.register('broken', () => { throw new Error('broken'); }); runtime.api.register('healthy', (rootNode, reason) => calls.push([rootNode, reason])); runtime.api.rehydrate(fragment, 'modal'); assert.deepEqual(calls, [[fragment, 'modal']]); assert.equal(runtime.errors.length, 1); assert.equal(runtime.events.at(-1).type, 'aksrt:rehydrated'); assert.equal(runtime.events.at(-1).detail.reason, 'modal'); assert.equal(runtime.events.at(-1).bubbles, true); });