Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/notes/__tests__/settings.test.ts

Raw
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createTestSession, type TestSession } from "../../../test/harness";

const extensionPath = path.resolve(import.meta.dirname, "../index.ts");
const DEFAULTS = ["alt+n", "ctrl+enter", "ctrl+alt+enter"];

type Case = {
	user?: unknown;
	project?: unknown;
	env?: Record<string, string>;
};

describe("notes shortcut settings", () => {
	let t: TestSession | undefined;
	let root: string | undefined;
	afterEach(async () => {
		await t?.session.extensionRunner.emit({
			type: "session_shutdown",
			reason: "quit",
		});
		vi.restoreAllMocks();
		t?.dispose();
		t = undefined;
		if (root) rmSync(root, { recursive: true, force: true });
		root = undefined;
	});

	async function start(input: Case) {
		root = mkdtempSync(path.join(tmpdir(), "pi-ext-notes-settings-"));
		const write = (dir: string, value: unknown) => {
			mkdirSync(dir, { recursive: true });
			writeFileSync(
				path.join(dir, "settings.json"),
				JSON.stringify({ notes: value }),
			);
		};
		if ("user" in input)
			write(path.join(root, ".test-home/.pi/agent"), input.user);
		if ("project" in input) write(path.join(root, ".pi"), input.project);
		// Shortcuts load before any session, from process.cwd().
		vi.spyOn(process, "cwd").mockReturnValue(root);
		t = await createTestSession({
			cwd: root,
			extensions: [extensionPath],
			env: input.env,
		});
		const extension = t.session.extensionRunner.extensions[0];
		const before = t.events.uiCallsFor("notify").length;
		await t.session.extensionRunner.emit({
			type: "session_start",
			reason: "startup",
		});
		const warnings = t.events
			.uiCallsFor("notify")
			.slice(before)
			.filter((call) => call.args[1] === "warning")
			.map((call) => call.args[0]);
		return { shortcuts: [...extension.shortcuts.keys()], warnings };
	}

	it.each([
		["default", {}, DEFAULTS],
		[
			"user",
			{
				user: {
					toggleShortcut: "alt+j",
					addFromEditorShortcut: "alt+k",
					sendPickerShortcut: "alt+l",
				},
			},
			["alt+j", "alt+k", "alt+l"],
		],
		[
			"env over user",
			{
				user: { toggleShortcut: "alt+j" },
				env: {
					PI_NOTES_TOGGLE_SHORTCUT: "alt+x",
					PI_NOTES_ADD_FROM_EDITOR_SHORTCUT: "alt+y",
					PI_NOTES_SEND_PICKER_SHORTCUT: "alt+z",
				},
			},
			["alt+x", "alt+y", "alt+z"],
		],
		[
			"trusted project ignored",
			{
				user: { toggleShortcut: "alt+j" },
				project: { toggleShortcut: "alt+p", sendPickerShortcut: "alt+q" },
			},
			["alt+j", "ctrl+enter", "ctrl+alt+enter"],
		],
		[
			"swapped shortcuts stay unique",
			{
				user: {
					toggleShortcut: "ctrl+enter",
					addFromEditorShortcut: "alt+n",
				},
			},
			["ctrl+enter", "alt+n", "ctrl+alt+enter"],
		],
	] satisfies [string, Case, string[]][])(
		"%s",
		async (_name, input, expected) => {
			const { shortcuts, warnings } = await start(input);
			expect(shortcuts).toEqual(expected);
			expect(warnings).toEqual([]);
		},
	);

	it.each([
		[
			"wrong type",
			{ user: { toggleShortcut: 1 } },
			[
				"Invalid notes.toggleShortcut from user settings: must be a non-empty string",
			],
		],
		[
			"blank user value",
			{ user: { sendPickerShortcut: " " } },
			[
				"Invalid notes.sendPickerShortcut from user settings: must be a non-empty string",
			],
		],
		[
			"empty env does not fall through to user",
			{
				user: { addFromEditorShortcut: "alt+k" },
				env: { PI_NOTES_ADD_FROM_EDITOR_SHORTCUT: "" },
			},
			[
				"Invalid notes.addFromEditorShortcut from environment variable PI_NOTES_ADD_FROM_EDITOR_SHORTCUT: must be a non-empty string",
			],
		],
		[
			"duplicates",
			{ user: { toggleShortcut: "alt+j", sendPickerShortcut: "alt+j" } },
			["Duplicate notes shortcuts: alt+j, ctrl+enter, alt+j"],
		],
	] satisfies [string, Case, string[]][])(
		"%s warns and keeps defaults",
		async (_name, input, expected) => {
			const { shortcuts, warnings } = await start(input);
			expect(shortcuts).toEqual(DEFAULTS);
			expect(warnings).toEqual(expected);
		},
	);
});