import test from "node:test"; import assert from "node:assert/strict"; import { DEFAULT_SETTINGS } from "../lib/defaults.js"; import { mergeSettings } from "../lib/settings.js"; test("empty object yields the defaults", () => { assert.deepEqual(mergeSettings({}), DEFAULT_SETTINGS); }); test("null and undefined yield the defaults", () => { assert.deepEqual(mergeSettings(null), DEFAULT_SETTINGS); assert.deepEqual(mergeSettings(undefined), DEFAULT_SETTINGS); }); test("a non-object yields the defaults", () => { assert.deepEqual(mergeSettings("nonsense"), DEFAULT_SETTINGS); assert.deepEqual(mergeSettings(42), DEFAULT_SETTINGS); }); test("valid stored values are preserved", () => { assert.deepEqual( mergeSettings({ showContextMenu: false, temporaryChat: true, webSearch: true, openIn: "new-window", promptTemplate: "Explain: {query}", }), { ...DEFAULT_SETTINGS, contextMenuMode: "off", temporaryChat: true, webSearch: true, openIn: "new-window", promptTemplate: "Explain: {query}", }, ); }); test("missing keys fall back individually", () => { 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); }); test("unknown keys are dropped", () => { const merged = mergeSettings({ model: "auto", nope: "gone", openInn: "typo" }); assert.deepEqual(Object.keys(merged).sort(), Object.keys(DEFAULT_SETTINGS).sort()); assert.equal("nope" in merged, false); }); test("wrong types fall back to the default", () => { const merged = mergeSettings({ showContextMenu: "false", model: 123, temporaryChat: "yes", webSearch: 1, promptTemplate: [], }); 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); }); test("an out-of-range openIn falls back to the default", () => { assert.equal(mergeSettings({ openIn: "teleport" }).openIn, DEFAULT_SETTINGS.openIn); assert.equal(mergeSettings({ openIn: "" }).openIn, DEFAULT_SETTINGS.openIn); }); test("each documented openIn value is accepted", () => { for (const value of ["new-tab", "background-tab", "new-window"]) { assert.equal(mergeSettings({ openIn: value }).openIn, value); } }); test("mergeSettings does not mutate DEFAULT_SETTINGS", () => { mergeSettings({ openIn: "new-window" }); assert.equal(DEFAULT_SETTINGS.openIn, "new-tab"); }); test("previously saved model selections are ignored", () => { assert.deepEqual(mergeSettings({ model: "gpt-5.6-sol" }), DEFAULT_SETTINGS); }); 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; } });