feat: add advanced context actions and optional translation
This commit is contained in:
+57
-18
@@ -1,7 +1,7 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { DEFAULT_SETTINGS, MAX_MODEL_CHARS } from "../lib/defaults.js";
|
||||
import { DEFAULT_SETTINGS } from "../lib/defaults.js";
|
||||
import { mergeSettings } from "../lib/settings.js";
|
||||
|
||||
test("empty object yields the defaults", () => {
|
||||
@@ -21,14 +21,15 @@ test("a non-object yields the defaults", () => {
|
||||
test("valid stored values are preserved", () => {
|
||||
assert.deepEqual(
|
||||
mergeSettings({
|
||||
model: "auto",
|
||||
showContextMenu: false,
|
||||
temporaryChat: true,
|
||||
webSearch: true,
|
||||
openIn: "new-window",
|
||||
promptTemplate: "Explain: {query}",
|
||||
}),
|
||||
{
|
||||
model: "auto",
|
||||
...DEFAULT_SETTINGS,
|
||||
contextMenuMode: "off",
|
||||
temporaryChat: true,
|
||||
webSearch: true,
|
||||
openIn: "new-window",
|
||||
@@ -38,8 +39,9 @@ test("valid stored values are preserved", () => {
|
||||
});
|
||||
|
||||
test("missing keys fall back individually", () => {
|
||||
const merged = mergeSettings({ model: "auto" });
|
||||
assert.equal(merged.model, "auto");
|
||||
const merged = mergeSettings({ openIn: "new-window" });
|
||||
assert.equal(merged.openIn, "new-window");
|
||||
assert.equal(merged.contextMenuMode, "simple");
|
||||
assert.equal(merged.temporaryChat, DEFAULT_SETTINGS.temporaryChat);
|
||||
assert.equal(merged.promptTemplate, DEFAULT_SETTINGS.promptTemplate);
|
||||
});
|
||||
@@ -52,12 +54,14 @@ test("unknown keys are dropped", () => {
|
||||
|
||||
test("wrong types fall back to the default", () => {
|
||||
const merged = mergeSettings({
|
||||
showContextMenu: "false",
|
||||
model: 123,
|
||||
temporaryChat: "yes",
|
||||
webSearch: 1,
|
||||
promptTemplate: [],
|
||||
});
|
||||
assert.equal(merged.model, DEFAULT_SETTINGS.model);
|
||||
assert.equal(merged.contextMenuMode, "simple");
|
||||
assert.equal("model" in merged, false);
|
||||
assert.equal(merged.temporaryChat, DEFAULT_SETTINGS.temporaryChat);
|
||||
assert.equal(merged.webSearch, DEFAULT_SETTINGS.webSearch);
|
||||
assert.equal(merged.promptTemplate, DEFAULT_SETTINGS.promptTemplate);
|
||||
@@ -75,20 +79,55 @@ test("each documented openIn value is accepted", () => {
|
||||
});
|
||||
|
||||
test("mergeSettings does not mutate DEFAULT_SETTINGS", () => {
|
||||
mergeSettings({ model: "mutated" });
|
||||
assert.equal(DEFAULT_SETTINGS.model, "");
|
||||
mergeSettings({ openIn: "new-window" });
|
||||
assert.equal(DEFAULT_SETTINGS.openIn, "new-tab");
|
||||
});
|
||||
|
||||
// An unbounded model string can, on its own, push the finished URL over
|
||||
// MAX_URL_CHARS (lib/build-url.js), which silently drops `q` — the user's
|
||||
// selection vanishes with no signal. mergeSettings() must bound `model` the
|
||||
// same way it already bounds an out-of-range openIn: fall back to the default.
|
||||
test("a model at exactly MAX_MODEL_CHARS is accepted", () => {
|
||||
const model = "m".repeat(MAX_MODEL_CHARS);
|
||||
assert.equal(mergeSettings({ model }).model, model);
|
||||
test("previously saved model selections are ignored", () => {
|
||||
assert.deepEqual(mergeSettings({ model: "gpt-5.6-sol" }), DEFAULT_SETTINGS);
|
||||
});
|
||||
|
||||
test("a model one character over MAX_MODEL_CHARS falls back to the default", () => {
|
||||
const model = "m".repeat(MAX_MODEL_CHARS + 1);
|
||||
assert.equal(mergeSettings({ model }).model, DEFAULT_SETTINGS.model);
|
||||
|
||||
test("legacy mode migrates and a valid new mode wins", () => {
|
||||
assert.equal(mergeSettings({ showContextMenu: false }).contextMenuMode, "off");
|
||||
assert.equal(mergeSettings({ showContextMenu: true }).contextMenuMode, "simple");
|
||||
assert.equal(mergeSettings({ showContextMenu: false, contextMenuMode: "advanced" }).contextMenuMode, "advanced");
|
||||
assert.equal(mergeSettings({ showContextMenu: false, contextMenuMode: "bad" }).contextMenuMode, "off");
|
||||
});
|
||||
|
||||
test("translation defaults and Unicode language limits are safe", () => {
|
||||
assert.equal(mergeSettings({}).showTranslate, false);
|
||||
assert.equal(mergeSettings({}).translationMode, "ask");
|
||||
assert.equal(mergeSettings({ translationLanguage: " Spanish " }).translationLanguage, "Spanish");
|
||||
assert.equal(mergeSettings({ translationLanguage: "😀".repeat(100) }).translationLanguage, "😀".repeat(100));
|
||||
assert.equal(mergeSettings({ translationLanguage: "😀".repeat(101) }).translationLanguage, "");
|
||||
assert.equal(mergeSettings({ translationLanguage: 42, translationMode: "bad", showTranslate: "yes" }).translationMode, "ask");
|
||||
});
|
||||
|
||||
test("load includes legacy-only preferences and save filters unknown keys", async () => {
|
||||
const { load, save } = await import("../lib/settings.js");
|
||||
let saved;
|
||||
globalThis.browser = { storage: { sync: {
|
||||
get: async (keys) => Object.fromEntries(keys.filter(k => k === "showContextMenu").map(k => [k, false])),
|
||||
set: async (value) => { saved = value; },
|
||||
} } };
|
||||
try {
|
||||
assert.equal((await load()).contextMenuMode, "off");
|
||||
await save({ ...DEFAULT_SETTINGS, contextMenuMode: "advanced", unknown: true });
|
||||
assert.equal(saved.contextMenuMode, "advanced");
|
||||
assert.equal("unknown" in saved, false);
|
||||
} finally { delete globalThis.browser; }
|
||||
});
|
||||
|
||||
test("partial saves preserve unrelated stored preferences", async () => {
|
||||
const { save } = await import("../lib/settings.js");
|
||||
const stored = { contextMenuMode: "advanced", translationLanguage: "Spanish", promptTemplate: "Explain {query}" };
|
||||
globalThis.browser = { storage: { sync: { set: async patch => Object.assign(stored, patch) } } };
|
||||
try {
|
||||
await save({ webSearch: true });
|
||||
assert.equal(stored.contextMenuMode, "advanced");
|
||||
assert.equal(stored.translationLanguage, "Spanish");
|
||||
assert.equal(stored.promptTemplate, "Explain {query}");
|
||||
assert.equal(stored.webSearch, true);
|
||||
} finally { delete globalThis.browser; }
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user