Files
device-tools/tests/core/device.test.ts
T
2026-09-01 14:22:42 +02:00

183 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it, vi } from "vitest";
import {
collectPassiveCapabilities,
createRedactedReport,
exportReportCsv,
exportReportJson,
runProbe,
type Capability,
type DeviceEnvironment,
type ProbeResult,
} from "../../src/core/device";
function environment(
navigatorOverrides: Record<string, unknown> = {},
): DeviceEnvironment {
const fakeWindow = {
innerWidth: 1234,
innerHeight: 777,
devicePixelRatio: 2.25,
top: null,
visualViewport: { width: 1200, height: 700 },
matchMedia: (query: string) => ({ matches: query.includes("fine") }),
WebGLRenderingContext: function WebGLRenderingContext() {},
indexedDB: {},
caches: {},
} as unknown as Window;
Object.defineProperty(fakeWindow, "top", { value: fakeWindow });
const fakeDocument = {
fullscreenEnabled: true,
pictureInPictureEnabled: true,
createElement: (name: string) =>
name === "video" || name === "audio"
? {
canPlayType: (mime: string) =>
mime.includes("opus") ? "probably" : "",
}
: {},
} as unknown as Document;
return {
window: fakeWindow,
document: fakeDocument,
navigator: {
onLine: true,
maxTouchPoints: 5,
mediaDevices: {
getSupportedConstraints: () => ({
width: true,
echoCancellation: true,
}),
},
...navigatorOverrides,
} as unknown as DeviceEnvironment["navigator"],
screen: {
width: 2560,
height: 1440,
availWidth: 2500,
availHeight: 1400,
colorDepth: 24,
orientation: { type: "landscape-primary" },
} as Screen,
secureContext: true,
isolated: false,
};
}
describe("passive device inventory", () => {
it("reports display, input, constraints, codecs and API presence without probing", () => {
const capabilities = collectPassiveCapabilities(environment());
expect(capabilities.find((item) => item.id === "viewport")?.value).toBe(
"1234 × 777 CSS px",
);
expect(capabilities.find((item) => item.id === "touch")?.state).toBe(
"available",
);
expect(
capabilities.find((item) => item.id === "media-constraints")?.value,
).toContain("echoCancellation");
expect(capabilities.find((item) => item.id === "codec-opus")?.value).toBe(
"Probably",
);
expect(capabilities.find((item) => item.id === "camera-api")?.state).toBe(
"unavailable",
);
});
});
describe("explicit probes", () => {
it("reads storage locally and preserves only buckets in a report", async () => {
const result = await runProbe(
"storage",
environment({
storage: {
estimate: async () => ({ usage: 500_000_000, quota: 2_000_000_000 }),
},
}),
);
expect(result.state).toBe("complete");
expect(result.details[0]?.value).toContain("MiB");
const report = createRedactedReport([], [result]);
const serialized = exportReportJson(report);
expect(serialized).not.toContain("476.84");
expect(serialized).toContain("25-to-49%");
});
it("stops every user-media track immediately", async () => {
const stop = vi.fn();
const result = await runProbe(
"camera",
environment({
mediaDevices: {
getUserMedia: async () => ({ getTracks: () => [{ stop }, { stop }] }),
},
}),
);
expect(result.state).toBe("complete");
expect(stop).toHaveBeenCalledTimes(2);
});
it("queries permission state without treating unsupported names as failure", async () => {
const query = vi.fn(async ({ name }: { name: string }) => {
if (name === "camera") throw new TypeError("not supported");
return { state: "prompt" };
});
const result = await runProbe(
"permissions",
environment({ permissions: { query } }),
);
expect(result.state).toBe("complete");
expect(result.details.find((item) => item.label === "camera")?.value).toBe(
"unsupported",
);
expect(query).toHaveBeenCalled();
});
});
describe("redacted exports", () => {
it("never emits exact high-entropy capability values", () => {
const capabilities: Capability[] = [
{
id: "viewport",
group: "Display",
label: "Viewport",
state: "available",
value: "1234 × 777 CSS px",
privacy: "high",
explanation: "",
},
{
id: "dpr",
group: "Display",
label: "DPR",
state: "available",
value: "2.25",
privacy: "high",
explanation: "",
},
{
id: "touch",
group: "Input",
label: "Touch",
state: "available",
value: "5 maximum point(s)",
privacy: "medium",
explanation: "",
},
];
const probe: ProbeResult = {
id: "media-devices",
state: "complete",
summary: "done",
details: [{ label: "videoinput", value: "7", redacted: "many" }],
};
const report = createRedactedReport(capabilities, [probe]);
const json = exportReportJson(report);
expect(json).not.toContain("1234");
expect(json).not.toContain("2.25");
expect(json).toContain("medium");
expect(json).toContain("multi-touch");
expect(exportReportCsv(report)).toContain("capability,viewport");
expect(report.policy.join(" ")).toContain("No stable identifier");
});
});