import test from 'node:test' import assert from 'node:assert/strict' async function fixture() { const { createDictionaryLoader } = await import('../src/dictionaries.mjs') const pending = [] const later = () => new Promise((resolve, reject) => pending.push({ resolve, reject })) const session = { state: { generation: 1 }, listDictionaries: later, importDictionary: later, setDictionaryEnabled: later } const state = { items: [{ id: 1, name: 'Existing', enabled: true }], supported: null, loading: false, saving: false, error: '', notice: '' } return { loader: createDictionaryLoader(session, state), state, session, pending } } test('failed upload preserves the existing resource and exposes the error', async () => { const { loader, state, pending } = await fixture() const action = loader.import(new FormData()) assert.equal(state.saving, true) pending[0].reject(new Error('文件摘要不匹配')) assert.equal(await action, false) assert.equal(state.items[0].name, 'Existing') assert.equal(state.error, '文件摘要不匹配') assert.equal(state.saving, false) }) test('accepted mutation replaces old state directly, without a second refresh dependency', async () => { const { loader, state, pending } = await fixture() const action = loader.toggle(1, false) pending[0].resolve({ resource: { id: 1, name: 'Existing', enabled: false, status: 'disabled' } }) assert.equal(await action, true) assert.equal(state.items[0].enabled, false) assert.equal(pending.length, 1) }) test('an old list cannot overwrite a newer import and duplicate import is explicit', async () => { const { loader, state, pending } = await fixture() const read = loader.load() const action = loader.import(new FormData()) pending[1].resolve({ resource: { id: 1, name: 'Imported', enabled: true }, duplicate: true }) await action pending[0].resolve({ items: [{ id: 1, name: 'Old' }] }) await read assert.equal(state.items[0].name, 'Imported') assert.match(state.notice, /已存在/) }) test('page exit or session change discards late responses', async () => { const { loader, state, pending, session } = await fixture() const action = loader.import(new FormData()) loader.invalidate() pending[0].resolve({ resource: { id: 1, name: 'Late' } }) assert.equal(await action, false) assert.deepEqual(state.items, []) const read = loader.load() session.state.generation++ pending[1].resolve({ items: [{ id: 1, name: 'Other session' }] }) await read assert.deepEqual(state.items, []) }) test('both dictionaries stay independently switchable and keep their provider', async () => { const { createDictionaryLoader } = await import('../src/dictionaries.mjs') const state = { items: [], supported: null, loading: false, saving: false, error: '', notice: '' } const supported = [ { provider: 'wordnet', name: 'Princeton WordNet', version: '3.0', format: 'wordnet-3.0-zip', source: 'https://example.invalid/wordnet.zip', downloadable: true }, { provider: 'ecdict', name: '简明英汉字典增强版', version: 'ecdict-1.0.28+cmudict-0.7b', format: 'lexgo-zh-dict-v1', source: 'https://example.invalid/ecdict-stardict-28.zip', downloadable: false } ] const rows = [ { id: 1, provider: 'wordnet', name: 'Princeton WordNet', enabled: true, status: 'ready' }, { id: 2, provider: 'ecdict', name: '简明英汉字典增强版', enabled: true, status: 'ready' } ] const session = { state: { generation: 1 }, listDictionaries: async () => ({ items: rows, supported }), importDictionary: async () => ({ resource: rows[1], duplicate: false }), setDictionaryEnabled: async (id, enabled) => ({ resource: { ...rows.find(row => row.id === id), enabled } }) } const loader = createDictionaryLoader(session, state) assert.equal(await loader.load(), true) assert.deepEqual(state.supported, supported) assert.equal(state.items.length, 2) assert.equal(await loader.toggle(2, false), true) assert.equal(state.items[1].enabled, false) assert.equal(state.items[0].enabled, true) assert.match(state.notice, /停用/) })