Files
SubTracker/apps/web/tests/unit/utils/single-flight.test.ts
SmileQWQ 5004c3b3c8 perf: port reusable worker improvements to main
Port cross-runtime improvements from the cf-worker branch back to main without bringing Worker-free specific tradeoffs.

Frontend:
- add single-flight request coalescing for save, settings, tag, and AI actions
- add explicit saving/loading states for subscription save and AI recognition
- unify settings data access behind useSettingsQuery with a shared query key
- stop AI autofill from overwriting advance/overdue reminder rules
- deep-clone settings query data before binding form state so nested notification and AI inputs remain editable
- refine AI loading copy layout and adjust calendar card wording for clearer UX

Backend:
- split getAppSettings hot-path reads into narrow getters for login options, AI config, reminder defaults, and notification channel/scan settings
- cache auth session secret, stored credentials, and mustChangePassword in memory to avoid repeated password verification work on every request
- slim statistics and calendar reads to only fetch fields required by each view
- reduce exchange-rate read duplication by reusing resolved base currency instead of re-reading settings unnecessarily
- batch Wallos import writes via createMany for tags, subscriptions, and subscription-tag joins, then append subscription order once

Tests:
- add auth service cache coverage
- add Wallos commit batching coverage
- add single-flight, settings-form clone, and AI recognition status unit tests
- update auth, AI, and statistics tests for the new main-branch implementation strategy

Validation:
- targeted API tests passed
- targeted web tests passed
- npm run lint passed
- npm run build passed
2026-04-23 09:10:06 +08:00

43 lines
1.3 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import { createSingleFlight } from '@/utils/single-flight'
function deferred<T>() {
let resolve!: (value: T) => void
let reject!: (reason?: unknown) => void
const promise = new Promise<T>((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
describe('createSingleFlight', () => {
it('coalesces repeated concurrent calls into a single request', async () => {
const task = deferred<string>()
const worker = vi.fn(async (value: string) => task.promise)
const singleFlight = createSingleFlight(worker)
const first = singleFlight.run('save')
const second = singleFlight.run('save')
expect(singleFlight.pending).toBe(true)
expect(worker).toHaveBeenCalledTimes(1)
expect(first).toBe(second)
task.resolve('done')
await expect(first).resolves.toBe('done')
expect(singleFlight.pending).toBe(false)
})
it('allows a new call after the previous one settles', async () => {
const worker = vi.fn(async (value: string) => value)
const singleFlight = createSingleFlight(worker)
await expect(singleFlight.run('first')).resolves.toBe('first')
await expect(singleFlight.run('second')).resolves.toBe('second')
expect(worker).toHaveBeenCalledTimes(2)
})
})