buildChatGptUrl and its fitToBudget helpers have had no production caller since prepareAction took over routing; the 2026-10-05 plan kept them only for existing tests until then. They also shortened silently, against the ask-before-shortening rule, so they were a trap to reuse. Port the tests that guard live code onto applyTemplate and serializePrompt, including the $&, $`, $' and $$ regressions (checked to fail against the string-replacement form), and cover prepareAction's whole-word cut, which nothing tested before. Also drop getContext's inputType password check. menus.OnClickData has no inputType, so it never fired; Firefox already withholds password fields from selectionText via mozIsTextField(true) in SelectionUtils (bug 565717). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
3.7 KiB
JavaScript
88 lines
3.7 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { DEFAULT_SETTINGS } from "../lib/defaults.js";
|
|
import { applyTemplate, serializePrompt } from "../lib/build-url.js";
|
|
|
|
const withSettings = (overrides) => ({ ...DEFAULT_SETTINGS, ...overrides });
|
|
const queryOf = (url) => new URL(url).searchParams.get("q");
|
|
const paramsOf = (url) => [...new URL(url).searchParams.keys()];
|
|
|
|
test("defaults produce only the q parameter on the root path", () => {
|
|
assert.equal(serializePrompt(DEFAULT_SETTINGS, "hello"), "https://chatgpt.com/?q=hello");
|
|
});
|
|
|
|
test("an empty prompt omits q entirely", () => {
|
|
assert.equal(serializePrompt(DEFAULT_SETTINGS, ""), "https://chatgpt.com/");
|
|
for (const template of ["{query}", "Explain: {query}", "Be concise:", ""]) {
|
|
assert.equal(applyTemplate(template, ""), "", `template=${JSON.stringify(template)}`);
|
|
}
|
|
});
|
|
|
|
test("legacy model values never add a model URL parameter", () => {
|
|
assert.equal(serializePrompt(withSettings({ model: "gpt-5.6-sol" }), "hi"),
|
|
serializePrompt(DEFAULT_SETTINGS, "hi"));
|
|
});
|
|
|
|
test("temporaryChat and webSearch each appear only when enabled", () => {
|
|
assert.deepEqual(paramsOf(serializePrompt(DEFAULT_SETTINGS, "hi")), ["q"]);
|
|
|
|
const temp = new URL(serializePrompt(withSettings({ temporaryChat: true }), "hi"));
|
|
assert.equal(temp.searchParams.get("temporary-chat"), "true");
|
|
|
|
const search = new URL(serializePrompt(withSettings({ webSearch: true }), "hi"));
|
|
assert.equal(search.searchParams.get("hints"), "search");
|
|
});
|
|
|
|
test("all URL-affecting settings combine, q first", () => {
|
|
const settings = withSettings({ temporaryChat: true, webSearch: true, promptTemplate: "Explain: {query}" });
|
|
const url = serializePrompt(settings, applyTemplate(settings.promptTemplate, "entropy"));
|
|
assert.deepEqual(paramsOf(url), ["q", "temporary-chat", "hints"]);
|
|
assert.equal(queryOf(url), "Explain: entropy");
|
|
});
|
|
|
|
test("openIn never influences the URL", () => {
|
|
const base = serializePrompt(DEFAULT_SETTINGS, "hi");
|
|
for (const openIn of ["new-tab", "background-tab", "new-window"]) {
|
|
assert.equal(serializePrompt(withSettings({ openIn }), "hi"), base);
|
|
}
|
|
});
|
|
|
|
test("special characters survive encoding round-trip", () => {
|
|
const raw = 'a & b # c ? d = e + f "g" <h> 100% 😀 日本語\nnewline';
|
|
const url = serializePrompt(DEFAULT_SETTINGS, raw);
|
|
assert.equal(queryOf(url), raw);
|
|
assert.equal(url.includes(" "), false);
|
|
assert.equal(url.includes("#"), false);
|
|
});
|
|
|
|
test("the template replaces every {query} occurrence", () => {
|
|
assert.equal(applyTemplate("{query} / {query}", "x"), "x / x");
|
|
});
|
|
|
|
test("a template without {query} is treated as a prefix, never dropping the query", () => {
|
|
assert.equal(applyTemplate("Be concise:", "why"), "Be concise: why");
|
|
});
|
|
|
|
test("an empty or whitespace-only template yields the bare query", () => {
|
|
for (const template of ["", " "]) {
|
|
assert.equal(applyTemplate(template, "why"), "why");
|
|
}
|
|
});
|
|
|
|
test("a non-string template degrades to the bare query", () => {
|
|
assert.equal(applyTemplate(null, "why"), "why");
|
|
});
|
|
|
|
// String.prototype.replaceAll gives $&, $`, $', and $$ special meaning in the
|
|
// REPLACEMENT string, and `query` (arbitrary page-selected text) is exactly
|
|
// that argument. These selections are ordinary shell one-liners, not
|
|
// adversarial input, and must round-trip byte-identically into `q`.
|
|
for (const raw of ["awk print $& here", "shell $` and $' here", "PID is $$ in bash"]) {
|
|
test(`a selection containing ${raw.match(/\$./g).join(" and ")} survives templating byte-identically`, () => {
|
|
assert.equal(applyTemplate("{query}", raw), raw);
|
|
assert.equal(applyTemplate("Explain: {query}", raw), `Explain: ${raw}`);
|
|
assert.equal(queryOf(serializePrompt(DEFAULT_SETTINGS, applyTemplate("{query}", raw))), raw);
|
|
});
|
|
}
|