test(gsd): add overlay backdrop and notification lock safety tests
- Overlay layout: verify backdrop dims base lines, no dim without flag, overlay composites on top of dimmed background - Notification store: verify markAllRead and clearNotifications do not delete a foreign process's lock file
This commit is contained in:
parent
d553455732
commit
9d1e343e41
2 changed files with 99 additions and 1 deletions
65
packages/pi-tui/src/__tests__/overlay-layout.test.ts
Normal file
65
packages/pi-tui/src/__tests__/overlay-layout.test.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// pi-tui — Overlay Layout Tests (backdrop dimming)
|
||||
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { compositeOverlays, type OverlayEntry } from "../overlay-layout.js";
|
||||
|
||||
function makeEntry(
|
||||
lines: string[],
|
||||
options?: OverlayEntry["options"],
|
||||
): OverlayEntry {
|
||||
return {
|
||||
component: { render: () => lines },
|
||||
options,
|
||||
hidden: false,
|
||||
focusOrder: 1,
|
||||
};
|
||||
}
|
||||
|
||||
describe("compositeOverlays — backdrop", () => {
|
||||
it("dims base lines when backdrop is true", () => {
|
||||
const base = ["hello world", "second line"];
|
||||
const overlay = makeEntry(["OVERLAY"], {
|
||||
width: 7,
|
||||
anchor: "top-left",
|
||||
backdrop: true,
|
||||
});
|
||||
|
||||
const result = compositeOverlays(base, [overlay], 20, 20, 2);
|
||||
|
||||
// All base lines in viewport should contain dim escape (\x1b[2m)
|
||||
// The overlay line itself is composited on top, but underlying lines get dimmed
|
||||
const dimmedLine = result.find((l) => l.includes("second line"));
|
||||
assert.ok(dimmedLine, "should have a line containing 'second line'");
|
||||
assert.ok(dimmedLine.includes("\x1b[2m"), "base line should be dimmed");
|
||||
});
|
||||
|
||||
it("does not dim when backdrop is false/absent", () => {
|
||||
const base = ["hello world", "second line"];
|
||||
const overlay = makeEntry(["OVERLAY"], {
|
||||
width: 7,
|
||||
anchor: "top-left",
|
||||
});
|
||||
|
||||
const result = compositeOverlays(base, [overlay], 20, 20, 2);
|
||||
|
||||
// Lines not covered by overlay should remain undimmed
|
||||
const secondLine = result.find((l) => l.includes("second line"));
|
||||
assert.ok(secondLine, "should have a line containing 'second line'");
|
||||
assert.ok(!secondLine.includes("\x1b[2m"), "base line should not be dimmed");
|
||||
});
|
||||
|
||||
it("overlay content renders on top of dimmed background", () => {
|
||||
const base = ["aaaaaaaaaa"];
|
||||
const overlay = makeEntry(["XX"], {
|
||||
width: 2,
|
||||
anchor: "top-left",
|
||||
backdrop: true,
|
||||
});
|
||||
|
||||
const result = compositeOverlays(base, [overlay], 10, 10, 1);
|
||||
|
||||
// The first line should contain the overlay text
|
||||
assert.ok(result[0].includes("XX"), "overlay text should be composited");
|
||||
});
|
||||
});
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { describe, test, beforeEach, afterEach } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync } from "node:fs";
|
||||
import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
|
|
@ -246,4 +246,37 @@ describe("notification-store", () => {
|
|||
assert.equal(getUnreadCount(), 0);
|
||||
assert.equal(getLineCount(), 0);
|
||||
});
|
||||
|
||||
test("markAllRead does not delete a foreign lock file", () => {
|
||||
initNotificationStore(tmp);
|
||||
appendNotification("msg1", "info");
|
||||
|
||||
// Simulate another process holding the lock
|
||||
const lockPath = join(tmp, ".gsd", "notifications.lock");
|
||||
writeFileSync(lockPath, String(Date.now()), "utf-8");
|
||||
|
||||
// markAllRead should still work (best-effort) but not delete the foreign lock
|
||||
markAllRead();
|
||||
|
||||
assert.ok(existsSync(lockPath), "foreign lock file should not be deleted");
|
||||
|
||||
// Clean up the lock so afterEach doesn't leave artifacts
|
||||
rmSync(lockPath, { force: true });
|
||||
});
|
||||
|
||||
test("clearNotifications does not delete a foreign lock file", () => {
|
||||
initNotificationStore(tmp);
|
||||
appendNotification("msg1", "info");
|
||||
|
||||
// Simulate another process holding the lock
|
||||
const lockPath = join(tmp, ".gsd", "notifications.lock");
|
||||
writeFileSync(lockPath, String(Date.now()), "utf-8");
|
||||
|
||||
// clearNotifications should still work but not delete the foreign lock
|
||||
clearNotifications();
|
||||
|
||||
assert.ok(existsSync(lockPath), "foreign lock file should not be deleted");
|
||||
|
||||
rmSync(lockPath, { force: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue