mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 17:47:40 +00:00
111 lines
4.3 KiB
TypeScript
111 lines
4.3 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
installChatViewHooks,
|
|
assistantTurn,
|
|
banners,
|
|
bannerText,
|
|
hostType,
|
|
listIds,
|
|
pressSend,
|
|
render,
|
|
root,
|
|
update
|
|
} from './MobileNativeChatView.harness.test-fixture'
|
|
|
|
vi.mock('react-native', async () => {
|
|
const { reactNativeStub } = await import('./MobileNativeChatView.render-stubs.test-fixture')
|
|
return reactNativeStub()
|
|
})
|
|
vi.mock('react-native-safe-area-context', () => ({
|
|
useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 })
|
|
}))
|
|
vi.mock('react-native-gesture-handler', async () => {
|
|
const { gestureHandlerStub } = await import('./MobileNativeChatView.render-stubs.test-fixture')
|
|
return gestureHandlerStub()
|
|
})
|
|
vi.mock('lucide-react-native', () => ({
|
|
ArrowDown: 'ArrowDown',
|
|
ChevronsDownUp: 'ChevronsDownUp',
|
|
ChevronsUpDown: 'ChevronsUpDown',
|
|
Square: 'Square'
|
|
}))
|
|
vi.mock('./MobileNativeChatMessage', () => ({ MobileNativeChatMessage: 'ChatMessage' }))
|
|
vi.mock('./MobileNativeChatLiveLine', () => ({ MobileNativeChatLiveLine: 'LiveStatus' }))
|
|
vi.mock('./MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' }))
|
|
vi.mock('./MobileNativeChatPermission', () => ({ MobileNativeChatPermission: 'ChatPermission' }))
|
|
vi.mock('./MobileNativeChatQuestion', () => ({ MobileNativeChatQuestion: 'ChatQuestion' }))
|
|
vi.mock('../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionSheetModal' }))
|
|
vi.mock('./MobileNativeChatComposer', async () => {
|
|
const { composerStub } = await import('./MobileNativeChatView.render-stubs.test-fixture')
|
|
return composerStub()
|
|
})
|
|
|
|
describe('MobileNativeChatView: send and read failure banners', () => {
|
|
installChatViewHooks()
|
|
|
|
// A read no retry gets past takes the whole pane, even over a transcript already on screen: its
|
|
// words alone, and nothing that could only be refused again.
|
|
it('leaves only the words of a read that failed for good, over a loaded transcript', async () => {
|
|
const loaded = [assistantTurn('m1', 'earlier reply')]
|
|
const words = 'This chat was saved by a newer Orca. Update Orca to open it.'
|
|
const shown = (text: string) =>
|
|
root().findAll((node) => hostType(node) === 'Text' && node.props.children === text)
|
|
const composers = () => root().findAll((node) => hostType(node) === 'Composer')
|
|
const lists = () => root().findAll((node) => hostType(node) === 'FlatList')
|
|
const failure = {
|
|
messages: loaded,
|
|
folded: loaded,
|
|
status: 'error' as const,
|
|
error: words,
|
|
canStop: true,
|
|
permission: { title: 'Approve?', options: [{ label: 'Allow', send: '1' }] },
|
|
// A resend answered unknown says nothing beside the read's words.
|
|
sendErrorMessage: 'Message unconfirmed — check chat before retrying'
|
|
}
|
|
await render({ ...failure, readFailedFinally: true })
|
|
expect(shown(words)).toHaveLength(1)
|
|
expect(composers()).toHaveLength(0)
|
|
expect(lists()).toHaveLength(0)
|
|
expect(banners()).toHaveLength(0)
|
|
expect(root().findAllByProps({ accessibilityLabel: 'Stop the agent' })).toHaveLength(0)
|
|
|
|
// A failure that can clear keeps the transcript and the composer.
|
|
await update({ ...failure, error: "Orca couldn't open this chat's history right now." })
|
|
expect(listIds()).toEqual(['m1'])
|
|
expect(composers()).toHaveLength(1)
|
|
expect(banners()).toHaveLength(1)
|
|
})
|
|
|
|
it('renders the route-reported failure verbatim', async () => {
|
|
await render({ sendErrorMessage: 'Permission reply failed' })
|
|
|
|
expect(banners()).toHaveLength(1)
|
|
expect(bannerText()).toContain('Permission reply failed')
|
|
})
|
|
|
|
it('does not duplicate the route banner when the composer rejects', async () => {
|
|
const onClearSendError = vi.fn()
|
|
await render({
|
|
onSend: vi.fn().mockResolvedValue(false),
|
|
inputLockReason: 'disconnected',
|
|
sendErrorMessage: 'Stop failed',
|
|
onClearSendError
|
|
})
|
|
await pressSend()
|
|
|
|
expect(onClearSendError).not.toHaveBeenCalled()
|
|
expect(banners()).toHaveLength(1)
|
|
expect(bannerText()).toContain('Stop failed')
|
|
expect(bannerText()).toBe('Stop failed')
|
|
})
|
|
|
|
it('retires the route-owned banner once a send is accepted', async () => {
|
|
const onClearSendError = vi.fn()
|
|
await render({ sendErrorMessage: 'Stop failed', onClearSendError })
|
|
|
|
await pressSend()
|
|
|
|
expect(onClearSendError).toHaveBeenCalledOnce()
|
|
})
|
|
})
|