d3ro-voice/apps/mobile-rn/__tests__/preferences.test.ts
2026-08-29 18:33:45 +09:00

114 lines
3.5 KiB
TypeScript

import {
applyPreferencesPatch,
clearAllUserPreferenceCaches,
normalizePreferences,
normalizePreferencesPatch,
} from '../src/lib/preferences-context'
import AsyncStorage from '@react-native-async-storage/async-storage'
import {
getMobileThemePalette,
resolveEffectiveTheme,
} from '../src/theme/mobile-theme'
describe('mobile preferences model', () => {
test('rejects malformed cached values and restores safe defaults', () => {
const normalized = normalizePreferences({
themeMode: 'neon',
locale: 'unknown',
hapticEnabled: 'yes',
autoPolishEnabled: false,
onboardingVersion: -4,
tutorialCompletedAt: 'not-a-date',
revision: 0,
})
expect(normalized).toMatchObject({
themeMode: 'system',
locale: 'ko',
hapticEnabled: true,
autoPolishEnabled: false,
onboardingVersion: 0,
tutorialCompletedAt: null,
revision: 1,
})
})
test('accepts only supported fields in a pending patch', () => {
expect(normalizePreferencesPatch({
themeMode: 'light',
locale: 'en',
hapticEnabled: false,
preferredSttModel: ' cloud-default ',
revision: 999,
unknownSetting: true,
})).toEqual({
themeMode: 'light',
locale: 'en',
hapticEnabled: false,
preferredSttModel: 'cloud-default',
})
})
test('applies an offline patch without discarding server metadata', () => {
const original = normalizePreferences({
themeMode: 'dark',
locale: 'ko',
hapticEnabled: true,
autoPolishEnabled: true,
preferredSttModel: 'cloud-default',
preferredLlmModel: 'claude',
onboardingVersion: 1,
tutorialCompletedAt: '2026-08-21T01:00:00.000Z',
revision: 8,
updatedAt: '2026-08-21T01:05:00.000Z',
})
const updated = applyPreferencesPatch(original, {
themeMode: 'light',
hapticEnabled: false,
})
expect(updated.themeMode).toBe('light')
expect(updated.hapticEnabled).toBe(false)
expect(updated.preferredSttModel).toBe('cloud-default')
expect(updated.preferredLlmModel).toBe('claude')
expect(updated.revision).toBe(8)
expect(updated.updatedAt).toBe('2026-08-21T01:05:00.000Z')
})
test('logout purge removes account caches but preserves installation settings', async () => {
const getAllKeys = AsyncStorage.getAllKeys as jest.MockedFunction<
typeof AsyncStorage.getAllKeys
>
const multiRemove = AsyncStorage.multiRemove as jest.MockedFunction<
typeof AsyncStorage.multiRemove
>
getAllKeys.mockResolvedValueOnce([
'@d3ro/mobile/preferences/installation-v1',
'@d3ro/mobile/preferences/user-v1/account-a',
'@d3ro/mobile/preferences/user-v1/account-b',
'@another/app/key',
])
await clearAllUserPreferenceCaches()
expect(multiRemove).toHaveBeenCalledWith([
'@d3ro/mobile/preferences/user-v1/account-a',
'@d3ro/mobile/preferences/user-v1/account-b',
])
})
})
describe('mobile theme resolution', () => {
test('system mode follows the device and falls back to dark', () => {
expect(resolveEffectiveTheme('system', 'light')).toBe('light')
expect(resolveEffectiveTheme('system', 'dark')).toBe('dark')
expect(resolveEffectiveTheme('system', null)).toBe('dark')
})
test('an explicit theme does not change with the device setting', () => {
expect(resolveEffectiveTheme('light', 'dark')).toBe('light')
expect(resolveEffectiveTheme('dark', 'light')).toBe('dark')
expect(getMobileThemePalette('light').bg.app)
.not.toBe(getMobileThemePalette('dark').bg.app)
})
})