aboutsummaryrefslogtreecommitdiff
path: root/test-suite/unit/shared-window.test.mjs
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-11-08 12:37:44 +0000
committerIgor Soarez <igor@soarez.org>2026-11-08 12:37:44 +0000
commit66fbd3015ff68aa94f1faff1ad84740052b86a0d (patch)
treedf9a7be3c1d0f4fb91fcfa501eb33778a9a6ac6e /test-suite/unit/shared-window.test.mjs
parente538c3e53e203fabb8a9fed94b6810c345ee6f80 (diff)
tabs: agent sessions share one window instead of opening one each
A session's first tab used chrome.windows.create, so every new session name (and every Claude Code process) got its own window. It now goes into the window that already holds any 'Pi Session:' group; a window is created only when none exists. chrome_tab new for a session with no group also uses that window, not the user's focused one. The primer and the chrome_tab description say so.
Diffstat (limited to 'test-suite/unit/shared-window.test.mjs')
-rw-r--r--test-suite/unit/shared-window.test.mjs78
1 files changed, 78 insertions, 0 deletions
diff --git a/test-suite/unit/shared-window.test.mjs b/test-suite/unit/shared-window.test.mjs
new file mode 100644
index 0000000..7afeb77
--- /dev/null
+++ b/test-suite/unit/shared-window.test.mjs
@@ -0,0 +1,78 @@
+// Agent sessions share one window: a new session's tab goes into the window that already holds a
+// "Pi Session:" group, and a window is created only when there is none.
+import assert from "node:assert/strict";
+import fs from "node:fs";
+import vm from "node:vm";
+import { test } from "node:test";
+
+const workerSource = fs.readFileSync(new URL("../../extensions/chrome-profile-bridge/browser-extension/service_worker.js", import.meta.url), "utf8");
+
+function worker({ groups = [], windows = { 5: { id: 5, type: "normal" } } } = {}) {
+ const calls = [];
+ const listener = { addListener() {}, removeListener() {} };
+ let nextTab = 100;
+ const w = {
+ console, setTimeout, clearTimeout, setInterval: () => 0, navigator: { userAgent: "unit-test" }, fetch: async () => { throw new Error("offline"); },
+ chrome: {
+ runtime: { id: "test", getManifest: () => ({ version: "0.0.0" }), onInstalled: listener, onStartup: listener },
+ alarms: { create() {}, onAlarm: listener }, action: { onClicked: listener }, webNavigation: { onCommitted: listener },
+ debugger: { onDetach: listener, onEvent: listener, getTargets: (cb) => cb([]) },
+ storage: { session: { get: async () => ({}), set: async (v) => { calls.push({ method: 'storage.set', json: JSON.stringify(v) }); } } },
+ tabGroups: { query: async () => groups },
+ windows: {
+ get: async (id) => windows[id] ?? Promise.reject(new Error("No window")),
+ create: async (props) => { calls.push({ method: "windows.create", props }); return { id: 9, tabs: [{ id: 900, windowId: 9 }] }; },
+ },
+ tabs: {
+ create: async (props) => { calls.push({ method: "tabs.create", props }); return { id: nextTab++, windowId: props.windowId }; },
+ query: async () => [], get: async () => null, onUpdated: listener,
+ },
+ },
+ };
+ w.self = w;
+ vm.runInNewContext(workerSource, w);
+ return { w, calls };
+}
+
+const piGroup = (windowId, name = "Pi Session: claude-code 1 other") => ({ id: 1, windowId, title: name });
+
+test("a new session's tab goes into the window that already holds another Pi session", async () => {
+ const { w, calls } = worker({ groups: [piGroup(5)] });
+ await w.createAutomationTarget("claude-code 2 mine", "Pi Session: claude-code 2 mine");
+ assert.deepEqual(calls.filter((c) => c.method !== "storage.set").map((c) => c.method), ["tabs.create"]);
+ assert.equal(calls[0].props.windowId, 5);
+});
+
+test("with no Pi window yet, the first session creates one", async () => {
+ const { w, calls } = worker({ groups: [] });
+ await w.createAutomationTarget("claude-code 2 mine", "Pi Session: claude-code 2 mine");
+ assert.deepEqual(calls.filter((c) => c.method !== "storage.set").map((c) => c.method), ["windows.create"]);
+});
+
+test("groups that are not Pi sessions do not attract agent tabs", async () => {
+ const { w, calls } = worker({ groups: [{ id: 2, windowId: 5, title: "Work" }] });
+ await w.createAutomationTarget("claude-code 2 mine", "Pi Session: claude-code 2 mine");
+ assert.deepEqual(calls.filter((c) => c.method !== "storage.set").map((c) => c.method), ["windows.create"]);
+});
+
+test("a Pi group whose window is gone, or is not a normal window, is skipped", async () => {
+ const { w, calls } = worker({ groups: [piGroup(7), piGroup(8)], windows: { 8: { id: 8, type: "popup" } } });
+ await w.createAutomationTarget("claude-code 2 mine", "Pi Session: claude-code 2 mine");
+ assert.deepEqual(calls.filter((c) => c.method !== "storage.set").map((c) => c.method), ["windows.create"]);
+});
+
+test("the shared window is never recorded as ours, so cleanup cannot close it", async () => {
+ const { w, calls } = worker({ groups: [piGroup(5)] });
+ await w.createAutomationTarget("claude-code 2 mine", "Pi Session: claude-code 2 mine");
+ const stored = calls.filter((c) => c.method === "storage.set").map((c) => c.json).join("");
+ assert.doesNotMatch(stored, /"windowId":5/);
+});
+
+test("chrome_tab new for a session with no group uses the shared window, not the focused one", async () => {
+ const { w, calls } = worker({ groups: [piGroup(5)] });
+ w.groupTab = async (tab) => tab;
+ w.bringToFront = async () => {};
+ await w.dispatch("tab.new", { url: "about:blank", groupTitle: "Pi Session: claude-code 2 mine", sessionKey: "claude-code 2 mine" });
+ const created = calls.find((c) => c.method === "tabs.create");
+ assert.equal(created.props.windowId, 5);
+});