browser-plugin.client.spec.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { Context } from '@deepseek-ai/cordis'
  2. import { describe, expect, it } from 'vitest'
  3. import InvariantRegistry from '@deepseek-ai/dsh-invariants'
  4. import { SlotRegistry } from '@deepseek-ai/dsh-client-ui-renderer/client'
  5. import { stubSettingsScope } from '@deepseek-ai/dsh-client-test-runtime'
  6. import { apply as applyLocale, inject as localeInject } from '@deepseek-ai/dsh-client-locale/client'
  7. import { apply, inject } from '../src/client/index.ts'
  8. import { apply as applyNode } from '../src/index.ts'
  9. import * as ScheduleInvariant from '../src/invariant.ts'
  10. import { en, NS, zh } from '../src/client/locales.ts'
  11. const Empty = () => null
  12. function headerEntryIds(ctx: Context): (string | undefined)[] {
  13. return ctx.slots
  14. .entries('conversation.session.header.actions')
  15. .map(entry => entry.options.id)
  16. }
  17. async function baseContext(): Promise<Context> {
  18. const ctx = new Context()
  19. await ctx.plugin(SlotRegistry).await()
  20. ctx.provide('connection', { api: { settings: {} }, isLoopback: false } as never)
  21. ctx.provide('remote', { $on: () => () => {} } as never)
  22. ctx.provide('settingsScope', { bind: () => stubSettingsScope().scope } as never)
  23. await ctx.plugin({ inject: localeInject, apply: applyLocale }).await()
  24. return ctx
  25. }
  26. function declareHeader(ctx: Context): () => void {
  27. return ctx.slots.register({
  28. name: 'root',
  29. children: {
  30. 'conversation.session.header.actions': { kind: 'list', scope: 'session' },
  31. },
  32. } as never, Empty)
  33. }
  34. describe('ui-schedule browser half', () => {
  35. it('declares only the services used by registration', () => {
  36. expect(inject).toEqual(['slots', 'locale'])
  37. })
  38. it('waits for the header declaration, orders between static context and Jobs, and tears down', async () => {
  39. const ctx = await baseContext()
  40. const fiber = ctx.plugin({ inject: [...inject], apply })
  41. await fiber.await()
  42. expect(headerEntryIds(ctx)).toEqual([])
  43. const header = declareHeader(ctx)
  44. ctx.slots.register({
  45. name: 'conversation.session.header.actions', id: 'agent-preset', order: -10,
  46. }, Empty)
  47. ctx.slots.register({
  48. name: 'conversation.session.header.actions', id: 'job-list', order: 20,
  49. }, Empty)
  50. expect(headerEntryIds(ctx)).toEqual(['agent-preset', 'schedule-catalog', 'job-list'])
  51. await fiber.dispose()
  52. expect(headerEntryIds(ctx)).toEqual(['agent-preset', 'job-list'])
  53. header()
  54. await ctx.fiber.dispose()
  55. })
  56. it('registers both dictionaries and releases them with its fiber', async () => {
  57. const ctx = await baseContext()
  58. declareHeader(ctx)
  59. ctx.locale.setLocale('zh')
  60. const fiber = ctx.plugin({ inject: [...inject], apply })
  61. await fiber.await()
  62. const translate = ctx.locale.bind(NS)
  63. expect(translate('list.aria')).toBe(zh['list.aria'])
  64. ctx.locale.setLocale('en')
  65. expect(translate('list.aria')).toBe(en['list.aria'])
  66. expect(Object.keys(en).sort()).toEqual(Object.keys(zh).sort())
  67. await fiber.dispose()
  68. expect(translate('list.aria')).not.toBe(en['list.aria'])
  69. await ctx.fiber.dispose()
  70. })
  71. })
  72. describe('ui-schedule node and invariant halves', () => {
  73. it('keeps the node half inert', () => {
  74. expect(applyNode).not.toThrow()
  75. })
  76. it('reserves package ownership under its invariant companion name', async () => {
  77. const ctx = new Context()
  78. await ctx.plugin(InvariantRegistry, { enabled: true })
  79. const fiber = ctx.plugin(ScheduleInvariant)
  80. await fiber.await()
  81. expect(ScheduleInvariant.name).toBe('client-ui-schedule-invariant')
  82. expect(ScheduleInvariant.inject).toEqual(['invariants'])
  83. expect(() => {
  84. Reflect.apply(ctx.emit.bind(ctx), undefined, ['unrelated/event'])
  85. }).not.toThrow()
  86. await fiber.dispose()
  87. await ctx.fiber.dispose()
  88. })
  89. })