mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 08:03:39 +00:00
* feat(mobile): edit saved host endpoints * fix(mobile): reject ambiguous numeric host addresses * fix(mobile): label edit host inputs * fix(mobile): make host edit save atomic and remove superseded mutators Two independent review rounds found the same class of foot-gun: a superseded mutator (updateHostEndpoint, then renameHost) left in host-store.ts after the atomic updateHostNameAndEndpoint refactor, with zero remaining callers. Either could be reintroduced by a future caller and silently regress the non-atomic name/endpoint race the atomic function was written to close, so both are removed. Also covers reconnect-rejection and endpoint-only save paths that were missing test coverage, and merges origin/main (#8789) so this lands without reverting the mobile terminal restore fix. Co-authored-by: Orca <help@stably.ai> * Simplify save-race comment and reword host-removed error message - Trims the redundant comment explaining the savingRef race guard down to one line. - Changes the "no longer saved" load-error copy to "was removed" for clearer phrasing, updating the matching test expectation. --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai>
107 lines
3.0 KiB
TypeScript
107 lines
3.0 KiB
TypeScript
import { createElement } from 'react'
|
|
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import EditHostScreen from '../app/h/[hostId]/edit'
|
|
|
|
const dependencies = vi.hoisted(() => ({
|
|
back: vi.fn(),
|
|
forceReconnectHost: vi.fn(),
|
|
loadHosts: vi.fn(),
|
|
primeHosts: vi.fn(),
|
|
updateHostNameAndEndpoint: vi.fn()
|
|
}))
|
|
|
|
vi.mock('react-native', () => ({
|
|
ActivityIndicator: 'ActivityIndicator',
|
|
KeyboardAvoidingView: 'KeyboardAvoidingView',
|
|
Platform: { OS: 'ios' },
|
|
Pressable: 'Pressable',
|
|
ScrollView: 'ScrollView',
|
|
StyleSheet: { create: (styles: unknown) => styles },
|
|
Text: 'Text',
|
|
TextInput: 'TextInput',
|
|
View: 'View'
|
|
}))
|
|
|
|
vi.mock('react-native-safe-area-context', () => ({
|
|
useSafeAreaInsets: () => ({ bottom: 0, left: 0, right: 0, top: 0 })
|
|
}))
|
|
|
|
vi.mock('expo-router', () => ({
|
|
useLocalSearchParams: () => ({ hostId: 'host-1' }),
|
|
useRouter: () => ({ back: dependencies.back })
|
|
}))
|
|
|
|
vi.mock('lucide-react-native', () => ({
|
|
ChevronLeft: 'ChevronLeft'
|
|
}))
|
|
|
|
vi.mock('./transport/host-store', () => ({
|
|
loadHosts: dependencies.loadHosts,
|
|
updateHostNameAndEndpoint: dependencies.updateHostNameAndEndpoint
|
|
}))
|
|
|
|
vi.mock('./transport/client-context', () => ({
|
|
useForceReconnect: () => dependencies.forceReconnectHost,
|
|
usePrimeHosts: () => dependencies.primeHosts
|
|
}))
|
|
|
|
function suppressReactTestRendererDeprecationWarning(): () => void {
|
|
const originalConsoleError = console.error
|
|
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => {
|
|
const firstArg = args[0]
|
|
if (typeof firstArg === 'string' && firstArg.includes('react-test-renderer is deprecated')) {
|
|
return
|
|
}
|
|
originalConsoleError(...args)
|
|
})
|
|
return () => consoleErrorSpy.mockRestore()
|
|
}
|
|
|
|
async function renderEditHostRoute(): Promise<ReactTestRenderer> {
|
|
let renderer: ReactTestRenderer | null = null
|
|
const restoreConsoleError = suppressReactTestRendererDeprecationWarning()
|
|
try {
|
|
await act(async () => {
|
|
renderer = create(createElement(EditHostScreen))
|
|
await Promise.resolve()
|
|
})
|
|
} finally {
|
|
restoreConsoleError()
|
|
}
|
|
if (!renderer) {
|
|
throw new Error('Edit host route did not render')
|
|
}
|
|
return renderer
|
|
}
|
|
|
|
describe('edit host route accessibility', () => {
|
|
beforeEach(() => {
|
|
globalThis.IS_REACT_ACT_ENVIRONMENT = true
|
|
dependencies.loadHosts.mockReset().mockResolvedValue([
|
|
{
|
|
id: 'host-1',
|
|
name: 'Desk',
|
|
endpoint: 'ws://192.168.1.10:6768',
|
|
deviceToken: 'token',
|
|
publicKeyB64: 'public-key',
|
|
lastConnected: 1
|
|
}
|
|
])
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('exposes stable accessible names for both editable fields', async () => {
|
|
const renderer = await renderEditHostRoute()
|
|
|
|
const inputs = renderer.root.findAllByType('TextInput')
|
|
expect(inputs).toHaveLength(2)
|
|
expect(inputs.map((input) => input.props.accessibilityLabel)).toEqual(['Name', 'Address'])
|
|
|
|
act(() => renderer.unmount())
|
|
})
|
|
})
|