thats-me/frontend/test/vitest/audio-wav.spec.js
Kevin Adametz 83c6c5813d Frontend: Offline-Warteschlange, PWA, Event-Panel und Markenauftritt
Offline
- Alle Schreibzugriffe von API-Nutzern laufen über eine Warteschlange in
  IndexedDB (services/syncQueue.js): FIFO, zusammengefasste Updates,
  Wiederholung bei Netz/Sichtbarkeit/Backoff, 4xx wird als Fehler gemeldet.
- Medien-Uploads liegen mit Bytes in der Queue, erscheinen sofort mit
  Marker und werden nach dem Upload durch das Server-Objekt ersetzt.
- PWA-Modus mit Service Worker für die App-Hülle; API nie gecacht.
  npm run build erzeugt dist/pwa.
- Speicherzustand im Panel: ausstehend / Fehler mit Wiederholen-Knopf,
  Verbindungsanzeige im Header.

Event
- Event entsteht erst mit dem ersten Eintrag; jede Löschung fragt nach.
- Key Image als 4:3-Rahmen, Galerie mit Video, mehrere Sprachnotizen mit
  Notiz, Live-Pegel beim Aufnehmen, WAV-Umwandlung für lokale Aufnahmen.
- Neue Felder: Zeitraum, Kategorie, Personen, Meilenstein; Stimmung als
  kompakte Zeile mit ausklappbarer Punktfarbe.

Oberfläche
- Untere Leiste: Darstellung und Zoom links, Jahres-Pille mittig, Plus rechts.
- Logos und Goldpalette, Login neu, zehn Wellen-Presets mit Vorschau,
  Hintergrundbilder als Kacheln, Kontomenü und Einstellungen bereinigt,
  Escape schließt Ebenen, größere Touch-Ziele.
- Alten Sync-Service und ungenutzte Legacy-Seiten entfernt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-25 07:00:35 +00:00

43 lines
1.9 KiB
JavaScript

import { describe, expect, it } from 'vitest'
import { WAV_SAMPLE_RATE, encodeWav, needsWavForPlayback } from 'src/utils/audioWav'
function ascii(view, offset, length) {
return String.fromCharCode(...Array.from({ length }, (_, i) => view.getUint8(offset + i)))
}
describe('audioWav — WAV for recordings kept in the browser', () => {
it('writes a valid 16-bit mono RIFF header', () => {
const samples = new Float32Array([0, 0.5, -0.5, 1, -1])
const view = new DataView(encodeWav(samples, WAV_SAMPLE_RATE))
expect(ascii(view, 0, 4)).toBe('RIFF')
expect(ascii(view, 8, 4)).toBe('WAVE')
expect(ascii(view, 12, 4)).toBe('fmt ')
expect(view.getUint16(20, true)).toBe(1) // PCM
expect(view.getUint16(22, true)).toBe(1) // mono
expect(view.getUint32(24, true)).toBe(WAV_SAMPLE_RATE)
expect(view.getUint32(28, true)).toBe(WAV_SAMPLE_RATE * 2)
expect(view.getUint16(34, true)).toBe(16)
expect(ascii(view, 36, 4)).toBe('data')
expect(view.getUint32(40, true)).toBe(samples.length * 2)
expect(view.byteLength).toBe(44 + samples.length * 2)
expect(view.getUint32(4, true)).toBe(view.byteLength - 8)
})
it('scales and clamps samples to the 16-bit range', () => {
const view = new DataView(encodeWav(new Float32Array([0, 1, -1, 2, -2]), 8000))
expect(view.getInt16(44, true)).toBe(0)
expect(view.getInt16(46, true)).toBe(32767)
expect(view.getInt16(48, true)).toBe(-32768)
expect(view.getInt16(50, true)).toBe(32767)
expect(view.getInt16(52, true)).toBe(-32768)
})
it('converts only the containers Safari refuses', () => {
expect(needsWavForPlayback('audio/webm')).toBe(true)
expect(needsWavForPlayback('audio/ogg;codecs=opus')).toBe(true)
expect(needsWavForPlayback('audio/mp4')).toBe(false)
expect(needsWavForPlayback('audio/wav')).toBe(false)
expect(needsWavForPlayback('')).toBe(false)
})
})