Move sections to where they read better: - Phase 1 (Team registration): drop the "Your Agent" block. - Phase 2 (Meet your agent): host the "Your Agent" section (say-hi / Telegram / voice) AND the agent chat under it; remove "Pick your domain". - Module 1 (Domain & events): "Pick your domain" moves here (no more read-only carry) alongside the ADD Layer 1 card. - Module 2 (Skills & policies): the chat is gone; the L2/L3 ADD cards show directly (still auto-prefilled if the three prompts ran in Meet your agent). Also: OpenYourNode opens the browser-reachable board URL. In self-host/USB mode the stored url is container-facing (host.docker.internal) which the browser can't resolve — derive the current host on :8080 instead. Tests updated. Co-Authored-By: Claude Opus 4.8 <[email protected]>
68 lines
2.5 KiB
TypeScript
68 lines
2.5 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
import { render, screen, act } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { MemoryRouter } from 'react-router-dom'
|
|
import { BuildFlash } from './BuildFlash'
|
|
import { useSession } from '@/store/session'
|
|
import type { WsEvent } from '@/types'
|
|
|
|
// Capture the live-activity callback so tests can push node events.
|
|
const sendPrompt = vi.fn()
|
|
let liveOnEvent: ((e: WsEvent) => void) | null = null
|
|
const closeSpy = vi.fn()
|
|
|
|
vi.mock('@/lib/api', () => ({
|
|
AGENT: 'default',
|
|
sendPrompt: (...args: unknown[]) => sendPrompt(...args),
|
|
openTeamActivity: (_teamId: string, onEvent: (e: WsEvent) => void) => {
|
|
liveOnEvent = onEvent
|
|
return closeSpy
|
|
},
|
|
// OpenYourNode (rendered here) reads the runtime mode.
|
|
getMode: () => Promise.resolve({ localMode: false }),
|
|
}))
|
|
|
|
describe('BuildFlash', () => {
|
|
beforeEach(() => {
|
|
useSession.getState().reset()
|
|
sessionStorage.clear()
|
|
sendPrompt.mockReset()
|
|
sendPrompt.mockResolvedValue(undefined)
|
|
closeSpy.mockReset()
|
|
liveOnEvent = null
|
|
})
|
|
|
|
it('disables Send until there is a prompt', () => {
|
|
render(<MemoryRouter><BuildFlash /></MemoryRouter>)
|
|
expect(screen.getByRole('button', { name: /send/i })).toBeDisabled()
|
|
})
|
|
|
|
it('shows the Live board badge', () => {
|
|
render(<MemoryRouter><BuildFlash /></MemoryRouter>)
|
|
expect(screen.getByText(/live board/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('sends the prompt to the single agent and renders streamed board activity', async () => {
|
|
const user = userEvent.setup()
|
|
render(<MemoryRouter><BuildFlash /></MemoryRouter>)
|
|
|
|
await user.type(screen.getByLabelText(/prompt your board/i), 'scroll HELLO')
|
|
await user.click(screen.getByRole('button', { name: /working|send/i }))
|
|
expect(sendPrompt).toHaveBeenCalledWith(useSession.getState().teamId, 'scroll HELLO', 'default')
|
|
|
|
// a flash event streams in over the (mocked) SSE feed
|
|
act(() => {
|
|
liveOnEvent?.({ type: 'node:activity', teamId: 't', kind: 'flash', label: 'Flashed to 0x80F0000', ts: 'T' })
|
|
})
|
|
expect(screen.getByTestId('activity-log')).toHaveTextContent(/flashed to 0x80F0000/i)
|
|
// terminal event re-enables Send
|
|
expect(screen.getByRole('button', { name: /send/i })).toBeEnabled()
|
|
})
|
|
|
|
it('closes the activity stream on unmount', () => {
|
|
const { unmount } = render(<MemoryRouter><BuildFlash /></MemoryRouter>)
|
|
unmount()
|
|
expect(closeSpy).toHaveBeenCalled()
|
|
})
|
|
})
|