| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- /**
- * ui-task plugin halves: the browser entry's dictionary and header-slot
- * registrations against the real SlotsService (with fiber teardown proving
- * removal — HMR safety), the inert node entry, and the invariant companion's
- * ownership reservation.
- */
- import { Context } from '@deepseek-ai/cordis'
- import { describe, expect, it } from 'vitest'
- import InvariantService from '@deepseek-ai/dsh-invariants'
- import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
- import { stubSettingsScope } from '@deepseek-ai/dsh-client-test-runtime'
- import { apply as applyLocale, inject as localeInject } from '@deepseek-ai/dsh-client-locale/client'
- import { apply, inject } from '../src/client/index.ts'
- import { apply as applyNode } from '../src/index.ts'
- import * as TaskInvariant from '../src/invariant.ts'
- import { en, NS, zh } from '../src/client/locales.ts'
- /** Slot ledger reader: entry ids currently registered in the header list. */
- function headerEntryIds(ctx: Context): (string | undefined)[] {
- return ctx.slots
- .entries('conversation.session.header.actions')
- .map(entry => entry.options.id)
- }
- /** Boot the browser half over a real slot tree that declares the header list. */
- async function bench(): Promise<{ ctx: Context; fiber: ReturnType<Context['plugin']> }> {
- const ctx = new Context()
- await ctx.plugin(SlotsService).await()
- ctx.slots.register({
- name: 'root',
- children: {
- 'conversation.session.header.actions': { kind: 'list', scope: 'session' },
- },
- } as never, () => null)
- ctx.provide('sessions', {})
- // The locale plugin binds a settings scope, which reads the connection handle
- // and the forwarded-event port.
- ctx.provide('connection', { api: { settings: {} }, isLoopback: false } as never)
- ctx.provide('remote', { $on: () => () => {} } as never)
- ctx.provide('settingsScope', { bind: () => stubSettingsScope().scope } as never)
- await ctx.plugin({ inject: localeInject, apply: applyLocale }).await()
- const fiber = ctx.plugin({ inject: [...inject], apply })
- await fiber.await()
- return { ctx, fiber }
- }
- describe('ui-task browser half', () => {
- it('declares the services it binds', () => {
- expect(inject).toEqual(['sessions', 'slots', 'locale'])
- })
- it('registers the header action, and fiber teardown removes it (HMR safety)', async () => {
- const { ctx, fiber } = await bench()
- expect(headerEntryIds(ctx)).toContain('task-list')
- await fiber.dispose()
- expect(headerEntryIds(ctx)).not.toContain('task-list')
- })
- it('registers both dictionaries under its own namespace and releases them with the fiber', async () => {
- const { ctx, fiber } = await bench()
- const translate = ctx.locale.bind(NS)
- expect(translate('list.aria')).toBe(zh['list.aria'])
- ctx.locale.setLocale('en')
- expect(translate('list.aria')).toBe(en['list.aria'])
- // Withdrawn dictionaries leave the key unresolved rather than translated.
- await fiber.dispose()
- expect(translate('list.aria')).not.toBe(en['list.aria'])
- })
- it('keeps the English dictionary key-identical to the Chinese source of truth', () => {
- expect(Object.keys(en).sort()).toEqual(Object.keys(zh).sort())
- })
- })
- describe('ui-task node half', () => {
- it('contributes no host behavior', () => {
- // The node half exists only so the plugin appears in the Loader tree.
- expect(applyNode).not.toThrow()
- })
- })
- describe('ui-task invariant companion', () => {
- it('reserves package ownership under its declared companion name', async () => {
- const ctx = new Context()
- await ctx.plugin(InvariantService, { enabled: true })
- const fiber = ctx.plugin(TaskInvariant)
- await fiber.await()
- expect(TaskInvariant.name).toBe('client-ui-task-invariant')
- expect(TaskInvariant.inject).toEqual(['invariants'])
- // Emitting an unrelated event proves the companion installed no audit.
- expect(() => { (ctx.emit as (event: string) => void)('slots/changed') }).not.toThrow()
- await fiber.dispose()
- })
- })
|