mirror of
https://github.com/stablyai/orca.git
synced 2026-09-25 16:02:38 +00:00
220 lines
7.3 KiB
TypeScript
220 lines
7.3 KiB
TypeScript
import { createElement } from 'react'
|
|
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import type { HostProfile } from '../transport/types'
|
|
import { MobileHomeQuickActions } from './MobileHomeQuickActions'
|
|
|
|
vi.mock('react-native', () => ({
|
|
Pressable: 'Pressable',
|
|
StyleSheet: { create: (styles: unknown) => styles },
|
|
Text: 'Text',
|
|
View: 'View'
|
|
}))
|
|
|
|
vi.mock('lucide-react-native', () => ({
|
|
Plus: 'Plus',
|
|
QrCode: 'QrCode'
|
|
}))
|
|
|
|
vi.mock('./PickerModal', async () => {
|
|
const React = await import('react')
|
|
return {
|
|
PickerModal: (props: unknown) => React.createElement('PickerModal', props)
|
|
}
|
|
})
|
|
|
|
function host(id: string, name: string, endpoint: string): HostProfile {
|
|
return {
|
|
id,
|
|
name,
|
|
endpoint,
|
|
deviceToken: `token-${id}`,
|
|
publicKeyB64: `key-${id}`,
|
|
lastConnected: 1
|
|
}
|
|
}
|
|
|
|
describe('MobileHomeQuickActions', () => {
|
|
let renderer: ReactTestRenderer | null = null
|
|
|
|
afterEach(() => {
|
|
act(() => renderer?.unmount())
|
|
renderer = null
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
async function renderQuickActions(connectedHosts: HostProfile[]) {
|
|
const onPairDesktop = vi.fn()
|
|
const onCreateWorkspace = vi.fn()
|
|
const quickActions = (hosts: HostProfile[]) =>
|
|
createElement(MobileHomeQuickActions, {
|
|
connectedHosts: hosts,
|
|
onPairDesktop,
|
|
onCreateWorkspace
|
|
})
|
|
const consoleError = vi.spyOn(console, 'error').mockImplementation((...args) => {
|
|
if (typeof args[0] !== 'string' || !args[0].includes('react-test-renderer is deprecated')) {
|
|
throw new Error(String(args[0]))
|
|
}
|
|
})
|
|
await act(async () => {
|
|
renderer = create(quickActions(connectedHosts))
|
|
})
|
|
consoleError.mockRestore()
|
|
return {
|
|
onCreateWorkspace,
|
|
rerender: async (hosts: HostProfile[]) => {
|
|
await act(async () => renderer!.update(quickActions(hosts)))
|
|
}
|
|
}
|
|
}
|
|
|
|
function newWorkspaceButton() {
|
|
return renderer!.root.findAllByType('Pressable')[1]
|
|
}
|
|
|
|
function picker() {
|
|
return renderer!.root.findByType('PickerModal')
|
|
}
|
|
|
|
it('disables workspace creation without a connected host', async () => {
|
|
await renderQuickActions([])
|
|
|
|
expect(newWorkspaceButton().props.disabled).toBe(true)
|
|
expect(newWorkspaceButton().props.accessibilityState).toEqual({ disabled: true })
|
|
expect(picker().props.visible).toBe(false)
|
|
})
|
|
|
|
it('opens the only connected host directly', async () => {
|
|
const desk = host('desk', 'Desk', 'ws://192.168.1.2:6768')
|
|
const callbacks = await renderQuickActions([desk])
|
|
|
|
act(() => newWorkspaceButton().props.onPress())
|
|
|
|
expect(callbacks.onCreateWorkspace).toHaveBeenCalledWith('desk')
|
|
expect(picker().props.visible).toBe(false)
|
|
})
|
|
|
|
it('asks which host to use when multiple are connected', async () => {
|
|
const callbacks = await renderQuickActions([
|
|
host('desk', 'Desk', 'ws://192.168.1.2:6768'),
|
|
host('laptop', 'Laptop', 'wss://relay.example.com/mobile')
|
|
])
|
|
|
|
act(() => newWorkspaceButton().props.onPress())
|
|
|
|
expect(callbacks.onCreateWorkspace).not.toHaveBeenCalled()
|
|
expect(picker().props.visible).toBe(true)
|
|
expect(picker().props.title).toBe('Create Workspace On')
|
|
expect(picker().props.options).toEqual([
|
|
{ value: 'desk', label: 'Desk', subtitle: '192.168.1.2:6768' },
|
|
{ value: 'laptop', label: 'Laptop', subtitle: 'relay.example.com' }
|
|
])
|
|
|
|
act(() => picker().props.onSelect('laptop'))
|
|
expect(callbacks.onCreateWorkspace).not.toHaveBeenCalled()
|
|
expect(picker().props.visible).toBe(false)
|
|
|
|
act(() => picker().props.onAfterClose())
|
|
expect(callbacks.onCreateWorkspace).toHaveBeenCalledWith('laptop')
|
|
})
|
|
|
|
it('disambiguates path-routed hosts without exposing endpoint paths', async () => {
|
|
await renderQuickActions([
|
|
host('desk-a', 'Desk', 'wss://gateway.example.com/v1/connect/bearer-secret-a'),
|
|
host('desk-b', 'Desk', 'wss://gateway.example.com/v1/connect/bearer-secret-b')
|
|
])
|
|
|
|
act(() => newWorkspaceButton().props.onPress())
|
|
|
|
expect(picker().props.options).toEqual([
|
|
{
|
|
value: 'desk-a',
|
|
label: 'Desk',
|
|
subtitle: 'gateway.example.com · desk-a'
|
|
},
|
|
{
|
|
value: 'desk-b',
|
|
label: 'Desk',
|
|
subtitle: 'gateway.example.com · desk-b'
|
|
}
|
|
])
|
|
expect(JSON.stringify(picker().props.options)).not.toContain('bearer-secret')
|
|
})
|
|
|
|
it('does not expose malformed legacy endpoint details', async () => {
|
|
await renderQuickActions([
|
|
host('desk-a', 'Desk', 'gateway.example.com/v1/connect/bearer-secret?token=query-secret'),
|
|
host('desk-b', 'Desk', 'localhost:6768/private-secret')
|
|
])
|
|
|
|
act(() => newWorkspaceButton().props.onPress())
|
|
|
|
expect(picker().props.options).toEqual([
|
|
{ value: 'desk-a', label: 'Desk', subtitle: 'Unknown endpoint · desk-a' },
|
|
{ value: 'desk-b', label: 'Desk', subtitle: 'Unknown endpoint · desk-b' }
|
|
])
|
|
expect(JSON.stringify(picker().props.options)).not.toContain('secret')
|
|
})
|
|
|
|
it('closes a stale picker when fewer than two hosts remain connected', async () => {
|
|
const desk = host('desk', 'Desk', 'ws://192.168.1.2:6768')
|
|
const laptop = host('laptop', 'Laptop', 'wss://relay.example.com/mobile')
|
|
const callbacks = await renderQuickActions([desk, laptop])
|
|
|
|
act(() => newWorkspaceButton().props.onPress())
|
|
expect(picker().props.visible).toBe(true)
|
|
|
|
await callbacks.rerender([desk])
|
|
expect(picker().props.visible).toBe(false)
|
|
act(() => picker().props.onAfterClose())
|
|
|
|
await callbacks.rerender([desk, laptop])
|
|
expect(picker().props.visible).toBe(false)
|
|
})
|
|
|
|
it('does not reopen a stale picker if its old host set returns while closing', async () => {
|
|
const desk = host('desk', 'Desk', 'ws://192.168.1.2:6768')
|
|
const laptop = host('laptop', 'Laptop', 'wss://relay.example.com/mobile')
|
|
const callbacks = await renderQuickActions([desk, laptop])
|
|
|
|
act(() => newWorkspaceButton().props.onPress())
|
|
await callbacks.rerender([desk])
|
|
await callbacks.rerender([desk, laptop])
|
|
|
|
expect(picker().props.visible).toBe(false)
|
|
act(() => picker().props.onAfterClose())
|
|
expect(callbacks.onCreateWorkspace).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('keeps a selected host through an unrelated topology change while closing', async () => {
|
|
const desk = host('desk', 'Desk', 'ws://192.168.1.2:6768')
|
|
const laptop = host('laptop', 'Laptop', 'wss://relay.example.com/mobile')
|
|
const callbacks = await renderQuickActions([desk, laptop])
|
|
|
|
act(() => newWorkspaceButton().props.onPress())
|
|
act(() => picker().props.onSelect('laptop'))
|
|
await callbacks.rerender([
|
|
desk,
|
|
laptop,
|
|
host('server', 'Server', 'wss://ssh.example.com/mobile')
|
|
])
|
|
act(() => picker().props.onAfterClose())
|
|
|
|
expect(callbacks.onCreateWorkspace).toHaveBeenCalledWith('laptop')
|
|
})
|
|
|
|
it('drops a selection that disconnects while the picker is closing', async () => {
|
|
const desk = host('desk', 'Desk', 'ws://192.168.1.2:6768')
|
|
const laptop = host('laptop', 'Laptop', 'wss://relay.example.com/mobile')
|
|
const callbacks = await renderQuickActions([desk, laptop])
|
|
|
|
act(() => newWorkspaceButton().props.onPress())
|
|
act(() => picker().props.onSelect('laptop'))
|
|
await callbacks.rerender([desk])
|
|
act(() => picker().props.onAfterClose())
|
|
|
|
expect(callbacks.onCreateWorkspace).not.toHaveBeenCalled()
|
|
})
|
|
})
|