



















@@ -396,4 +396,212 @@ describe("browser server-context tab selection state", () => {
396396undefined,
397397]);
398398});
399+400+it("assigns stable tab ids and prefers labels as suggested target ids", async () => {
401+const fetchMock = vi.fn(async (url: unknown) => {
402+const value = String(url);
403+if (!value.includes("/json/list")) {
404+throw new Error(`unexpected fetch: ${value}`);
405+}
406+return {
407+ok: true,
408+json: async () => [
409+{
410+id: "DOCS_RAW",
411+title: "Docs",
412+url: "https://docs.example.com",
413+webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/DOCS_RAW",
414+type: "page",
415+},
416+{
417+id: "APP_RAW",
418+title: "App",
419+url: "https://app.example.com",
420+webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/APP_RAW",
421+type: "page",
422+},
423+],
424+} as unknown as Response;
425+});
426+427+global.fetch = withBrowserFetchPreconnect(fetchMock);
428+const state = makeState("openclaw");
429+const ctx = createTestBrowserRouteContext({ getState: () => state });
430+const openclaw = ctx.forProfile("openclaw");
431+432+expect(await openclaw.listTabs()).toEqual([
433+expect.objectContaining({
434+targetId: "DOCS_RAW",
435+tabId: "t1",
436+suggestedTargetId: "t1",
437+}),
438+expect.objectContaining({
439+targetId: "APP_RAW",
440+tabId: "t2",
441+suggestedTargetId: "t2",
442+}),
443+]);
444+445+await expect(openclaw.labelTab("t1", "docs")).resolves.toEqual(
446+expect.objectContaining({
447+targetId: "DOCS_RAW",
448+tabId: "t1",
449+label: "docs",
450+suggestedTargetId: "docs",
451+}),
452+);
453+});
454+455+it("carries a stale alias to a single replacement target", async () => {
456+let listCount = 0;
457+const fetchMock = vi.fn(async (url: unknown) => {
458+const value = String(url);
459+if (!value.includes("/json/list")) {
460+throw new Error(`unexpected fetch: ${value}`);
461+}
462+listCount += 1;
463+const secondList = listCount > 1;
464+return {
465+ok: true,
466+json: async () =>
467+secondList
468+ ? [
469+{
470+id: "FIRST_RAW",
471+title: "First",
472+url: "https://first.example.com",
473+webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/FIRST_RAW",
474+type: "page",
475+},
476+{
477+id: "THIRD_RAW",
478+title: "Third",
479+url: "https://third.example.com",
480+webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/THIRD_RAW",
481+type: "page",
482+},
483+]
484+ : [
485+{
486+id: "FIRST_RAW",
487+title: "First",
488+url: "https://first.example.com",
489+webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/FIRST_RAW",
490+type: "page",
491+},
492+{
493+id: "SECOND_RAW",
494+title: "Second",
495+url: "https://second.example.com",
496+webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/SECOND_RAW",
497+type: "page",
498+},
499+],
500+} as unknown as Response;
501+});
502+503+global.fetch = withBrowserFetchPreconnect(fetchMock);
504+const state = makeState("openclaw");
505+const ctx = createTestBrowserRouteContext({ getState: () => state });
506+const openclaw = ctx.forProfile("openclaw");
507+508+expect((await openclaw.listTabs()).map((tab) => tab.tabId)).toEqual(["t1", "t2"]);
509+expect(await openclaw.listTabs()).toEqual([
510+expect.objectContaining({ targetId: "FIRST_RAW", tabId: "t1" }),
511+expect.objectContaining({ targetId: "THIRD_RAW", tabId: "t2" }),
512+]);
513+});
514+515+it("carries stable aliases across confident raw target replacement", async () => {
516+let listCount = 0;
517+const fetchMock = vi.fn(async (url: unknown) => {
518+const value = String(url);
519+if (!value.includes("/json/list")) {
520+throw new Error(`unexpected fetch: ${value}`);
521+}
522+listCount += 1;
523+const targetId = listCount > 1 ? "NEW_RAW" : "OLD_RAW";
524+return {
525+ok: true,
526+json: async () => [
527+{
528+id: targetId,
529+title: "Checkout",
530+url: "https://shop.example.com/checkout",
531+webSocketDebuggerUrl: `ws://127.0.0.1/devtools/page/${targetId}`,
532+type: "page",
533+},
534+],
535+} as unknown as Response;
536+});
537+538+global.fetch = withBrowserFetchPreconnect(fetchMock);
539+const state = makeState("openclaw");
540+const ctx = createTestBrowserRouteContext({ getState: () => state });
541+const openclaw = ctx.forProfile("openclaw");
542+543+await expect(openclaw.labelTab("OLD_RAW", "checkout")).resolves.toEqual(
544+expect.objectContaining({
545+targetId: "OLD_RAW",
546+tabId: "t1",
547+suggestedTargetId: "checkout",
548+}),
549+);
550+const profileState = state.profiles.get("openclaw");
551+if (!profileState) {
552+throw new Error("expected profile state");
553+}
554+profileState.lastTargetId = "OLD_RAW";
555+556+await expect(openclaw.listTabs()).resolves.toEqual([
557+expect.objectContaining({
558+targetId: "NEW_RAW",
559+tabId: "t1",
560+label: "checkout",
561+suggestedTargetId: "checkout",
562+}),
563+]);
564+expect(state.profiles.get("openclaw")?.lastTargetId).toBe("NEW_RAW");
565+});
566+567+it("resolves friendly tab references before backend focus and close calls", async () => {
568+const fetchMock = vi.fn(async (url: unknown) => {
569+const value = String(url);
570+if (value.includes("/json/list")) {
571+return {
572+ok: true,
573+json: async () => [
574+{
575+id: "DOCS_RAW",
576+title: "Docs",
577+url: "https://docs.example.com",
578+webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/DOCS_RAW",
579+type: "page",
580+},
581+],
582+} as unknown as Response;
583+}
584+if (value.includes("/json/activate/DOCS_RAW") || value.includes("/json/close/DOCS_RAW")) {
585+return { ok: true } as unknown as Response;
586+}
587+throw new Error(`unexpected fetch: ${value}`);
588+});
589+590+global.fetch = withBrowserFetchPreconnect(fetchMock);
591+const state = makeState("openclaw");
592+const ctx = createTestBrowserRouteContext({ getState: () => state });
593+const openclaw = ctx.forProfile("openclaw");
594+595+await openclaw.labelTab("DOCS_RAW", "docs");
596+await expect(openclaw.ensureTabAvailable("t1")).resolves.toEqual(
597+expect.objectContaining({ targetId: "DOCS_RAW" }),
598+);
599+await openclaw.focusTab("docs");
600+await openclaw.closeTab("t1");
601+602+expect(fetchCallUrls(fetchMock).some((url) => url.includes("/json/activate/DOCS_RAW"))).toBe(
603+true,
604+);
605+expect(fetchCallUrls(fetchMock).some((url) => url.includes("/json/close/DOCS_RAW"))).toBe(true);
606+});
399607});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。