Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0fed280
fix(web): give Stryker's typescript-checker a program covering router…
Mearman Sep 12, 2026
4989aa8
test(web): cover the filename-extension and relative-time helpers
Mearman Sep 12, 2026
b344a60
fix(web): remove the unreachable '.bin' fallback in the native save-p…
Mearman Sep 12, 2026
88e3106
test(web): cover the IndexedDB-backed recent-files store
Mearman Sep 12, 2026
fd2ffc0
chore(web): sync lockfile to the exact-pinned fake-indexeddb specifier
Mearman Sep 12, 2026
01846f0
test(web): cover every RPC-client-wrapping hook and the worker docume…
Mearman Sep 12, 2026
84a3ed7
test(web): cover the structural PDF/content inspection hooks
Mearman Sep 12, 2026
b28baf4
test(web): exercise the router's own oRPC procedures end to end
Mearman Sep 12, 2026
5f07c77
test(web): cover DiagnosticsPanel's collapse-threshold branching
Mearman Sep 12, 2026
80d2fc9
refactor(web): extract mountApp out of main.tsx for direct unit testing
Mearman Sep 12, 2026
2b38a4c
test(web): cover every branch of appendMathMlNodes' node-kind dispatch
Mearman Sep 12, 2026
4ab0af0
test(web): cover notifySuccess's warning-count branching and notifyEr…
Mearman Sep 12, 2026
e505442
test(web): cover the pending-reopen mailbox's write-once, read-once c…
Mearman Sep 12, 2026
6035299
test(web): cover StructureTree's empty-vs-populated render branch
Mearman Sep 12, 2026
e8ae2bb
test(web): cover InspectPanel, the format-neutral preview shells, and…
Mearman Sep 12, 2026
0ac5341
test(web): cover SlidesPreview's shape/vector rendering and paint ord…
Mearman Sep 12, 2026
cbe0472
test(web): cover FileUpload's drop/click file handling and accept nor…
Mearman Sep 12, 2026
8a572cd
test(web): cover RecentFilesPanel's reopen permission flow and size f…
Mearman Sep 12, 2026
f7b82b1
test(web): cover MarkdownPreview's paragraph styling and list grouping
Mearman Sep 12, 2026
40c8946
test(web): cover the / route's unconditional redirect to /convert
Mearman Sep 12, 2026
0c87456
refactor(web): extract the root layout's colour-scheme cycling as pur…
Mearman Sep 12, 2026
2082479
fix(web): disable route auto-code-splitting under vitest
Mearman Sep 12, 2026
be2dc45
test(web): cover FontsPage's extraction trigger and unrecognised-form…
Mearman Sep 12, 2026
a410718
test(web): add a QueryClientProvider-wrapped mount harness
Mearman Sep 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-react-hooks": "7.1.1",
"eslint-plugin-react-refresh": "0.5.3",
"fake-indexeddb": "6.2.5",
"globals": "17.9.0",
"husky": "9.1.7",
"jiti": "2.7.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, expect, it, vi } from "vitest";

import { createMockRpcClient } from "../../test/mockRpcClient";

vi.mock("../../rpc/client", () => ({ getRpcClient: vi.fn() }));

import { getRpcClient } from "../../rpc/client";
import { convertViaWorker } from "./workerDocumentConverter";

describe("convertViaWorker", () => {
it("calls the RPC client's convert with only the source, targetFormat, and bytes fields", async () => {
const client = createMockRpcClient();
const output = {
document: { format: "pdf" as const, bytes: new Uint8Array([9]) },
diagnostics: [],
content: undefined,
};
vi.mocked(client.convert).mockResolvedValue(output);
vi.mocked(getRpcClient).mockReturnValue(client);

const controller = new AbortController();
const result = await convertViaWorker({
source: "docx",
targetFormat: "pdf",
bytes: new Uint8Array([1, 2]),
signal: controller.signal,
});

expect(client.convert).toHaveBeenCalledWith(
{ source: "docx", targetFormat: "pdf", bytes: new Uint8Array([1, 2]) },
{ signal: controller.signal },
);
expect(result).toEqual(output);
});

it("passes an undefined signal through unchanged when the caller supplies none", async () => {
const client = createMockRpcClient();
vi.mocked(client.convert).mockResolvedValue({
document: { format: "pdf" as const, bytes: new Uint8Array() },
diagnostics: [],
content: undefined,
});
vi.mocked(getRpcClient).mockReturnValue(client);

await convertViaWorker({
source: "docx",
targetFormat: "pdf",
bytes: new Uint8Array([1]),
});

expect(client.convert).toHaveBeenCalledWith(expect.anything(), {
signal: undefined,
});
});
});
24 changes: 24 additions & 0 deletions packages/web/src/adapters/fileAccess/createFileAccess.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/// <reference lib="dom" />
/// <reference types="wicg-file-system-access" />
import { afterEach, describe, expect, it } from "vitest";

import { createFileAccess } from "./createFileAccess";

