Release Helper Tools 0.2.0
Verify / verify (push) Canceled after 0s

This commit is contained in:
2026-09-02 01:01:56 +02:00
parent 5fffd48642
commit 6c763fcb5a
30 changed files with 2022 additions and 51 deletions
@@ -6,12 +6,16 @@ import {
assertBoundedItems,
assertBoundedText,
createObjectUrlLease,
createObjectUrlLeasePool,
createSeededRandom,
formatBytes,
planBlobDownloads,
sanitizeDownloadFilename,
secureRandomBytes,
secureRandomInt,
shuffleSeeded,
triggerBlobDownload,
triggerBlobDownloads,
} from "../../src/helpers";
describe("shared ceilings and download safety", () => {
@@ -85,6 +89,113 @@ describe("shared ceilings and download safety", () => {
vi.stubGlobal("URL", originalUrl);
}
});
it("formats IEC and SI byte quantities deterministically", () => {
expect(formatBytes(0)).toBe("0 B");
expect(formatBytes(1536)).toBe("1.50 KiB");
expect(formatBytes(1500, { system: "si", fractionDigits: 1 })).toBe(
"1.5 kB",
);
expect(formatBytes(-1024)).toBe("-1.00 KiB");
expect(formatBytes(Number.NaN)).toBe("unknown");
});
it("replaces and revokes named object URL leases", () => {
let sequence = 0;
const revokeObjectURL = vi.fn();
const pool = createObjectUrlLeasePool({
createObjectURL: vi.fn(() => `blob:${++sequence}`),
revokeObjectURL,
});
const first = pool.create("preview", new Blob(["one"]));
const second = pool.create("preview", new Blob(["two"]));
expect(first.revoked).toBe(true);
expect(second.url).toBe("blob:2");
expect(pool.size).toBe(1);
expect(pool.revoke("missing")).toBe(false);
pool.revokeAll();
expect(second.revoked).toBe(true);
expect(pool.size).toBe(0);
expect(revokeObjectURL).toHaveBeenCalledTimes(2);
});
it("plans collision-free batch names and revokes every URL", () => {
const items = [
{ blob: new Blob(["a"]), filename: "Report.txt" },
{ blob: new Blob(["b"]), filename: "report.txt" },
{ blob: new Blob(["c"]), filename: "../unsafe?.txt" },
];
expect(planBlobDownloads(items).map((item) => item.filename)).toEqual([
"Report.txt",
"report (2).txt",
"_unsafe_.txt",
]);
const revokeObjectURL = vi.fn();
let index = 0;
const click = vi
.spyOn(HTMLAnchorElement.prototype, "click")
.mockImplementation(() => undefined);
let scheduled: (() => void) | undefined;
const batch = triggerBlobDownloads(items, {
ownerDocument: document,
urlApi: {
createObjectURL: () => `blob:batch-${++index}`,
revokeObjectURL,
},
schedule: (callback) => {
scheduled = callback;
},
});
expect(click).toHaveBeenCalledTimes(3);
expect(batch.leases.every((lease) => !lease.revoked)).toBe(true);
scheduled?.();
expect(batch.leases.every((lease) => lease.revoked)).toBe(true);
expect(revokeObjectURL).toHaveBeenCalledTimes(3);
});
it("bounds lazy download plans before consuming the whole iterable", () => {
let yielded = 0;
function* many() {
while (true) {
yielded += 1;
yield { blob: new Blob(["x"]), filename: "same.txt" };
}
}
expect(() => planBlobDownloads(many(), { maximumFiles: 2 })).toThrow(
HelperLimitError,
);
expect(yielded).toBe(3);
expect(() => planBlobDownloads([], { maximumFilenameLength: 7 })).toThrow(
/at least 8/u,
);
});
it("revokes already-created batch URLs when a click fails", () => {
const revokeObjectURL = vi.fn();
const click = vi
.spyOn(HTMLAnchorElement.prototype, "click")
.mockImplementationOnce(() => undefined)
.mockImplementationOnce(() => {
throw new Error("blocked");
});
expect(() =>
triggerBlobDownloads(
[
{ blob: new Blob(["a"]), filename: "a.txt" },
{ blob: new Blob(["b"]), filename: "b.txt" },
],
{
ownerDocument: document,
urlApi: {
createObjectURL: () => `blob:test:${click.mock.calls.length}`,
revokeObjectURL,
},
},
),
).toThrow("blocked");
expect(revokeObjectURL).toHaveBeenCalledTimes(2);
});
});
describe("secure and seeded randomness", () => {