import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' import type { ReactNode, TextareaHTMLAttributes } from 'react' import { useRef, useState } from 'react' import { afterEach, describe, expect, it, vi } from 'vitest' import { I18nProvider } from '@/lib/i18n-context' import type { PendingSchedule } from '@/components/AssistantChat/ScheduleTimePicker' import type { ComposerSendIntent } from '@/lib/messageDelivery' import { HappyComposer, type ComposerSendError } from './HappyComposer' /** * HappyComposer owns the recovery guard, while assistant-ui owns the live * composer store. This focused harness supplies the small subset of that * store necessary to exercise send → user interaction → delayed error races. */ type FakeAttachment = { id: string; status: { type: 'complete' } } type MockComposerInputProps = TextareaHTMLAttributes & { maxRows?: number submitOnEnter?: boolean cancelOnEscape?: boolean } type FakeRuntimeState = { composer: { text: string; attachments: FakeAttachment[] } thread: { isRunning: boolean; isDisabled: boolean } } const runtime = vi.hoisted(() => ({ snapshot: { composer: { text: '', attachments: [] as FakeAttachment[] }, thread: { isRunning: false, isDisabled: false }, } as FakeRuntimeState, setSnapshot: null as null | ((updater: (current: FakeRuntimeState) => FakeRuntimeState) => void), pendingSendIntentRef: null as null | { current: ComposerSendIntent }, sentIntents: [] as ComposerSendIntent[], })) vi.mock('@assistant-ui/react', async () => { const React = await import('react') return { useAui: () => ({ composer: () => ({ setText: (text: string) => { runtime.setSnapshot!((current) => ({ ...current, composer: { ...current.composer, text }, })) }, send: () => { const intent = runtime.pendingSendIntentRef?.current ?? 'default' runtime.sentIntents.push(intent) if (runtime.pendingSendIntentRef) runtime.pendingSendIntentRef.current = 'default' runtime.setSnapshot!((current) => ({ ...current, composer: { text: '', attachments: [] }, })) }, addAttachment: async () => {}, }), thread: () => ({ cancelRun: () => {} }), }), useAuiState: (selector: (state: typeof runtime.snapshot) => unknown) => selector(runtime.snapshot), ComposerPrimitive: { Root: ({ children, onSubmit }: { children: ReactNode; onSubmit?: () => void }) => (
{children}
), Input: React.forwardRef( ({ onChange, maxRows: _maxRows, submitOnEnter: _submitOnEnter, cancelOnEscape: _cancelOnEscape, ...props }, ref) => (