test(frontend): extract CancellableLoader; pin debounce-race invariants in unit tests (0.87.23)
0.87.16 followup. Extract the four debounce-race invariants (predecessor abort, late-result suppression, AbortError swallow, ownership-aware finally) from `loadCoverage` into a reusable `CancellableLoader` helper at $lib/util/cancellable, with 9 unit tests covering each. Page rewrite uses the helper. Also corrects: hooks.server.test.ts (added Node-fetch rejection shape for already-aborted entry; removed redundant tick); admin.test.ts (AbortError-propagation now uses pre-aborted signal + mockRejectedValueOnce mirroring real Node fetch). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -731,28 +731,19 @@ describe('admin crawler api client', () => {
|
||||
});
|
||||
|
||||
it('getAnalysisMangaCoverage propagates an abort rejection', async () => {
|
||||
// Mid-flight abort: caller flips the AbortController, the fetch
|
||||
// promise rejects, and the api client lets the rejection
|
||||
// surface to its caller (the debounce-race handler treats
|
||||
// AbortError as silent cancellation rather than an error toast).
|
||||
// Production Node fetch rejects synchronously with AbortError
|
||||
// when called with an already-aborted signal. Mirror that
|
||||
// (instead of wiring our own listener and asserting the
|
||||
// rejection we self-fire) so the test exercises the contract
|
||||
// the consumer relies on — the CancellableLoader at
|
||||
// $lib/util/cancellable.ts treats AbortError as `null` and
|
||||
// skips the state update.
|
||||
const ctrl = new AbortController();
|
||||
fetchSpy.mockImplementationOnce(
|
||||
(_url, init) =>
|
||||
new Promise((_resolve, reject) => {
|
||||
const signal = (init as RequestInit).signal;
|
||||
signal?.addEventListener(
|
||||
'abort',
|
||||
() => reject(new DOMException('aborted', 'AbortError')),
|
||||
{ once: true }
|
||||
);
|
||||
})
|
||||
);
|
||||
const inflight = getAnalysisMangaCoverage(
|
||||
{ limit: 25 },
|
||||
{ signal: ctrl.signal }
|
||||
);
|
||||
ctrl.abort();
|
||||
await expect(inflight).rejects.toMatchObject({ name: 'AbortError' });
|
||||
fetchSpy.mockRejectedValueOnce(new DOMException('aborted', 'AbortError'));
|
||||
await expect(
|
||||
getAnalysisMangaCoverage({ limit: 25 }, { signal: ctrl.signal })
|
||||
).rejects.toMatchObject({ name: 'AbortError' });
|
||||
});
|
||||
|
||||
it('getAnalysisChapterCoverage unwraps items', async () => {
|
||||
|
||||
Reference in New Issue
Block a user