// @vitest-environment jsdom /** * ClientModuleSystem behavior: lazy CJS arrival (bundle execution only * registers the factory), materialization on first import/require with * memoization and recursive self-sequencing, the resolution branch order, * shared in-flight arrival, invalidate-refetch (HMR), style claiming, the * default transport hook, and the loud failure modes (duplicate * registration, cycles, table misses, double boot). */ import { Context } from '@deepseek-ai/cordis' import { afterEach, describe, expect, it, vi } from 'vitest' import { apply, createClientModuleSystem, parseBootManifest, type BootModuleRow, type ClientBundleRegistration, type ClientModuleCreateOptions, type ClientModuleLoader, type ClientModuleLoaderTarget, type DshWindow, } from '../src/client/index.ts' const MODULES_ID = '@deepseek-ai/dsh-client-modules' const win = globalThis as DshWindow const bootstrapExports = { apply, createClientModuleSystem } type Factory = ClientBundleRegistration['factory'] afterEach(() => { vi.unstubAllGlobals() delete win.__ModuleLoader__ for (const el of document.querySelectorAll('style, script')) el.remove() }) const row = (id: string, fields: Partial = {}): BootModuleRow => ({ id, url: `/plugins/${id}/client.js?rev=0`, rev: '0', external: [], ...fields }) interface Bench { loader: ClientModuleLoader target: ClientModuleLoaderTarget fetched: string[] gates: Map void> } /** Build the page-global facade shape consumed by the module system. */ function registrationTarget(pending: ClientBundleRegistration[] = []): ClientModuleLoaderTarget { const pendingQueue = [...pending] const target: ClientModuleLoaderTarget = { mode: 'queue', pendingQueue, load: (registration) => { pendingQueue.push(registration) }, create: options => createClientModuleSystem(target, { id: MODULES_ID, exports: bootstrapExports, }, options), } return target } /** * Loader over scripted bundles: load records the row URL, optionally waits on * a release callback, then registers the scripted factory through the window * sink (`null` scripts a bundle that never calls load). */ function bench( entries: BootModuleRow[], bundles: Record = {}, opts: { seed?: Record gated?: string[] pending?: ClientBundleRegistration[] defaultTransport?: boolean } = {}, ): Bench { const fetched: string[] = [] const gates = new Map void>() const target = registrationTarget(opts.pending) win.__ModuleLoader__ = target const loadBundle = async (url: string): Promise => { fetched.push(url) if (opts.gated?.includes(url) === true) { await new Promise((resolve) => { gates.set(url, resolve) }) } const id = /\/plugins\/(.+)\/client\.js/.exec(url)?.[1] const factory = id === undefined ? undefined : bundles[id] if (factory == null || id === undefined) return win.__ModuleLoader__?.load({ id, factory }) } const loader = target.create({ boot: { rev: 'graph', entries }, staticModules: opts.seed ?? {}, ...(opts.defaultTransport === true ? {} : { loadBundle }), }) return { loader, target, fetched, gates } } describe('Cordis plugin face', () => { it('rejects activation before the HTML facade creates the module system', () => { expect(() => { apply(new Context()) }).toThrow('createClientModuleSystem must run before plugin boot') }) }) describe('lazy CJS arrival', () => { it('drains registrations queued by parser-blocking preload scripts into the same live facade', async () => { const b = bench([row('runtime')], {}, { pending: [{ id: 'runtime', factory: () => ({ marker: 'preloaded' }) }], }) const exports = await b.loader.import('runtime', '', {}) expect((exports as { marker: string }).marker).toBe('preloaded') expect(b.target.pendingQueue).toEqual([]) expect(b.fetched).toEqual([]) expect(win.__ModuleLoader__).toBe(b.target) expect(b.target.mode).toBe('live') }) it('prefetch loads and registers but does not run the factory', async () => { const ran: string[] = [] const b = bench([row('a')], { a: () => { ran.push('a'); return {} } }) await b.loader.prefetch('a') expect(b.fetched).toEqual(['/plugins/a/client.js?rev=0']) expect(ran).toEqual([]) expect(b.loader.loadCache.has('a')).toBe(false) }) it('import materializes once and memoizes the exports', async () => { const ran: string[] = [] const b = bench([row('a')], { a: () => { ran.push('a'); return { marker: 'a' } } }) const first = await b.loader.import('a', '', {}) const second = await b.loader.import('a', '', {}) expect(first).toBe(second) expect((first as { marker: string }).marker).toBe('a') expect(ran).toEqual(['a']) expect(b.loader.loadCache.get('a')?.id).toBe('a') }) it('import without prefetch loads, registers, and materializes in one call', async () => { const b = bench([row('a')], { a: () => ({ marker: 'direct' }) }) const exports = await b.loader.import('a', '', {}) expect((exports as { marker: string }).marker).toBe('direct') expect(b.fetched).toHaveLength(1) }) it('registers declared dynamic requests before materializing their consumer', async () => { const b = bench([ row('consumer', { external: ['provider/client', 'react'] }), row('provider'), ], { consumer: req => ({ provider: req('provider/client'), react: req('react') }), provider: () => ({ marker: 'provider' }), }, { seed: { react: { marker: 'react' } } }) const exports = await b.loader.import('consumer', '', {}) as { provider: { marker: string } react: { marker: string } } expect(b.fetched).toEqual([ '/plugins/provider/client.js?rev=0', '/plugins/consumer/client.js?rev=0', ]) expect(exports.provider.marker).toBe('provider') expect(exports.react.marker).toBe('react') }) it('concurrent callers share one in-flight arrival and materialize once', async () => { const ran: string[] = [] const url = '/plugins/a/client.js?rev=0' const b = bench([row('a')], { a: () => { ran.push('a'); return { marker: 'a' } } }, { gated: [url] }) const first = b.loader.import('a', '', {}) const second = b.loader.import('a', '', {}) const third = b.loader.prefetch('a') b.gates.get(url)?.() const [s1, s2] = await Promise.all([first, second, third]) expect(s1).toBe(s2) expect(b.fetched).toEqual([url]) expect(ran).toEqual(['a']) }) it('prefetch after registration is a no-op without invalidate', async () => { const b = bench([row('a')], { a: () => ({}) }) await b.loader.prefetch('a') await b.loader.prefetch('a') expect(b.fetched).toHaveLength(1) }) }) describe('require resolution', () => { it('a factory requiring a registered-but-unmaterialized module materializes it recursively', async () => { const order: string[] = [] const b = bench([row('a'), row('b')], { a: (req) => { order.push('a') const dep = req('b/client') as { helper: string } return { got: dep.helper } }, b: () => { order.push('b'); return { helper: 'from-b' } }, }) await b.loader.prefetch('a') await b.loader.prefetch('b') const exports = await b.loader.import('a', '', {}) expect((exports as { got: string }).got).toBe('from-b') expect(order).toEqual(['a', 'b']) expect(b.loader.loadCache.get('a')?.edges.has('b/client')).toBe(true) expect(b.loader.loadCache.has('b')).toBe(true) }) it('require prefers the platform seed word over the module table', async () => { const react = { marker: 'react' } const b = bench([row('a')], { a: req => ({ dep: req('react') }), }, { seed: { react } }) const exports = await b.loader.import('a', '', {}) expect((exports as { dep: unknown }).dep).toBe(react) expect(await b.loader.import('react', '', {})).toBe(react) expect(b.loader.loadCache.has('react')).toBe(false) }) it('require answers an already-materialized module from the cache', async () => { let built = 0 const b = bench([row('a'), row('c')], { a: req => ({ dep: req('c') }), c: () => { built += 1; return { marker: 'c' } }, }) const c = await b.loader.import('c', '', {}) const a = await b.loader.import('a', '', {}) expect((a as { dep: unknown }).dep).toBe(c) expect(built).toBe(1) }) it('a require that misses the module table is loud', async () => { const b = bench([row('a')], { a: req => ({ dep: req('ghost') }) }) await expect(b.loader.import('a', '', {})).rejects.toThrow('require("ghost") missed the module table') }) it('a require cycle is fatal', async () => { const b = bench([row('a'), row('b')], { a: req => ({ dep: req('b') }), b: req => ({ dep: req('a') }), }) await b.loader.prefetch('b') await expect(b.loader.import('a', '', {})).rejects.toThrow('require cycle through "a"') }) }) describe('bootstrap module', () => { it('caches the materialized modules exports under the package id and /client alias', async () => { const b = bench([ row('consumer', { external: [`${MODULES_ID}/client`] }), row(MODULES_ID), ], { consumer: req => ({ dep: req(`${MODULES_ID}/client`) }), }) await b.loader.prefetch(MODULES_ID) const exports = await b.loader.import('consumer', '', {}) as { dep: unknown } expect(exports.dep).toBe(bootstrapExports) expect(await b.loader.import(`${MODULES_ID}/client`, '', {})).toBe(bootstrapExports) expect(b.fetched).toEqual(['/plugins/consumer/client.js?rev=0']) }) it('publishes the same closed-over system when the modules Cordis plugin activates', () => { const b = bench([]) const ctx = new Context() apply(ctx) expect(ctx.modules).toBe(b.loader) }) it('rejects a second queued registration for the bootstrap id', () => { expect(() => bench([], {}, { pending: [{ id: `${MODULES_ID}/client`, factory: () => ({}) }], })).toThrow(`duplicate factory registration for "${MODULES_ID}/client"`) }) }) describe('failure modes', () => { it('duplicate factory registration is loud', () => { bench([]) win.__ModuleLoader__?.load({ id: 'x', factory: () => ({}) }) expect(() => win.__ModuleLoader__?.load({ id: 'x', factory: () => ({}) })) .toThrow('duplicate factory registration for "x"') }) it('a bundle that never registers its id is loud', async () => { const b = bench([row('a')], { a: null }) await expect(b.loader.import('a', '', {})).rejects.toThrow('without registering "a"') }) it('an unknown import specifier is loud', async () => { const b = bench([]) await expect(b.loader.import('nope', '', {})).rejects.toThrow('cannot resolve "nope"') }) it('an unknown prefetch id is loud', async () => { const b = bench([]) await expect(b.loader.prefetch('nope')).rejects.toThrow('prefetch("nope") — not a graph entry') }) it('a duplicate graph entry is loud at construction', () => { expect(() => bench([row('a'), row('a')])).toThrow('duplicate graph entry "a"') }) it('a module arrival cycle is loud even if a malformed host graph reaches the browser', async () => { const b = bench([ row('a', { external: ['b'] }), row('b', { external: ['a'] }), ]) await expect(b.loader.prefetch('a')).rejects.toThrow('module arrival cycle a -> b -> a') }) it('double boot is loud', () => { const b = bench([]) const options: ClientModuleCreateOptions = { boot: { rev: 'graph', entries: [] }, staticModules: {}, } expect(() => b.target.create(options)).toThrow('create called after module-system boot') }) }) describe('boot manifest wire', () => { it('normalizes absent shared-module fields and carries the declared ones', () => { const manifest = parseBootManifest({ rev: 'graph', entries: [ { id: 'a', url: '/plugins/a/client.js', rev: '1' }, { id: 'b', url: '/plugins/b/client.js', rev: '2', external: ['react'] }, ], }) expect(manifest.modules).toEqual([ { id: 'a', url: '/plugins/a/client.js', rev: '1', external: [] }, { id: 'b', url: '/plugins/b/client.js', rev: '2', external: ['react'] }, ]) }) it('rejects a non-array external', () => { expect(() => parseBootManifest({ rev: 'graph', entries: [{ id: 'a', url: '/a', rev: '1', external: 'react' }], })).toThrow('client-modules: boot manifest entry "a" external must be a string array') }) }) describe('HMR reset', () => { it('invalidate drops the factory and record so the module reloads and re-registers', async () => { let generation = 0 const b = bench([row('a')], { a: () => ({ generation: ++generation }) }) const first = await b.loader.import('a', '', {}) b.loader.invalidate('a') expect(b.loader.loadCache.has('a')).toBe(false) await b.loader.prefetch('a') const second = await b.loader.import('a', '', {}) expect(b.fetched).toHaveLength(2) expect((first as { generation: number }).generation).toBe(1) expect((second as { generation: number }).generation).toBe(2) }) }) describe('style claiming', () => { it('claims untagged style tags for the materializing plugin and inventories owned css ids', async () => { const foreign = document.createElement('style') foreign.setAttribute('data-plugin', 'other') document.head.appendChild(foreign) const b = bench([row('a')], { a: () => { document.head.appendChild(document.createElement('style')) const tagged = document.createElement('style') tagged.setAttribute('data-plugin', 'a') tagged.setAttribute('data-plugin-css', 'sheet-1') document.head.appendChild(tagged) return {} }, }) await b.loader.import('a', '', {}) expect(b.loader.loadCache.get('a')?.styles).toEqual(['a', 'sheet-1']) expect(document.querySelectorAll('style[data-plugin="a"]')).toHaveLength(2) expect(foreign.getAttribute('data-plugin')).toBe('other') }) it('materialization without a document skips the style inventory', async () => { const b = bench([row('a')], { a: () => ({}) }) vi.stubGlobal('document', undefined) try { await b.loader.import('a', '', {}) } finally { vi.unstubAllGlobals() } expect(b.loader.loadCache.get('a')?.styles).toEqual([]) }) }) describe('default transport seam', () => { it('loads through an external classic script and removes the settled node', async () => { const append = vi.spyOn(document.head, 'append').mockImplementation((...nodes) => { const script = nodes[0] if (!(script instanceof HTMLScriptElement)) throw new Error('expected script node') expect(script.async).toBe(true) expect(script.getAttribute('src')).toBe('/plugins/dee/client.js?rev=0') queueMicrotask(() => { win.__ModuleLoader__?.load({ id: 'dee', factory: () => ({ marker: 'via-script' }) }) script.dispatchEvent(new Event('load')) }) }) const b = bench([row('dee')], {}, { defaultTransport: true }) const exports = await b.loader.import('dee', '', {}) expect((exports as { marker: string }).marker).toBe('via-script') expect(append).toHaveBeenCalledOnce() expect([...document.querySelectorAll('script')]).toEqual([]) }) it('a script load failure is loud and removes the node', async () => { vi.spyOn(document.head, 'append').mockImplementation((...nodes) => { const script = nodes[0] if (!(script instanceof HTMLScriptElement)) throw new Error('expected script node') queueMicrotask(() => { script.dispatchEvent(new Event('error')) }) }) const b = bench([row('dee')], {}, { defaultTransport: true }) await expect(b.loader.prefetch('dee')).rejects.toThrow( 'bundle script /plugins/dee/client.js?rev=0 failed to load', ) expect([...document.querySelectorAll('script')]).toEqual([]) }) })