114 lines
3.5 KiB
TypeScript
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)
|
|
})
|
|
})
|