Files
whisper_web/tests/export.test.ts
T

129 lines
3.9 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import {
exportFilename,
formatSrtTimestamp,
transcriptToSrt,
transcriptToTxt,
} from "../src/lib/export";
import type { TranscriptChunk } from "../src/worker/protocol";
describe("transcriptToTxt", () => {
test("trims and appends a single trailing newline", () => {
expect(transcriptToTxt({ text: " Hello world. " })).toBe("Hello world.\n");
});
test("empty transcript becomes a bare newline", () => {
expect(transcriptToTxt({ text: "" })).toBe("\n");
});
});
describe("formatSrtTimestamp", () => {
test("zero", () => {
expect(formatSrtTimestamp(0)).toBe("00:00:00,000");
});
test("pads milliseconds to three digits", () => {
expect(formatSrtTimestamp(1.5)).toBe("00:00:01,500");
expect(formatSrtTimestamp(1.05)).toBe("00:00:01,050");
expect(formatSrtTimestamp(1.005)).toBe("00:00:01,005");
});
test("rolls over past one hour", () => {
expect(formatSrtTimestamp(3661.25)).toBe("01:01:01,250");
});
test("rounds sub-millisecond values", () => {
expect(formatSrtTimestamp(0.9996)).toBe("00:00:01,000");
});
test("clamps negatives to zero", () => {
expect(formatSrtTimestamp(-1)).toBe("00:00:00,000");
});
});
describe("transcriptToSrt", () => {
const chunk = (
text: string,
start: number,
end: number | null,
): TranscriptChunk => ({ text, timestamp: [start, end] });
test("formats numbered cues separated by blank lines", () => {
const srt = transcriptToSrt([
chunk(" Hello.", 0, 2.5),
chunk(" World.", 2.5, 4),
]);
expect(srt).toBe(
"1\n00:00:00,000 --> 00:00:02,500\nHello.\n" +
"\n" +
"2\n00:00:02,500 --> 00:00:04,000\nWorld.\n",
);
});
test("null end borrows the next chunk's start", () => {
const srt = transcriptToSrt([
chunk("One", 0, null),
chunk("Two", 3, 5),
]);
expect(srt).toContain("1\n00:00:00,000 --> 00:00:03,000\nOne");
});
test("null end on the final chunk falls back to start + 2s", () => {
const srt = transcriptToSrt([chunk("Tail", 10, null)]);
expect(srt).toBe("1\n00:00:10,000 --> 00:00:12,000\nTail\n");
});
test("skips empty and whitespace-only chunks (numbering stays contiguous)", () => {
const srt = transcriptToSrt([
chunk(" ", 0, 1),
chunk("Kept", 1, null),
chunk("", 2, 3),
chunk("Also kept", 4, 5),
]);
// The null end skips over the empty chunk to the next *captioned* start.
expect(srt).toBe(
"1\n00:00:01,000 --> 00:00:04,000\nKept\n" +
"\n" +
"2\n00:00:04,000 --> 00:00:05,000\nAlso kept\n",
);
});
test("cue never ends before it starts", () => {
// Out-of-order borrow: next captioned chunk starts before this one.
const srt = transcriptToSrt([chunk("A", 5, null), chunk("B", 4, 6)]);
expect(srt).toContain("00:00:05,000 --> 00:00:05,000");
});
test("no captioned chunks yields an empty string", () => {
expect(transcriptToSrt([])).toBe("");
expect(transcriptToSrt([chunk(" ", 0, 1)])).toBe("");
});
});
describe("exportFilename", () => {
test("swaps the extension", () => {
expect(exportFilename("talk.m4a", "srt")).toBe("talk.srt");
expect(exportFilename("talk.m4a", "txt")).toBe("talk.txt");
});
test("only the last dot counts", () => {
expect(exportFilename("2026.07.14 meeting.wav", "srt")).toBe(
"2026.07.14 meeting.srt",
);
});
test("no extension keeps the whole name", () => {
expect(exportFilename("recording", "txt")).toBe("recording.txt");
});
test("leading dot is a hidden file, not an extension", () => {
expect(exportFilename(".bashrc", "txt")).toBe(".bashrc.txt");
});
test("null, undefined, and blank fall back to transcript.*", () => {
expect(exportFilename(null, "srt")).toBe("transcript.srt");
expect(exportFilename(undefined, "txt")).toBe("transcript.txt");
expect(exportFilename(" ", "srt")).toBe("transcript.srt");
});
});