import assert from 'node:assert/strict'; import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { JSDOM } from 'jsdom'; const markdown = await readFile(new URL('../../../docs/hosting-views.md', import.meta.url), 'utf8'); const marker = ''; assert.equal(markdown.split(marker).length - 1, 1); const match = markdown.slice(markdown.indexOf(marker) + marker.length) .match(/^\s*```javascript\n([\s\S]*?)\n```/); assert.ok(match, 'The host example must be an executable module'); await mkdir(new URL('./dist/', import.meta.url), { recursive: true }); const moduleUrl = new URL('./dist/host-screen.mjs', import.meta.url); await writeFile(moduleUrl, match[1]); const dom = new JSDOM('
'); globalThis.window = dom.window; globalThis.document = dom.window.document; const host = document.querySelector('main'); const { mountScreen } = await import(moduleUrl.href); const { View } = await import('marionette'); const { default: LitDomApi } = await import('@mnjs/adapters/dom/lit-html'); const { html } = await import('lit-html'); const { AsyncDirective } = await import('lit-html/async-directive.js'); const { directive } = await import('lit-html/directive.js'); const notifications = []; class Resource extends AsyncDirective { render() { notifications.push(`render:${this.isConnected}`); return 'Child'; } reconnected() { notifications.push('connected'); } disconnected() { notifications.push('disconnected'); } } const resource = directive(Resource); let clicks = 0; const Child = View.extend({ template: () => html``, events: { 'click button': () => clicks++ } }); Child.setDomApi(LitDomApi); const Screen = View.extend({ template: () => '
', regions: { content: 'section' }, onRender() { this.showChildView('content', new Child()); } }); let mounted; let unmanaged; try { // Public reproduction of the consumer's raw-insertion boundary. unmanaged = new Child(); unmanaged.render(); host.replaceChildren(unmanaged.el); assert.equal(unmanaged.el.isConnected, true); assert.equal(unmanaged.isAttached(), false); assert.deepEqual(notifications, ['render:false']); unmanaged.destroy(); notifications.length = 0; for (let cycle = 0; cycle < 2; cycle++) { mounted = mountScreen(host, Screen); const child = mounted.view.getChildView('content'); assert.equal(mounted.view.isAttached(), true); assert.equal(child.isAttached(), true); assert.deepEqual(notifications, ['render:false', 'connected']); const button = child.el.querySelector('button'); button.click(); assert.equal(clicks, cycle + 1); mounted.destroy(); mounted.destroy(); assert.equal(mounted.view.isDestroyed(), true); assert.equal(child.isDestroyed(), true); assert.deepEqual(notifications, ['render:false', 'connected', 'disconnected']); button.click(); assert.equal(clicks, cycle + 1, 'Destroyed child must not handle clicks'); assert.equal(host.isConnected, true, 'Shell retains its host'); assert.equal(host.childNodes.length, 0); notifications.length = 0; } console.log('Hosted View: raw insertion mismatch and managed Lit child cleanup passed'); } finally { mounted?.destroy(); unmanaged?.destroy(); dom.window.close(); delete globalThis.window; delete globalThis.document; }