import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { test } from "node:test"; import vm from "node:vm"; // Replay the actual service functions, not a second screenshot implementation. // This is not QML loading, native capture, input, file, or visual evidence. const qml = readFileSync(new URL("Screenshots.qml", import.meta.url), "utf8"); const start = qml.indexOf(" function rectFromPoints("); const end = qml.indexOf(" Connections {", start); assert.ok(start >= 0 && end > start); const plain = value => JSON.parse(JSON.stringify(value)); function service(available = true) { const calls = []; const state = { niriService: { available, screenshotScreen: path => calls.push(["screenshot-screen", path]), screenshotWindow: path => calls.push(["screenshot-window", path]), screenshot: path => calls.push(["screenshot", path]), pickColor: () => calls.push(["pick-color"]), }, osdService: null, lastColor: "", recent: [], pending: [], requestSerial: 0, lastAction: {}, lastOsd: {}, selection: { phase: "idle", completionFlash: false }, nativeActions: ["screenshot-screen", "screenshot-window", "screenshot", "pick-color"], boundedActions: [], toolbarActions: ["copy", "save", "cancel", "color"], }; Object.defineProperty(state, "nativeActionRoute", { get: () => !!state.niriService?.available, }); state.root = state; vm.createContext(state); vm.runInContext(qml.slice(start, end), state); const handler = qml.slice(end).match(/function onScreenshotCaptured\(path\) \{([^}]+)\}/); assert.ok(handler, "completion uses Connections"); assert.match(qml.slice(end), /target: root\.niriService/); state.emit = vm.runInContext(`(function(path) { ${handler[1]} })`, state); state.calls = calls; return state; } for (const [kind, native] of [ ["focused-screen", "screenshot-screen"], ["focused-window", "screenshot-window"], ["interactive", "screenshot"], ]) { test(`${kind}: submission is pending; exact completion alone saves`, () => { const s = service(); const path = `/captures/${kind}.png`; const submitted = JSON.parse(s.capture(kind, path)); assert.deepEqual(s.calls, [[native, path]]); assert.equal(submitted.recentCount, 0); assert.equal(submitted.pendingCount, 1); assert.equal(submitted.lastAction.status, "pending"); assert.equal(submitted.lastAction.saved, false); assert.equal(submitted.lastAction.completed, false); s.emit(`/unrelated/${kind}.png`); assert.equal(s.recent.length, 0); assert.equal(s.pending.length, 1); s.emit(path); assert.equal(s.pending.length, 0); assert.deepEqual(plain(s.recent), [{ id: submitted.lastAction.id, kind, path, source: "niri-native", saved: true, }]); assert.equal(s.lastAction.status, "saved"); assert.equal(s.lastAction.completed, true); s.emit(path); assert.equal(s.recent.length, 1, "unmatched duplicate cannot add a record"); }); } test("unavailable or absent compositor never dispatches or records", () => { for (const kind of ["focused-screen", "focused-window", "interactive", "select-region"]) { for (const absent of [false, true]) { const s = service(false); if (absent) s.niriService = null; if (kind === "select-region") s.completeSelection("/capture.png"); else s.capture(kind, "/capture.png"); s.emit("/capture.png"); assert.equal(s.calls.length, 0); assert.equal(s.pending.length, 0); assert.equal(s.recent.length, 0); assert.equal(s.lastAction.status, "unavailable"); assert.equal(s.selection.completionFlash, false); } } }); test("unsupported actions and missing paths never dispatch or queue", () => { const s = service(); s.capture("copy", "/capture.png"); assert.equal(s.lastAction.status, "unsupported"); for (const path of ["", null, undefined]) { s.capture("interactive", path); assert.equal(s.lastAction.status, "invalid-path"); } assert.equal(s.calls.length, 0); assert.equal(s.pending.length, 0); assert.equal(s.recent.length, 0); }); test("null completion cannot identify clipboard-only or failed-write requests", () => { const s = service(); s.capture("interactive", "/clipboard-only.png"); s.capture("focused-screen", "/missing-parent/failure.png"); const pending = plain(s.pending); for (const path of [null, null, undefined, "", 42, { path: "/clipboard-only.png" }]) s.emit(path); assert.deepEqual(plain(s.pending), pending); assert.equal(s.recent.length, 0); assert.equal(s.lastAction.completed, false); }); test("no event means unconfirmed, not saved or inferred cancelled/failed", () => { const s = service(); s.capture("interactive", "/cancelled.png"); s.capture("focused-window", "/render-failure.png"); s.niriService.available = false; const state = JSON.parse(s.stateJson()); assert.equal(state.pendingCount, 2); assert.ok(state.pending.every(request => request.status === "pending" && !request.saved)); assert.equal(state.recentCount, 0); s.emit("/render-failure.png"); assert.equal(s.recent.length, 1, "a late matching save still counts while disconnected"); }); test("repeated kinds retain distinct requests and out-of-order completions", () => { const s = service(); s.capture("focused-screen", "/first.png"); s.capture("focused-screen", "/second.png"); const [first, second] = plain(s.pending); assert.notEqual(first.id, second.id); s.emit(second.path); s.emit(first.path); assert.deepEqual(plain(s.recent).map(record => record.id), [first.id, second.id]); assert.equal(s.lastAction.id, second.id, "late completion cannot overwrite newer action"); assert.equal(s.lastAction.status, "saved"); }); test("same-path repeats consume one FIFO match per event, without pretending request identity", () => { const s = service(); s.capture("focused-screen", "/same.png"); s.capture("focused-window", "/same.png"); const [first, second] = plain(s.pending); s.emit("/same.png"); assert.equal(s.pending.length, 1); assert.equal(s.recent[0].id, first.id); assert.equal(s.lastAction.status, "pending"); s.emit("/same.png"); assert.equal(s.pending.length, 0); assert.deepEqual(plain(s.recent).map(record => record.id), [second.id, first.id]); s.capture("interactive", "/same.png"); s.emit("/same.png"); assert.equal(s.recent.length, 3, "sequential reuse also retains a distinct saved record"); }); test("matching is exact, not basename or normalized-path guessing", () => { const s = service(); s.capture("focused-screen", "capture.png"); s.emit("/capture.png"); s.emit("./capture.png"); assert.equal(s.recent.length, 0); s.emit("capture.png"); assert.equal(s.recent.length, 1); }); test("completeSelection preserves geometry but does not flash success before matching save", () => { const s = service(); s.beginSelection(30, 40); s.updateSelection(-10, -20); const submitted = JSON.parse(s.completeSelection("/region.png")); assert.deepEqual(s.calls, [["screenshot", "/region.png"]]); assert.deepEqual(submitted.selection.rect, { x: -10, y: -20, width: 40, height: 60 }); assert.equal(submitted.selection.phase, "pending"); assert.equal(submitted.selection.completionFlash, false); assert.equal(submitted.selectionVisuals.dimmedMask.visible, true); assert.equal(submitted.recentCount, 0); assert.equal(submitted.pending[0].kind, "select-region"); s.emit(null); s.emit("/other.png"); assert.equal(s.selection.phase, "pending"); s.emit("/region.png"); assert.equal(s.selection.phase, "completed"); assert.equal(s.selection.completionFlash, true); assert.equal(s.recent[0].kind, "select-region"); }); test("repeated selections only complete the current preview", () => { const s = service(); s.completeSelection("/first.png"); s.completeSelection("/second.png"); s.emit("/first.png"); assert.equal(s.selection.phase, "pending"); s.emit("/second.png"); assert.equal(s.selection.phase, "completed"); s.completeSelection("/third.png"); s.updateSelection(10, 20); s.emit("/third.png"); assert.equal(s.selection.phase, "dragging", "new drag is not completed by an older request"); }); test("local preview cancellation does not claim native cancellation or fabricate a save", () => { const s = service(); s.completeSelection("/region.png"); s.cancelSelection(); assert.equal(s.selection.phase, "idle"); assert.equal(s.selection.completionFlash, false); assert.equal(s.pending.length, 1, "native outcome remains unknown"); assert.equal(s.recent.length, 0); assert.equal(s.calls.length, 1, "no invented native cancel command"); s.emit("/region.png"); assert.equal(s.recent.length, 1, "real late save remains visible in history"); assert.equal(s.selection.phase, "idle"); assert.equal(s.lastAction.kind, "cancel"); }); test("pending is registered before native dispatch", () => { const s = service(); s.niriService.screenshot = path => s.emit(path); const result = JSON.parse(s.completeSelection("/immediate.png")); assert.equal(result.recentCount, 1); assert.equal(result.pendingCount, 0); assert.equal(result.lastAction.status, "saved"); assert.equal(result.selection.completionFlash, true); }); test("history stays capped at twelve successes; clear forgets pending without cancelling Niri", () => { const s = service(); for (let i = 0; i < 14; i++) { s.capture("focused-window", `/capture-${i}.png`); s.emit(`/capture-${i}.png`); } assert.equal(s.recent.length, 12); assert.equal(s.recent[0].path, "/capture-13.png"); assert.equal(s.recent[11].path, "/capture-2.png"); s.completeSelection("/pending.png"); const serial = s.requestSerial; const cleared = JSON.parse(s.clear()); assert.equal(cleared.recentCount, 0); assert.equal(cleared.pendingCount, 0); assert.equal(cleared.lastAction.kind, "clear"); assert.equal(cleared.selection.phase, "idle"); assert.equal(s.calls.length, 15); s.emit("/pending.png"); assert.equal(s.recent.length, 0); s.capture("focused-screen", "/new.png"); assert.ok(s.requestSerial > serial, "clear cannot reuse local IDs"); }); test("color picking, OSD fallback and metadata-only record actions remain independent", () => { const s = service(); s.capture("focused-screen", "/capture.png"); s.pickColor(); assert.deepEqual(s.calls.at(-1), ["pick-color"]); s.acceptPickedColor("not a color"); assert.equal(s.lastColor, ""); s.acceptPickedColor("#ABCDEF"); assert.equal(s.lastColor, "#abcdef"); assert.deepEqual(plain(s.lastOsd), { event: "color", value: "#abcdef" }); s.osdService = { trigger: (event, value) => JSON.stringify({ event, value, delivered: true }) }; s.acceptPickedColor("#123456"); assert.equal(s.lastOsd.delivered, true); s.osdService = { trigger() { throw Error("unavailable"); } }; s.acceptPickedColor("#654321"); assert.equal(s.lastOsd.value, "#654321"); s.emit("/capture.png"); assert.equal(s.lastAction.kind, "pick-color"); s.recordAction("copy", s.recent[0].id); assert.equal(s.lastAction.nativeActionRoute, false); assert.equal(s.recent.length, 1); s.niriService = null; s.pickColor(); assert.equal(s.calls.length, 2, "unavailable color picking does not dispatch"); });