Add update_channel setting (stable/canary) to Settings with UI toggle. Stable channel uses Tauri updater plugin; canary fetches latest-canary.json and opens GitHub release page for manual download. Add useFeatureFlag() hook with localStorage overrides and compile-time defaults (no remote dependencies). Add release-canary.yml CI workflow for canary branch builds. Update stable workflow to preserve latest-canary.json on GH Pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
71 lines
2.3 KiB
TypeScript
71 lines
2.3 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest'
|
|
import { renderHook } from '@testing-library/react'
|
|
import { useFeatureFlag } from './useFeatureFlag'
|
|
|
|
describe('useFeatureFlag', () => {
|
|
it('returns false for example_flag by default', () => {
|
|
vi.spyOn(globalThis, 'localStorage', 'get').mockReturnValue({
|
|
getItem: () => null,
|
|
setItem: () => {},
|
|
removeItem: () => {},
|
|
clear: () => {},
|
|
length: 0,
|
|
key: () => null,
|
|
})
|
|
const { result } = renderHook(() => useFeatureFlag('example_flag'))
|
|
expect(result.current).toBe(false)
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('returns true when localStorage override is set to "true"', () => {
|
|
vi.spyOn(globalThis, 'localStorage', 'get').mockReturnValue({
|
|
getItem: (key: string) => key === 'ff_example_flag' ? 'true' : null,
|
|
setItem: () => {},
|
|
removeItem: () => {},
|
|
clear: () => {},
|
|
length: 0,
|
|
key: () => null,
|
|
})
|
|
const { result } = renderHook(() => useFeatureFlag('example_flag'))
|
|
expect(result.current).toBe(true)
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('returns false when localStorage override is set to "false"', () => {
|
|
vi.spyOn(globalThis, 'localStorage', 'get').mockReturnValue({
|
|
getItem: (key: string) => key === 'ff_example_flag' ? 'false' : null,
|
|
setItem: () => {},
|
|
removeItem: () => {},
|
|
clear: () => {},
|
|
length: 0,
|
|
key: () => null,
|
|
})
|
|
const { result } = renderHook(() => useFeatureFlag('example_flag'))
|
|
expect(result.current).toBe(false)
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('ignores non-boolean localStorage values (treats as false)', () => {
|
|
vi.spyOn(globalThis, 'localStorage', 'get').mockReturnValue({
|
|
getItem: (key: string) => key === 'ff_example_flag' ? 'maybe' : null,
|
|
setItem: () => {},
|
|
removeItem: () => {},
|
|
clear: () => {},
|
|
length: 0,
|
|
key: () => null,
|
|
})
|
|
const { result } = renderHook(() => useFeatureFlag('example_flag'))
|
|
expect(result.current).toBe(false)
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('falls back to default when localStorage throws', () => {
|
|
vi.spyOn(globalThis, 'localStorage', 'get').mockImplementation(() => {
|
|
throw new Error('localStorage disabled')
|
|
})
|
|
const { result } = renderHook(() => useFeatureFlag('example_flag'))
|
|
expect(result.current).toBe(false)
|
|
vi.restoreAllMocks()
|
|
})
|
|
})
|