`expect(x).toBeInstanceOf(Blob)` asks which realm constructed the body, not
whether there is one. Under jsdom the ambient `Blob` is jsdom's class, while
`Response.blob()` hands back node's own on Node 22 and jsdom's on Node 24 — so
both assertions passed locally and failed on the server gate, for a reason that
says nothing about the code under test. `.text()` is the mirror image: jsdom's
Blob does not implement it, so reading the bytes back is green on 22 and red on
24.
Both now assert the brand (`[object Blob]`, agreed by both realms) plus size and
type. Mutation-checked in node:22-bookworm: dropping `{ raw: true }` from
`debugExport` still turns the test red, so the repaired assertion still catches
the defect it was written for.
core.test.ts also stops building its Response from a jsdom `Blob`: on Node 22
undici does not recognise it as a body and stringifies it, so the fixture's body
was the 13 bytes of "[object Blob]" rather than the zip header. A string body is
what the sibling test already used.
61 lines
2.4 KiB
TypeScript
61 lines
2.4 KiB
TypeScript
// @vitest-environment jsdom
|
|
//
|
|
// The zip export is the one api method that reads a RAW response instead of a parsed answer, so it
|
|
// carries contract of its own: the bytes, and the name the server picked. The composer's DOM test
|
|
// mocks this method wholesale, so without this the blob and the filename parse ship untested — and
|
|
// a broken header regex would silently save every report under the fallback name.
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
vi.mock("../errorDialog", () => ({ reportError: vi.fn() }));
|
|
vi.mock("../notifications", () => ({
|
|
notify: vi.fn(() => 1),
|
|
remove: vi.fn(),
|
|
getActiveToasts: vi.fn((): { id: number }[] => []),
|
|
}));
|
|
vi.mock("../../i18n", () => ({ default: { t: (k: string) => k, language: "en" } }));
|
|
vi.mock("../activeAccount", () => ({
|
|
activeAccountId: () => null,
|
|
ACTIVE_ACCOUNT_KEY: "k",
|
|
pinActiveAccount: () => {},
|
|
}));
|
|
|
|
import { debugSessionsApi } from "./diagnostics";
|
|
|
|
const zip = (disposition?: string) =>
|
|
new Response("PK", {
|
|
status: 200,
|
|
headers: {
|
|
"content-type": "application/zip",
|
|
...(disposition ? { "content-disposition": disposition } : {}),
|
|
},
|
|
});
|
|
|
|
beforeEach(() => vi.restoreAllMocks());
|
|
|
|
describe("debugExport", () => {
|
|
it("hands back the bytes and the name the server chose", async () => {
|
|
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
zip('attachment; filename="siftlode-debug-20260919-1402-abc123.zip"'),
|
|
);
|
|
|
|
const out = await debugSessionsApi.debugExport("0123456789abcdef");
|
|
|
|
expect(out.name).toBe("siftlode-debug-20260919-1402-abc123.zip");
|
|
// The body is read as bytes, not parsed: `r.json()` on a zip would throw on its first one.
|
|
// Brand, not `toBeInstanceOf(Blob)` — that asks which realm built the body: under jsdom the
|
|
// ambient `Blob` is jsdom's class, while `Response.blob()` returns node's own on Node 22 and
|
|
// jsdom's on Node 24, so the instance check passed locally and failed on the server gate.
|
|
expect(Object.prototype.toString.call(out.blob)).toBe("[object Blob]");
|
|
expect(out.blob.size).toBe(4);
|
|
});
|
|
|
|
it("names the file itself when the answer does not", async () => {
|
|
// A proxy that strips the header must not leave the browser saving something called "export".
|
|
vi.spyOn(globalThis, "fetch").mockResolvedValue(zip());
|
|
|
|
const out = await debugSessionsApi.debugExport("0123456789abcdef");
|
|
|
|
expect(out.name).toBe("siftlode-debug-0123456789abcdef.zip");
|
|
});
|
|
});
|