fix(client): work through the findings from the coverage pass

214 items came out of writing the tests. Real defects are corrected, dead code
is removed rather than covered, and the tests that pinned the old behaviour
move with them.

Also drops two agent coverage scratch files that slipped into the coverage PR
and adds the ignore rule that keeps them out.
This commit is contained in:
Maurice
2026-07-29 11:57:24 +02:00
committed by Maurice
parent a4bf815d24
commit 72a82b3c44
425 changed files with 3155 additions and 1968 deletions
+11
View File
@@ -73,6 +73,17 @@ async function getFileBlob(url: string): Promise<Blob> {
return await resp.blob()
}
/**
* Triggers a browser download for an in-memory Blob (CSV export, generated
* report, …). Use this instead of hand-rolling the anchor: Firefox ignores a
* click on an anchor that is not in the document, and revoking the object URL
* in the same tick can abort the download in other browsers.
*/
export function downloadBlob(blob: Blob, filename: string): void {
const blobUrl = URL.createObjectURL(blob)
triggerAnchorDownload(blobUrl, filename)
}
/**
* Fetches a protected file using cookie auth (credentials: include) and
* triggers a browser download. Works inside PWA standalone mode because the
+17 -1
View File
@@ -1,4 +1,4 @@
// FE-W4UPQ-001 to FE-W4UPQ-010
// FE-W4UPQ-001 to FE-W4UPQ-012
import { describe, it, expect, vi } from 'vitest'
import { uploadFilesResilient, type UploadOpts, type UploadProgress } from './uploadQueue'
@@ -139,4 +139,20 @@ describe('uploadFilesResilient', () => {
expect(uploadOne).not.toHaveBeenCalled()
expect(res).toEqual({ succeeded: [], failed: [] })
})
it('FE-W4UPQ-012: the bytes of an aborted attempt do not survive into the retry', async () => {
const seen: UploadProgress[] = []
let attempt = 0
const uploadOne = async (f: File, opts: UploadOpts) => {
attempt++
opts.onUploadProgress({ loaded: f.size / 2, total: f.size } as never)
if (attempt === 1) throw new Error('connection reset')
return [f.name]
}
await uploadFilesResilient([file('a.txt', 200)], uploadOne, { retries: 1, onProgress: p => { seen.push(p) } })
// 50% from the aborted attempt, then back to 0 before the retry reports its own.
expect(seen.map(p => p.percent)).toEqual([50, 0, 50, 100])
})
})
+8 -1
View File
@@ -67,7 +67,14 @@ export async function uploadFilesResilient<T>(
let items: T[] | null = null
for (let attempt = 0; attempt <= maxRetries; attempt++) {
if (attempt > 0) await sleep(400 * attempt)
if (attempt > 0) {
// The aborted attempt's bytes never landed, so drop them before the
// retry starts reporting its own — otherwise they'd be counted twice
// until the first onUploadProgress of the new attempt arrives.
loadedMap.set(i, 0)
emitProgress()
await sleep(400 * attempt)
}
try {
items = await uploadOne(file, {
idempotencyKey,