Files
clean-architecture-frontend…/tests/browser-capabilities/browser-files.spec.ts
T

332 lines
9.6 KiB
TypeScript

import { expect, test } from "../support/browser/strict-browser-test.ts";
test("selects, inspects, streams, and previews a real browser File", async ({
page,
}) => {
await page.goto("/");
await page.evaluate(async () => {
const modulePath =
"/src/adapters/browser-files/index.ts";
const {
createBrowserFileRuntime,
browserFilePolicyReference,
} = await import(/* @vite-ignore */ modulePath);
const input = document.createElement("input");
input.id = "capability-file-input";
input.type = "file";
const label = document.createElement("label");
label.htmlFor = input.id;
label.textContent = "PNG file";
const button = document.createElement("button");
button.type = "button";
button.textContent = "Choose test file";
document.body.append(label, input, button);
const filePolicy = browserFilePolicyReference(
"avatar",
"select-inspect-preview-png",
);
const runtime = createBrowserFileRuntime({
input,
policies: [
{
reference: filePolicy,
selection: {
policyId: "avatar-v1",
purpose: "avatar",
classification: "PERSONAL",
multiple: false,
maxCount: 1,
maxFileBytes: 1024,
maxTotalBytes: 1024,
allowEmpty: false,
accept: [
{
mediaType: "image/png",
extensions: [".png"],
},
],
},
inspection: {
policyId: "png-signature-v1",
maxInspectionBytes: 16,
acceptedSignatures: [
{
mediaType: "image/png",
extensions: [".png"],
patterns: [
{
offset: 0,
bytes: [137, 80, 78, 71, 13, 10, 26, 10],
},
],
},
],
},
preview: {
allowedMediaTypes: ["image/png"],
maxPreviewBytes: 1024,
},
},
],
limits: {
hardMaxPreviewBytes: 1024,
hardMaxObjectUrlBytes: 1024,
hardMaxTransferBytes: 4096,
},
download: {
host: Object.freeze({ handoff() {} }),
browserManagedCapabilities: {
resolve() {
return {
ok: false as const,
error: {
code: "POLICY_REJECTED" as const,
operation: "DOWNLOAD" as const,
retryable: false,
recovery: "NONE" as const,
},
};
},
},
},
});
const state = globalThis as typeof globalThis & {
__browserFileResult?: Promise<unknown>;
};
button.addEventListener("click", () => {
state.__browserFileResult = (async () => {
const selected = await runtime.baselinePicker.select({
policy: filePolicy,
});
if (
!selected.ok ||
selected.value.kind !== "SELECTED" ||
selected.value.files.length !== 1
) {
runtime.dispose();
return selected;
}
const candidate = selected.value.files[0]!;
const signal = new AbortController().signal;
const inspected = await runtime.content.inspect({
ref: candidate.ref,
policy: filePolicy,
signal,
});
const range = await runtime.content.readRange({
ref: candidate.ref,
offset: 1,
length: 4,
signal,
});
const opened = await runtime.content.openSource({
ref: candidate.ref,
signal,
});
const streamed: number[] = [];
if (opened.ok) {
for await (const chunk of opened.value.stream(signal)) {
if (chunk.ok) streamed.push(...chunk.value);
}
}
const verificationReceipt = inspected.ok
? inspected.value.verificationReceipt
: null;
if (!verificationReceipt) {
runtime.dispose();
return inspected;
}
const preview = await runtime.previews.create({
ref: candidate.ref,
verificationReceipt,
policy: filePolicy,
maxPreviewBytes: 1024,
signal,
});
let previewBytes: number[] = [];
let previewProtocol: string | null = null;
if (preview.ok) {
previewProtocol = new URL(preview.value.url).protocol;
previewBytes = Array.from(
new Uint8Array(await (await fetch(preview.value.url)).arrayBuffer()),
);
preview.value.release();
}
runtime.baselinePicker.release(candidate.ref);
runtime.dispose();
return {
selected,
inspected,
range: range.ok ? Array.from(range.value) : range,
streamed,
preview: preview.ok ? { ok: true } : preview,
previewBytes,
previewProtocol,
};
})();
});
});
const chooserPromise = page.waitForEvent("filechooser");
await page.getByRole("button", { name: "Choose test file" }).click();
const chooser = await chooserPromise;
const bytes = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10, 1, 2, 3, 4]);
await chooser.setFiles({
name: "sample.png",
mimeType: "image/png",
buffer: bytes,
});
const result = await page.evaluate(async () => {
const state = globalThis as typeof globalThis & {
__browserFileResult?: Promise<unknown>;
};
return await state.__browserFileResult;
});
expect(result).toMatchObject({
selected: {
ok: true,
value: {
kind: "SELECTED",
files: [{ sizeBytes: bytes.byteLength, source: "NATIVE_INPUT" }],
},
},
inspected: {
ok: true,
value: {
byteLength: bytes.byteLength,
detectedMediaType: "image/png",
signature: "MATCHED",
},
},
range: [80, 78, 71, 13],
streamed: Array.from(bytes),
preview: { ok: true },
previewBytes: Array.from(bytes),
previewProtocol: "blob:",
});
});
test("hands a bounded generated Blob to the real browser download path", async ({
page,
}) => {
await page.goto("/");
await page.evaluate(async () => {
const modulePath = "/src/adapters/browser-files/index.ts";
const {
createBrowserFileRuntime,
createAnchorDownloadHost,
browserFilePolicyReference,
} = await import(/* @vite-ignore */ modulePath);
const input = document.createElement("input");
input.type = "file";
input.id = "unused-download-input";
const label = document.createElement("label");
label.htmlFor = input.id;
label.textContent = "Unused file control";
const button = document.createElement("button");
button.type = "button";
button.textContent = "Download generated file";
document.body.append(label, input, button);
const downloadPolicy = browserFilePolicyReference(
"generated-export",
"bounded-object-url-bin",
);
const runtime = createBrowserFileRuntime({
input,
policies: [
{
reference: downloadPolicy,
download: {
strategy: "BOUNDED_OBJECT_URL",
mediaType: "application/octet-stream",
safeExtension: ".bin",
maxTransferBytes: 1024,
maxBufferedBytes: 1024,
integrity: "OPTIONAL",
},
},
],
limits: {
hardMaxPreviewBytes: 1024,
hardMaxObjectUrlBytes: 1024,
hardMaxTransferBytes: 4096,
},
download: {
host: createAnchorDownloadHost(document),
browserManagedCapabilities: {
resolve() {
return {
ok: false as const,
error: {
code: "POLICY_REJECTED" as const,
operation: "DOWNLOAD" as const,
retryable: false,
recovery: "NONE" as const,
},
};
},
},
},
});
const state = globalThis as typeof globalThis & {
__browserDownloadResult?: Promise<unknown>;
};
button.addEventListener("click", () => {
state.__browserDownloadResult = runtime.downloads
.deliver({
policy: downloadPolicy,
source: {
kind: "GENERATED",
bytes: {
byteLength: 5,
async *stream() {
yield {
ok: true as const,
value: new Uint8Array([1, 2]),
};
yield {
ok: true as const,
value: new Uint8Array([3, 4, 5]),
};
},
},
},
suggestedFileName: "report",
maxTransferBytes: 1024,
maxBufferedBytes: 1024,
signal: new AbortController().signal,
onProgress() {},
})
.finally(() => runtime.dispose());
});
});
const downloadPromise = page.waitForEvent("download");
await page
.getByRole("button", { name: "Download generated file" })
.click();
const download = await downloadPromise;
const stream = await download.createReadStream();
const chunks: Buffer[] = [];
for await (const chunk of stream) {
chunks.push(Buffer.from(chunk));
}
const result = await page.evaluate(async () => {
const state = globalThis as typeof globalThis & {
__browserDownloadResult?: Promise<unknown>;
};
return await state.__browserDownloadResult;
});
expect(download.suggestedFilename()).toBe("report.bin");
expect(Buffer.concat(chunks)).toEqual(Buffer.from([1, 2, 3, 4, 5]));
expect(result).toMatchObject({
ok: true,
value: {
kind: "BROWSER_HANDOFF",
},
});
});