feat: Sprachnotizen in Synchronisierung, Warteschlange und Abmelden (T7 L5 Task 18)
Wartende Sprachnotizen werden nach Einträgen und Fotos hochgeladen, je Notiz ein fester Idempotency-Key; endgültige 4xx markieren die Notiz mit festem deutschem Text, 401/408/429/5xx/Netz warten auf den nächsten Lauf, „wird bearbeitet“ wird übersprungen. Warteschlangen-Sheet mit Abschnitt „Sprachnotizen“ (Verwerfen je Notiz nur für den Besitzer, alle verwerfen mit Rückfrage, Hinweis bei zu vielen offenen Notizen), Offline-Band zählt nur wartende Notizen, Abmelden fragt bei wartenden Notizen nach. Zählen liest keine Audiodaten. Gates: Frontend lint, typecheck, 2886 Tests. Refs #27 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
0c7147f626
commit
e614ded967
18 files changed
+1201
-30
No files matched your search
@@ -628,3 +628,68 @@ describe("bulk entries in the offline queue", () => {
|
||||
expect(await onlyBulkEntry()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("voice notes in the offline queue", () => {
|
||||
beforeEach(() => {
|
||||
globalThis.indexedDB = new IDBFactory();
|
||||
});
|
||||
|
||||
function note(id: string, ownerUserId: string, lastError?: typeof REJECTION) {
|
||||
return {
|
||||
id,
|
||||
ownerUserId,
|
||||
blob: new Blob(["audio"]),
|
||||
mime: "audio/webm;codecs=opus",
|
||||
durationSec: 30,
|
||||
colonyId: "colony-1",
|
||||
inspectionDate: "2026-10-16",
|
||||
createdAt: "2026-10-16T08:00:00.000Z",
|
||||
uploadIdempotencyKey: `upload-${id}`,
|
||||
state: "recorded" as const,
|
||||
...(lastError ? { lastError } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
async function storeNotes() {
|
||||
const db = await loadDb();
|
||||
await db.queueInspection(INSPECTION, "key-1", "user-1");
|
||||
const raw = await openDB(DB_NAME);
|
||||
await raw.put("pending-voice", note("v1", "user-1"));
|
||||
await raw.put("pending-voice", note("v2", "user-1", REJECTION));
|
||||
await raw.put("pending-voice", note("v3", "user-2"));
|
||||
raw.close();
|
||||
return db;
|
||||
}
|
||||
|
||||
it("summarizeQueue counts waiting, rejected and foreign voice notes", async () => {
|
||||
const db = await storeNotes();
|
||||
|
||||
expect(await db.summarizeQueue("user-1")).toEqual({
|
||||
own: 2,
|
||||
failed: 1,
|
||||
foreign: 1,
|
||||
voice: 2,
|
||||
voiceWaiting: 1,
|
||||
});
|
||||
expect(await db.summarizeQueue("user-2")).toEqual({
|
||||
own: 1,
|
||||
failed: 0,
|
||||
foreign: 3,
|
||||
voice: 1,
|
||||
voiceWaiting: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("listQueue lists the user's voice notes and never another account's", async () => {
|
||||
const db = await storeNotes();
|
||||
|
||||
const items = (await db.listQueue("user-1")).filter((item) => item.store === "pending-voice");
|
||||
|
||||
expect(items.map((item) => item.id)).toEqual(["v1", "v2"]);
|
||||
expect(items[0]).toMatchObject({
|
||||
label: "Sprachnotiz",
|
||||
voice: { colonyId: "colony-1", inspectionDate: "2026-10-16", durationSec: 30 },
|
||||
});
|
||||
expect(items[1].lastError).toMatchObject({ status: 400 });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user