Files
orca/mobile/src/components/MobileHomeQuickActions.test.ts
T

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()
})
})