approval-command.client.spec.tsx 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. // @vitest-environment jsdom
  2. import { Context } from '@deepseek-ai/cordis'
  3. import InvariantRegistry from '@deepseek-ai/dsh-invariants'
  4. import type { ChatSnapshot, UseChat } from '@deepseek-ai/dsh-client-ui-chat/client'
  5. import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
  6. import { render, screen } from '@testing-library/react'
  7. import { describe, expect, it } from 'vitest'
  8. import { ApprovalCommand, commandOf } from '../src/client/chat/ApprovalCommand.tsx'
  9. import { apply as nodeApply } from '../src/index.ts'
  10. import * as ChatInvariant from '../src/invariant.ts'
  11. function props(
  12. nodes: readonly unknown[],
  13. callId = 'call-1',
  14. ): PropsRuntime<'conversation.approval.detail'> {
  15. const snapshot = {
  16. nodes: { values: () => nodes },
  17. } as unknown as ChatSnapshot
  18. const useChat = ((selector: (value: ChatSnapshot) => unknown) => selector(snapshot)) as UseChat
  19. return { callId, useChat } as PropsRuntime<'conversation.approval.detail'>
  20. }
  21. describe('commandOf', () => {
  22. it('accepts only a string command from valid JSON arguments', () => {
  23. expect(commandOf(undefined)).toBeUndefined()
  24. expect(commandOf({ callId: 'c1', argsRaw: '{' })).toBeUndefined()
  25. expect(commandOf({ callId: 'c1', argsRaw: '{}' })).toBeUndefined()
  26. expect(commandOf({ callId: 'c1', argsRaw: '{"command":42}' })).toBeUndefined()
  27. expect(commandOf({ callId: 'c1', argsRaw: '{"command":"pnpm test"}' })).toBe('pnpm test')
  28. })
  29. })
  30. describe('ApprovalCommand', () => {
  31. it('renders the running correlated Tool command', () => {
  32. render(<ApprovalCommand {...props([
  33. { kind: 'assistant-step', data: {} },
  34. { kind: 'tool-call', data: { root: { callId: 'other', argsRaw: '{"command":"wrong"}' } } },
  35. { kind: 'tool-call', data: { root: { callId: 'call-1', argsRaw: '{"command":"pnpm test"}' } } },
  36. ] as never)} />)
  37. expect(screen.getByText('pnpm test')).toBeTruthy()
  38. })
  39. it('omits absent, uncorrelated, and settled Tool calls', () => {
  40. const { container, rerender } = render(<ApprovalCommand {...props([
  41. { kind: 'assistant-step', data: {} },
  42. { kind: 'tool-call', data: { root: undefined } },
  43. { kind: 'tool-call', data: { root: { callId: 'other', argsRaw: '{}' } } },
  44. {
  45. kind: 'tool-call',
  46. data: { root: { kind: 'tool-result', callId: 'call-1', argsRaw: '{"command":"ignored"}' } },
  47. },
  48. ] as never)} />)
  49. expect(container.textContent).toBe('')
  50. rerender(<ApprovalCommand {...props([
  51. { kind: 'tool-call', data: { root: { callId: 'call-1', argsRaw: '{}' } } },
  52. ] as never)} />)
  53. expect(container.textContent).toBe('')
  54. })
  55. })
  56. describe('ui-chat package entries', () => {
  57. it('keeps the Host half inert and registers the invariant companion', async () => {
  58. expect(() => { nodeApply() }).not.toThrow()
  59. const ctx = new Context()
  60. await ctx.plugin(InvariantRegistry, { enabled: true })
  61. await expect(ctx.plugin(ChatInvariant).await()).resolves.toBeDefined()
  62. })
  63. })