···11-import { assertEquals, assertExists } from '@std/assert'
22-import { Locale } from '$/enums.ts'
33-import { Store } from '../store.ts'
44-55-let testCounter = 0
66-77-Deno.test.beforeEach(() => {
88- // Clear all localStorage to ensure clean state
99- globalThis.localStorage.clear()
1010- testCounter++
1111-})
1212-1313-Deno.test.afterEach(() => {
1414- // Clear localStorage again to remove any residual data
1515- globalThis.localStorage.clear()
1616-})
1717-1818-Deno.test('App - userLevel auto-initializes to 1 from clean slate', async () => {
1919- // This test simulates a fresh user with no localStorage data
2020- // The userLevel should automatically be set to 1 (not stay at 0)
2121-2222- const store = new Store(`hanzi-test-app-${testCounter}`)
2323-2424- try {
2525- // Get initial data - should be default state
2626- const initialData = await store.getAllData()
2727-2828- // On a clean slate, userLevels should default to 0
2929- assertEquals(
3030- initialData.userLevels[Locale.zh_CN],
3131- 0,
3232- 'Fresh store should start with userLevel 0',
3333- )
3434-3535- // Now simulate what the App does on initialization
3636- // If userLevel is 0, it should be set to 1
3737- if (initialData.userLevels[Locale.zh_CN] === 0) {
3838- await store.setUserLevel(Locale.zh_CN, 1)
3939- }
4040-4141- // Verify userLevel was set to 1
4242- const updatedData = await store.getAllData()
4343- assertEquals(
4444- updatedData.userLevels[Locale.zh_CN],
4545- 1,
4646- 'After initialization, userLevel should be 1',
4747- )
4848-4949- // Verify it persists in localStorage
5050- const persistedData = await store.getAllData()
5151- assertEquals(
5252- persistedData.userLevels[Locale.zh_CN],
5353- 1,
5454- 'UserLevel should persist across reads',
5555- )
5656- } finally {
5757- await store.clearAllData()
5858- store.dispose()
5959- }
6060-})
6161-6262-Deno.test('App - userLevel stays unchanged if already set', async () => {
6363- // This test ensures we don't overwrite an existing userLevel
6464-6565- const store = new Store(`hanzi-test-app-${testCounter}`)
6666-6767- try {
6868- // Set userLevel to 5 (existing user)
6969- await store.setUserLevel(Locale.zh_CN, 5)
7070-7171- const data = await store.getAllData()
7272- assertEquals(
7373- data.userLevels[Locale.zh_CN],
7474- 5,
7575- 'UserLevel should be set to 5',
7676- )
7777-7878- // Simulate App initialization logic - should NOT change userLevel
7979- // because it's already non-zero
8080- if (data.userLevels[Locale.zh_CN] === 0) {
8181- await store.setUserLevel(Locale.zh_CN, 1)
8282- }
8383-8484- const finalData = await store.getAllData()
8585- assertEquals(
8686- finalData.userLevels[Locale.zh_CN],
8787- 5,
8888- 'UserLevel should remain 5 after initialization',
8989- )
9090- } finally {
9191- await store.clearAllData()
9292- store.dispose()
9393- }
9494-})
9595-9696-Deno.test('App - data persists to localStorage', async () => {
9797- // This test verifies that Store actually writes to localStorage
9898-9999- const storeName = `hanzi-test-app-${testCounter}`
100100- const store = new Store(storeName)
101101-102102- try {
103103- // Set some data
104104- await store.setUserLevel(Locale.zh_CN, 3)
105105- await store.updateSettings({ playAudio: false })
106106-107107- // Give a moment for async operations to complete
108108- await new Promise((resolve) => setTimeout(resolve, 10))
109109-110110- // Verify via store API (more reliable than checking localStorage directly)
111111- const data = await store.getAllData()
112112- assertEquals(
113113- data.userLevels[Locale.zh_CN],
114114- 3,
115115- 'UserLevel should be persisted',
116116- )
117117- assertEquals(
118118- data.settings.playAudio,
119119- false,
120120- 'Settings should be persisted',
121121- )
122122-123123- // Also verify localStorage has something
124124- const storedData = localStorage.getItem(storeName)
125125- assertExists(storedData, 'Data should be written to localStorage')
126126- } finally {
127127- await store.clearAllData()
128128- store.dispose()
129129- }
130130-})
131131-132132-Deno.test('App - initialization completes without errors', async () => {
133133- // This test ensures the Store can initialize from clean state
134134-135135- const storeName = `hanzi-test-app-${testCounter}`
136136- // Explicitly clear before creating store
137137- localStorage.removeItem(storeName)
138138-139139- const store = new Store(storeName)
140140-141141- try {
142142- // Should be able to get data without errors
143143- const data = await store.getAllData()
144144-145145- assertExists(data, 'Should get data object')
146146- assertExists(data.settings, 'Should have settings')
147147- assertExists(data.assignments, 'Should have assignments')
148148- assertExists(data.userLevels, 'Should have userLevels')
149149- assertExists(data.flags, 'Should have flags')
150150- assertExists(data.overrides, 'Should have overrides')
151151-152152- // Check defaults are correct
153153- assertEquals(data.settings.locale, Locale.zh_CN, 'Default locale')
154154- assertEquals(
155155- typeof data.settings.playAudio,
156156- 'boolean',
157157- 'playAudio should be boolean',
158158- )
159159- assertEquals(data.flags.isFirstLoad, true, 'Default isFirstLoad')
160160- } finally {
161161- await store.clearAllData()
162162- store.dispose()
163163- }
164164-})
···11-import { Locale } from '$/enums.ts'
22-import {
33- type Assignment as AssignmentV0,
44- type StoreState as StoreStateV0,
55-} from '../schema/v0.ts'
66-import {
77- Assignment,
88- defaultAssignments,
99- defaultSettings,
1010- defaultUserLevels,
1111- StoreState,
1212-} from '../schema/v1.ts'
1313-1414-export function migrateFromV0(input: StoreStateV0): StoreState {
1515- const output: StoreState = {
1616- ...input,
1717- version: '1.0.0',
1818- assignments: defaultAssignments(),
1919- settings: input.settings || defaultSettings,
2020- userLevels: input.userLevels || defaultUserLevels(),
2121- overrides: input.overrides || {},
2222- }
2323-2424- // Safely handle assignments that may be null or undefined
2525- if (input.assignments && typeof input.assignments === 'object') {
2626- Object.entries(input.assignments).forEach(
2727- ([locale, assignments]) => {
2828- if (assignments && typeof assignments === 'object') {
2929- output.assignments[locale as Locale] = {}
3030- Object.values(assignments).forEach((a) => {
3131- if (a && a.subjectId) {
3232- output.assignments[locale as Locale][a.subjectId] =
3333- migrateAssignment(a)
3434- }
3535- })
3636- }
3737- },
3838- )
3939- }
4040-4141- return StoreState.parse(output)
4242-}
4343-4444-function migrateAssignment(v0: AssignmentV0): Assignment {
4545- let efactor = 0
4646- if (v0.startedAt) efactor = 1
4747- if (v0.passedAt) efactor = 5
4848- if (v0.completedAt) efactor = 9
4949-5050- return Assignment.parse({ ...v0, efactor })
5151-}
-7
www/models/migrations/v2.ts
···11-import { type StoreState as StoreStateV1 } from '../schema/v0.ts'
22-import { StoreState } from '../schema/v1.ts'
33-44-// v2: No data structure changes from v1, just renaming StoreState -> StoreState
55-export function migrateFromV1(input: StoreStateV1): StoreState {
66- return StoreState.parse({ ...input, version: '2.0.0' })
77-}