feat: add ask notifications
This commit is contained in:
+38
-28
@@ -9,6 +9,18 @@ import { AskConfigStore } from "../src/config/store.ts";
|
||||
const DEFAULT_KEYMAPS_NOTICE_PATTERN =
|
||||
/Using default ask keymaps for this session/;
|
||||
|
||||
function expectedConfigFile(
|
||||
overrides: { behaviour?: typeof DEFAULT_ASK_CONFIG.behaviour } = {}
|
||||
) {
|
||||
return {
|
||||
schemaVersion: 3,
|
||||
answer: DEFAULT_ASK_CONFIG.answer,
|
||||
behaviour: overrides.behaviour ?? DEFAULT_ASK_CONFIG.behaviour,
|
||||
keymaps: DEFAULT_ASK_CONFIG.keymaps,
|
||||
notifications: DEFAULT_ASK_CONFIG.notifications,
|
||||
};
|
||||
}
|
||||
|
||||
async function makeTempPath(name: string): Promise<string> {
|
||||
const root = await import("node:fs/promises").then(({ mkdtemp }) =>
|
||||
mkdtemp(join(tmpdir(), name))
|
||||
@@ -23,12 +35,10 @@ test("config store writes defaults when file is missing", async () => {
|
||||
const result = await store.ensureLoaded();
|
||||
|
||||
assert.deepEqual(result.config, DEFAULT_ASK_CONFIG);
|
||||
assert.deepEqual(JSON.parse(await readFile(path, "utf-8")), {
|
||||
schemaVersion: 2,
|
||||
answer: DEFAULT_ASK_CONFIG.answer,
|
||||
behaviour: DEFAULT_ASK_CONFIG.behaviour,
|
||||
keymaps: DEFAULT_ASK_CONFIG.keymaps,
|
||||
});
|
||||
assert.deepEqual(
|
||||
JSON.parse(await readFile(path, "utf-8")),
|
||||
expectedConfigFile()
|
||||
);
|
||||
await rm(dirname(path), { force: true, recursive: true });
|
||||
});
|
||||
|
||||
@@ -47,17 +57,17 @@ test("config store writes full normalized config on save", async () => {
|
||||
});
|
||||
|
||||
const content = await readFile(path, "utf-8");
|
||||
assert.deepEqual(JSON.parse(content), {
|
||||
schemaVersion: 2,
|
||||
answer: DEFAULT_ASK_CONFIG.answer,
|
||||
behaviour: {
|
||||
autoSubmitWhenAnsweredWithoutNotes: true,
|
||||
confirmDismissWhenDirty: true,
|
||||
doublePressReviewShortcuts: true,
|
||||
showFooterHints: false,
|
||||
},
|
||||
keymaps: DEFAULT_ASK_CONFIG.keymaps,
|
||||
});
|
||||
assert.deepEqual(
|
||||
JSON.parse(content),
|
||||
expectedConfigFile({
|
||||
behaviour: {
|
||||
autoSubmitWhenAnsweredWithoutNotes: true,
|
||||
confirmDismissWhenDirty: true,
|
||||
doublePressReviewShortcuts: true,
|
||||
showFooterHints: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
await rm(dirname(path), { force: true, recursive: true });
|
||||
});
|
||||
|
||||
@@ -144,17 +154,17 @@ test("config store migrates the legacy root config path into extensions", async
|
||||
assert.equal(result.config.behaviour.doublePressReviewShortcuts, true);
|
||||
assert.equal(result.config.behaviour.showFooterHints, false);
|
||||
await assert.rejects(readFile(legacyPath, "utf-8"));
|
||||
assert.deepEqual(JSON.parse(await readFile(path, "utf-8")), {
|
||||
schemaVersion: 2,
|
||||
answer: DEFAULT_ASK_CONFIG.answer,
|
||||
behaviour: {
|
||||
autoSubmitWhenAnsweredWithoutNotes: true,
|
||||
confirmDismissWhenDirty: true,
|
||||
doublePressReviewShortcuts: true,
|
||||
showFooterHints: false,
|
||||
},
|
||||
keymaps: DEFAULT_ASK_CONFIG.keymaps,
|
||||
});
|
||||
assert.deepEqual(
|
||||
JSON.parse(await readFile(path, "utf-8")),
|
||||
expectedConfigFile({
|
||||
behaviour: {
|
||||
autoSubmitWhenAnsweredWithoutNotes: true,
|
||||
confirmDismissWhenDirty: true,
|
||||
doublePressReviewShortcuts: true,
|
||||
showFooterHints: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
await rm(root, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { DEFAULT_ASK_CONFIG } from "../src/config/defaults.ts";
|
||||
import {
|
||||
createQuestionWaitingNotification,
|
||||
notifyQuestionWaiting,
|
||||
} from "../src/notifications.ts";
|
||||
|
||||
test("notification payload uses question label before prompt", () => {
|
||||
const payload = createQuestionWaitingNotification({
|
||||
label: "Scope",
|
||||
prompt: "What should we change?",
|
||||
});
|
||||
|
||||
assert.deepEqual(payload, {
|
||||
event: "question.waiting",
|
||||
title: "pi ask",
|
||||
message: "Question waiting: Scope",
|
||||
});
|
||||
});
|
||||
|
||||
test("notification payload falls back to prompt and compacts whitespace", () => {
|
||||
const payload = createQuestionWaitingNotification({
|
||||
label: "",
|
||||
prompt: "What\nshould\twe change?",
|
||||
});
|
||||
|
||||
assert.equal(payload.message, "Question waiting: What should we change?");
|
||||
});
|
||||
|
||||
test("disabled notifications are skipped", async () => {
|
||||
const attempts = await notifyQuestionWaiting(
|
||||
{
|
||||
...DEFAULT_ASK_CONFIG,
|
||||
notifications: { channels: ["bell"], enabled: false },
|
||||
},
|
||||
createQuestionWaitingNotification({ label: "Scope", prompt: "Prompt" })
|
||||
);
|
||||
|
||||
assert.deepEqual(attempts, [{ channel: "notifications", status: "skipped" }]);
|
||||
});
|
||||
|
||||
test("command notification receives env and swallows failures", async () => {
|
||||
const payload = createQuestionWaitingNotification({
|
||||
label: "Scope",
|
||||
prompt: "Prompt",
|
||||
});
|
||||
const attempts = await notifyQuestionWaiting(
|
||||
{
|
||||
...DEFAULT_ASK_CONFIG,
|
||||
notifications: {
|
||||
channels: [
|
||||
{
|
||||
command:
|
||||
"node -e 'if (process.env.ASK_NOTIFY_MESSAGE !== \"Question waiting: Scope\") process.exit(7)'",
|
||||
type: "command",
|
||||
},
|
||||
{ command: "node -e 'process.exit(3)'", type: "command" },
|
||||
],
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
payload
|
||||
);
|
||||
|
||||
assert.equal(attempts[0]?.status, "attempted");
|
||||
assert.equal(attempts[1]?.status, "failed");
|
||||
});
|
||||
@@ -23,6 +23,10 @@ const savedConfig: AskConfig = {
|
||||
optionNote: "n",
|
||||
questionNote: "shift+n",
|
||||
},
|
||||
notifications: {
|
||||
channels: ["bell"],
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
function plainTheme() {
|
||||
|
||||
Reference in New Issue
Block a user