515 tests across 38 files. Branch coverage 88%, function coverage 83% on testable logic (stores, composables, api, utils, services, router). New test files: websocket, useLoginSounds, useMobileBackButton, useControllerNav, routes. Extended: rpc-client (99.5%), container store (100%). Fixed: useNavSounds AudioContext mock, type errors across tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
|
|
vi.mock('@/api/rpc-client', () => ({
|
|
rpcClient: {
|
|
isOnboardingComplete: vi.fn(),
|
|
completeOnboarding: vi.fn(),
|
|
},
|
|
}))
|
|
|
|
import { isOnboardingComplete, completeOnboarding } from '../useOnboarding'
|
|
import { rpcClient } from '@/api/rpc-client'
|
|
|
|
const mockedRpc = vi.mocked(rpcClient)
|
|
|
|
describe('useOnboarding', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
localStorage.clear()
|
|
vi.useFakeTimers()
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
describe('isOnboardingComplete', () => {
|
|
it('returns true when RPC says complete', async () => {
|
|
mockedRpc.isOnboardingComplete.mockResolvedValue(true)
|
|
const result = await isOnboardingComplete()
|
|
expect(result).toBe(true)
|
|
})
|
|
|
|
it('returns false when RPC says not complete', async () => {
|
|
mockedRpc.isOnboardingComplete.mockResolvedValue(false)
|
|
const result = await isOnboardingComplete()
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('falls back to localStorage when RPC fails with non-retryable error', async () => {
|
|
mockedRpc.isOnboardingComplete.mockRejectedValue(new Error('Unknown error'))
|
|
localStorage.setItem('neode_onboarding_complete', '1')
|
|
const result = await isOnboardingComplete()
|
|
expect(result).toBe(true)
|
|
})
|
|
|
|
it('returns false from localStorage fallback when not set', async () => {
|
|
mockedRpc.isOnboardingComplete.mockRejectedValue(new Error('Unknown error'))
|
|
const result = await isOnboardingComplete()
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('retries on 502 errors before falling back', async () => {
|
|
mockedRpc.isOnboardingComplete
|
|
.mockRejectedValueOnce(new Error('502 Bad Gateway'))
|
|
.mockResolvedValueOnce(true)
|
|
|
|
const promise = isOnboardingComplete()
|
|
await vi.advanceTimersByTimeAsync(900)
|
|
const result = await promise
|
|
expect(result).toBe(true)
|
|
expect(mockedRpc.isOnboardingComplete).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('retries on 503 errors', async () => {
|
|
mockedRpc.isOnboardingComplete
|
|
.mockRejectedValueOnce(new Error('503 Service Unavailable'))
|
|
.mockResolvedValueOnce(false)
|
|
|
|
const promise = isOnboardingComplete()
|
|
await vi.advanceTimersByTimeAsync(900)
|
|
const result = await promise
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('falls back to localStorage after exhausting retries', async () => {
|
|
mockedRpc.isOnboardingComplete.mockRejectedValue(new Error('502 Bad Gateway'))
|
|
localStorage.setItem('neode_onboarding_complete', '1')
|
|
|
|
const promise = isOnboardingComplete()
|
|
await vi.advanceTimersByTimeAsync(2000)
|
|
const result = await promise
|
|
expect(result).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('completeOnboarding', () => {
|
|
it('calls RPC and sets localStorage', async () => {
|
|
mockedRpc.completeOnboarding.mockResolvedValue(true)
|
|
await completeOnboarding()
|
|
expect(mockedRpc.completeOnboarding).toHaveBeenCalled()
|
|
expect(localStorage.getItem('neode_onboarding_complete')).toBe('1')
|
|
})
|
|
|
|
it('sets localStorage even when RPC fails', async () => {
|
|
mockedRpc.completeOnboarding.mockRejectedValue(new Error('Network error'))
|
|
const promise = completeOnboarding()
|
|
await vi.advanceTimersByTimeAsync(10000)
|
|
await promise
|
|
expect(localStorage.getItem('neode_onboarding_complete')).toBe('1')
|
|
})
|
|
})
|
|
})
|