| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- /** ClientRoster: construction, duplicate refusal, order-preserving pick/closure/without with loud unknown names; graphFromRoster. */
- import { createClientModuleSystem, parseBootManifest, type ClientBundleRegistration, type ClientModuleLoaderTarget } from '@deepseek-ai/dsh-client-modules/client'
- import { describe, expect, it } from 'vitest'
- import { ClientRoster, graphFromRoster, type ClientRosterRow } from '../src/assembly/roster.ts'
- import { MODULES_PACKAGE } from '../src/assembly/modules.ts'
- const ROWS: readonly ClientRosterRow[] = [
- { name: MODULES_PACKAGE, inject: [], immediately: true },
- { name: '@x/a', inject: [], immediately: false },
- { name: '@x/b', inject: ['@x/a'], immediately: true },
- ]
- describe('graphFromRoster', () => {
- it('synthesizes one application batch over every row with placeholder URLs', () => {
- expect(graphFromRoster(ROWS)).toEqual({
- rev: 'local',
- entries: [
- { id: MODULES_PACKAGE, url: `/plugins/${MODULES_PACKAGE}/client.js`, rev: 'local', immediately: true },
- { id: '@x/a', url: '/plugins/@x/a/client.js', rev: 'local' },
- { id: '@x/b', url: '/plugins/@x/b/client.js', rev: 'local', inject: ['@x/a'], immediately: true },
- ],
- batches: [{ phase: 'application', url: '/plugins/local.js', rev: 'local', entries: [MODULES_PACKAGE, '@x/a', '@x/b'] }],
- })
- })
- it('parses through the production validator to plugin rows that mirror the roster, refusing duplicates and an empty roster', () => {
- const manifest = parseBootManifest(graphFromRoster(ROWS))
- expect(manifest.plugins).toEqual(ROWS.map(row => ({ id: row.name, inject: [...row.inject], immediately: row.immediately })))
- expect(manifest.modules.map(row => row.initialUrl)).toEqual(['/plugins/local.js', '/plugins/local.js', '/plugins/local.js'])
- const row = ROWS[1]!
- expect(() => parseBootManifest(graphFromRoster([row, row]))).toThrow('duplicate graph entry "@x/a"')
- expect(() => parseBootManifest(graphFromRoster([]))).toThrow('must be a non-empty string array')
- })
- it('imports seeded rows without reaching the bundle transport', async () => {
- const loaded: string[] = []
- const pendingQueue: ClientBundleRegistration[] = []
- const target: ClientModuleLoaderTarget = {
- mode: 'queue',
- pendingQueue,
- load: (registration) => { pendingQueue.push(registration) },
- create: options => createClientModuleSystem(target, { id: MODULES_PACKAGE, exports: {} }, options),
- }
- const a = { apply() {} }
- const b = { apply() {} }
- const modules = target.create({
- boot: graphFromRoster(ROWS),
- staticModules: { '@x/a': a, '@x/b': b },
- loadBundle: async (url) => { loaded.push(url) },
- })
- await expect(modules.import('@x/a')).resolves.toBe(a)
- await expect(modules.import('@x/b')).resolves.toBe(b)
- expect(modules.manifest).toEqual(parseBootManifest(graphFromRoster(ROWS)))
- expect(loaded).toEqual([])
- })
- })
- const A = { name: 'a', inject: [], immediately: true }
- const B = { name: 'b', inject: ['a'], immediately: false }
- const C = { name: 'c', inject: ['b'], immediately: false }
- describe('ClientRoster', () => {
- it('freezes a copy of the rows in composition order', () => {
- const rows = [A, B, C]
- const roster = ClientRoster.of(rows)
- expect(roster.rows).toEqual([A, B, C])
- expect(roster.rows).not.toBe(rows)
- expect(Object.isFrozen(roster.rows)).toBe(true)
- rows.push(A)
- expect(roster.rows).toHaveLength(3)
- })
- it('refuses duplicate names, listing each duplicate once', () => {
- expect(() => ClientRoster.of([A, B, A, C, B, A])).toThrow('duplicate roster rows: a, b')
- })
- it('pick keeps roster order regardless of the requested order', () => {
- const roster = ClientRoster.of([A, B, C])
- expect(roster.pick(['c', 'a']).rows.map(row => row.name)).toEqual(['a', 'c'])
- expect(roster.pick([]).rows).toEqual([])
- expect(Object.isFrozen(roster.pick(['b']).rows)).toBe(true)
- })
- it('without drops the named rows', () => {
- const roster = ClientRoster.of([A, B, C])
- expect(roster.without(['b']).rows.map(row => row.name)).toEqual(['a', 'c'])
- expect(roster.without(['a', 'b', 'c']).rows).toEqual([])
- })
- it('closure keeps the named rows and everything they inject, transitively, in roster order', () => {
- const D = { name: 'd', inject: [], immediately: false }
- const roster = ClientRoster.of([A, D, B, C])
- expect(roster.closure(['c']).rows.map(row => row.name)).toEqual(['a', 'b', 'c'])
- expect(roster.closure(['b', 'd']).rows.map(row => row.name)).toEqual(['a', 'd', 'b'])
- expect(roster.closure(['a']).rows.map(row => row.name)).toEqual(['a'])
- expect(roster.closure([]).rows).toEqual([])
- expect(Object.isFrozen(roster.closure(['c']).rows)).toBe(true)
- })
- it('pick, closure, and without throw on unknown names with the roster listed; closure also refuses an inject outside the roster', () => {
- const roster = ClientRoster.of([A, B])
- expect(() => roster.pick(['a', 'zz'])).toThrow('pick() names outside the roster: zz; roster: a, b')
- expect(() => roster.closure(['zz'])).toThrow('closure() names outside the roster: zz; roster: a, b')
- expect(() => roster.without(['x', 'y'])).toThrow('without() names outside the roster: x, y; roster: a, b')
- expect(() => ClientRoster.of([B]).closure(['b'])).toThrow('b injects a, which is outside the roster')
- })
- it('closure treats the platform seed modules as satisfied without a row', () => {
- const seeded = { name: 'seeded', inject: ['@deepseek-ai/dsh-client-ui-primitives', 'react', 'a'], immediately: false }
- expect(ClientRoster.of([A, seeded]).closure(['seeded']).rows.map(row => row.name)).toEqual(['a', 'seeded'])
- })
- })
|