afterEach(() => {
Reflect.deleteProperty(window, "showOpenFilePicker");
});

describe("createFileAccess", () => {
it("returns the native adapter when the browser exposes showOpenFilePicker", () => {
window.showOpenFilePicker = (): Promise<[FileSystemFileHandle]> =>
Promise.reject(new Error("not used by this test"));
const access = createFileAccess();
expect(access.supportsNativePicker()).toBe(true);
});

it("returns the fallback adapter when the browser has no showOpenFilePicker", () => {
Reflect.deleteProperty(window, "showOpenFilePicker");
const access = createFileAccess();
expect(access.supportsNativePicker()).toBe(false);
});
});
122 changes: 122 additions & 0 deletions packages/web/src/adapters/fileAccess/fallbackFileAccess.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/// <reference lib="dom" />
import { afterEach, describe, expect, it, vi } from "vitest";

import { createFallbackFileAccess } from "./fallbackFileAccess";

afterEach(() => {
vi.restoreAllMocks();
});

// The adapter only ever reads input.files?.[0] -- a numeric-indexed, length-and-item object is all FileList's real interface requires for that, so this builds one directly rather than via object-spreading a File[] (which TypeScript flags as overwriting length/index properties it considers already declared by the array's own structural type).
function fileList(files: File[]): FileList {
const list: FileList = {
length: files.length,
item: (index: number) => files[index] ?? null,
[Symbol.iterator]: () => files[Symbol.iterator](),
};
files.forEach((file, index) => {
list[index] = file;
});
return list;
}

// The adapter drives the picker via input.click(), which a real browser resolves only after the user interacts -- here we intercept the click itself to synthesize the OS picker's outcome (a chosen file, or none) before dispatching the 'change' listener the code awaits.
function stubPickedFiles(files: File[]): void {
vi.spyOn(HTMLInputElement.prototype, "click").mockImplementation(function (
this: HTMLInputElement,
) {
Object.defineProperty(this, "files", {
value: fileList(files),
configurable: true,
});
this.dispatchEvent(new Event("change"));
});
}

describe("createFallbackFileAccess", () => {
it("reports no native picker support", () => {
expect(createFallbackFileAccess().supportsNativePicker()).toBe(false);
});

it("resolves the opened file's bytes and name when a file is chosen", async () => {
const file = new File([new Uint8Array([1, 2, 3])], "report.pdf", {
type: "application/pdf",
});
stubPickedFiles([file]);
const opened = await createFallbackFileAccess().openFile({});
expect(opened?.name).toBe("report.pdf");
expect(Array.from(opened?.bytes ?? [])).toEqual([1, 2, 3]);
expect(opened?.handle).toBeUndefined();
});

it("resolves undefined when the picker is dismissed with no file chosen", async () => {
stubPickedFiles([]);
const opened = await createFallbackFileAccess().openFile({});
expect(opened).toBeUndefined();
});

it("flattens and joins an accept map's extension groups into the input's accept attribute", async () => {
let capturedAccept = "";
vi.spyOn(HTMLInputElement.prototype, "click").mockImplementation(function (
this: HTMLInputElement,
) {
capturedAccept = this.accept;
Object.defineProperty(this, "files", {
value: fileList([]),
configurable: true,
});
this.dispatchEvent(new Event("change"));
});
await createFallbackFileAccess().openFile({
accept: {
"application/pdf": [".pdf"],
"text/markdown": [".md", ".markdown"],
},
});
expect(capturedAccept).toBe(".pdf,.md,.markdown");
});

it("leaves the input's accept attribute empty when no accept option is given", async () => {
let capturedAccept = "not set";
vi.spyOn(HTMLInputElement.prototype, "click").mockImplementation(function (
this: HTMLInputElement,
) {
capturedAccept = this.accept;
Object.defineProperty(this, "files", {
value: fileList([]),
configurable: true,
});
this.dispatchEvent(new Event("change"));
});
await createFallbackFileAccess().openFile({});
expect(capturedAccept).toBe("");
});

it("saves via a Blob-URL download anchor and revokes the object URL afterwards", async () => {
const createObjectURLSpy = vi
.spyOn(URL, "createObjectURL")
.mockReturnValue("blob:mock-url");
const revokeObjectURLSpy = vi
.spyOn(URL, "revokeObjectURL")
.mockImplementation(() => {});
let clickedHref = "";
let clickedDownload = "";
vi.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation(function (
this: HTMLAnchorElement,
) {
clickedHref = this.href;
clickedDownload = this.download;
});

const result = await createFallbackFileAccess().saveFile(
new Uint8Array([1, 2, 3]),
{ suggestedName: "out.pdf", mimeType: "application/pdf" },
);

expect(createObjectURLSpy).toHaveBeenCalledTimes(1);
expect(clickedHref).toBe("blob:mock-url");
expect(clickedDownload).toBe("out.pdf");
expect(revokeObjectURLSpy).toHaveBeenCalledWith("blob:mock-url");
expect(result).toEqual({});
});
});
Loading
Loading