/** * A phone is turned away from a match, and nothing else is. * * The gate is a hard stop with no "play anyway" behind it, so the cost of it * answering yes to a machine that can play is a player who cannot play at * all. That is what these cases are: the two shapes a phone takes, and the * three things next to it that have to stay out — a desktop window dragged * narrow, a tablet in landscape, and a browser with no matchMedia at all. * * Run with `npm test`. */ import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; const run = promisify(execFile); const web = join(new URL(".", import.meta.url).pathname, ".."); const dir = await mkdtemp(join(tmpdir(), "phone-gate-")); const bundle = join(dir, "fixture.mjs"); await run( join(web, "node_modules/.bin/rolldown"), [ join(web, "scripts/phone-gate.fixture.ts"), "--format", "esm", "--platform", "node", "--file", bundle, ], { cwd: web }, ); const { isPhone } = await import(bundle); const ROOT_PX = 16; /** * A browser of the given size and pointer, answering the three queries the * predicate asks the way a real one would. * * Only the four features it uses are parsed. A query it does not know is a * mistake in the module rather than something to answer with false, so this * throws on one. */ function browser({ width, height, pointer }) { globalThis.window = { matchMedia(query) { const [, feature, value] = /\((.+):\s*(.+)\)/.exec(query) ?? []; const px = Number.parseFloat(value) * (value.endsWith("rem") ? ROOT_PX : 1); switch (feature) { case "pointer": return { matches: pointer === value }; case "max-width": return { matches: width <= px }; case "max-height": return { matches: height <= px }; default: throw new Error(`the gate asked an unhandled query: ${query}`); } }, }; } test("a phone held either way is a phone", () => { browser({ width: 390, height: 844, pointer: "coarse" }); assert.equal(isPhone(), true, "a phone upright was let through to a match"); browser({ width: 844, height: 390, pointer: "coarse" }); assert.equal( isPhone(), true, "a phone on its side is wide enough to pass the width check, and the " + "height check is the only thing that catches it", ); }); test("a narrow desktop window is not a phone", () => { browser({ width: 500, height: 900, pointer: "fine" }); assert.equal( isPhone(), false, "a mouse in front of a small viewport was locked out of a match it can play", ); }); test("a tablet in landscape is not a phone", () => { browser({ width: 1024, height: 768, pointer: "coarse" }); assert.equal(isPhone(), false); }); test("a browser without matchMedia is not a phone", () => { globalThis.window = {}; assert.equal( isPhone(), false, "an unanswerable question turned a player away; the gate must fail open", ); }); test.after(() => rm(dir, { recursive: true, force: true }));