@nextclaw/ui 0.13.5 → 0.13.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/assets/api-BCDb13Uv.js +8 -0
  3. package/dist/assets/app-presenter-provider-C-QMjK7m.js +3 -0
  4. package/dist/assets/arrow-left-rIXFA2Zy.js +1 -0
  5. package/dist/assets/{channels-list-page-BnGxv6ma.js → channels-list-page-DnqwsKGe.js} +6 -6
  6. package/dist/assets/chat-page-CsQx20xL.js +105 -0
  7. package/dist/assets/{config-split-page-CWN2YG9U.js → config-split-page-BwhGqmUS.js} +1 -1
  8. package/dist/assets/confirm-dialog-DBkKcQ1q.js +5 -0
  9. package/dist/assets/{desktop-update-config-abmYzHSy.js → desktop-update-config-BE9KjiXi.js} +1 -1
  10. package/dist/assets/dist-BI0bX8jv.js +1 -0
  11. package/dist/assets/dist-C8JRt-tf.js +1 -0
  12. package/dist/assets/{doc-browser-context-Cb1sEUT0.js → doc-browser-context-VHUOIUXL.js} +1 -1
  13. package/dist/assets/doc-browser-eqdNEerh.js +1 -0
  14. package/dist/assets/doc-browser-lpF_pyx4.js +1 -0
  15. package/dist/assets/ellipsis-C9VB2CxT.js +1 -0
  16. package/dist/assets/es2015-BCw3Q7nY.js +41 -0
  17. package/dist/assets/external-link-Bp7_4s9t.js +1 -0
  18. package/dist/assets/index-BCq7kbC8.css +1 -0
  19. package/dist/assets/{index-Ccbswzt9.js → index-DTdPDqto.js} +12 -12
  20. package/dist/assets/key-round-Ccy6oND3.js +1 -0
  21. package/dist/assets/loader-circle-OpH4EErP.js +1 -0
  22. package/dist/assets/{mcp-marketplace-page-CmUKUeno.js → mcp-marketplace-page-B-GxKOqS.js} +2 -2
  23. package/dist/assets/mcp-marketplace-page-Dm-0-w51.js +1 -0
  24. package/dist/assets/{model-config-Dbo3zWDl.js → model-config-BXPnV_7n.js} +1 -1
  25. package/dist/assets/{notice-card-CFTFtJky.js → notice-card-Dsbkl7u7.js} +1 -1
  26. package/dist/assets/play-CvgYhNbo.js +1 -0
  27. package/dist/assets/plus-DHcP-K1U.js +1 -0
  28. package/dist/assets/popover-BTLZE5Iz.js +1 -0
  29. package/dist/assets/provider-scoped-model-input-DKI9KN7N.js +1 -0
  30. package/dist/assets/providers-list-Bko6B7dE.js +1 -0
  31. package/dist/assets/react-BqS8oMyM.js +8 -0
  32. package/dist/assets/refresh-cw-BIaj7txb.js +1 -0
  33. package/dist/assets/remote-DHn_b8QU.js +1 -0
  34. package/dist/assets/rotate-cw-DMqSkRJQ.js +1 -0
  35. package/dist/assets/{runtime-config-page-Dg8aEgWx.js → runtime-config-page-DX_wSK1J.js} +1 -1
  36. package/dist/assets/save-BEoORten.js +1 -0
  37. package/dist/assets/search-COFh_SFI.js +1 -0
  38. package/dist/assets/{search-config-DAibVFGR.js → search-config-DA5By-tE.js} +1 -1
  39. package/dist/assets/{secrets-config-CoJzy13_.js → secrets-config-eDxG7iN4.js} +1 -1
  40. package/dist/assets/{status-dot-D8xx5ZR8.js → status-dot-oWBjTovo.js} +1 -1
  41. package/dist/assets/{tabs-DxmND60Q.js → tabs-CGaY0BVO.js} +1 -1
  42. package/dist/assets/{tabs-custom-Cjo0bHw8.js → tabs-custom-D2fPYb4G.js} +1 -1
  43. package/dist/assets/{tag-chip-DnNWFRMu.js → tag-chip-TVkgofS1.js} +1 -1
  44. package/dist/assets/tooltip-BwHPKBv9.js +1 -0
  45. package/dist/assets/trash-2-D3psAGrz.js +1 -0
  46. package/dist/assets/x-CHAxzyp2.js +1 -0
  47. package/dist/index.html +25 -24
  48. package/package.json +8 -8
  49. package/src/features/chat/components/chat-welcome.test.tsx +15 -0
  50. package/src/features/chat/components/chat-welcome.tsx +4 -7
  51. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +180 -117
  52. package/src/shared/components/doc-browser/doc-browser.test.tsx +146 -63
  53. package/dist/assets/api-BAyyGaoz.js +0 -15
  54. package/dist/assets/app-presenter-provider-DqHHfC1K.js +0 -3
  55. package/dist/assets/arrow-left-Bn-3Pj3p.js +0 -1
  56. package/dist/assets/boxes-ClVuotcz.js +0 -1
  57. package/dist/assets/chat-page-DInyWLb4.js +0 -105
  58. package/dist/assets/confirm-dialog-CVbgKuHp.js +0 -5
  59. package/dist/assets/createLucideIcon-CjPUPAAK.js +0 -1
  60. package/dist/assets/dist-hkrb6Ynt.js +0 -41
  61. package/dist/assets/doc-browser-BlrpUKVJ.js +0 -1
  62. package/dist/assets/doc-browser-kJ2ClG0H.js +0 -1
  63. package/dist/assets/ellipsis-CdkxFkVZ.js +0 -1
  64. package/dist/assets/external-link-CT9_qFwN.js +0 -1
  65. package/dist/assets/index-BGXTR0F_.css +0 -1
  66. package/dist/assets/key-round-xJfR4iRe.js +0 -1
  67. package/dist/assets/loader-circle-BI6HJDg5.js +0 -1
  68. package/dist/assets/mcp-marketplace-page-DH11bSVx.js +0 -1
  69. package/dist/assets/play-DyBWW2j4.js +0 -1
  70. package/dist/assets/plus-BlnBmcKE.js +0 -1
  71. package/dist/assets/popover-DPqa_Mfb.js +0 -1
  72. package/dist/assets/provider-scoped-model-input-C6xL7zSJ.js +0 -1
  73. package/dist/assets/providers-list-DOj_7GL8.js +0 -1
  74. package/dist/assets/react-oAHNoVya.js +0 -1
  75. package/dist/assets/refresh-cw-o8NSmIYV.js +0 -1
  76. package/dist/assets/remote-fCE5fLsj.js +0 -1
  77. package/dist/assets/rotate-cw-DA8EqNAH.js +0 -1
  78. package/dist/assets/save-D4-g0EAq.js +0 -1
  79. package/dist/assets/search-G2cMn3M5.js +0 -1
  80. package/dist/assets/trash-2-C9tc-9sQ.js +0 -1
  81. package/dist/assets/x-DmLl2yG6.js +0 -1
  82. /package/dist/assets/{config-hints-TmNgpFHv.js → config-hints-Ceiol9x4.js} +0 -0
  83. /package/dist/assets/{provider-models-CXUgDxGR.js → provider-models-C_yOh6DE.js} +0 -0
@@ -1,8 +1,23 @@
1
- import type { PointerEvent } from 'react';
2
- import { ArrowLeft, ArrowRight, Maximize2, PanelRightOpen, Pin, PinOff, Plus, X } from 'lucide-react';
3
- import type { DocBrowserDockState, DocBrowserTab } from './doc-browser-context';
4
- import { cn } from '@/shared/lib/utils';
5
- import { t } from '@/shared/lib/i18n';
1
+ import type { ButtonHTMLAttributes, PointerEvent, ReactElement } from "react";
2
+ import {
3
+ ArrowLeft,
4
+ ArrowRight,
5
+ Maximize2,
6
+ PanelRightOpen,
7
+ Pin,
8
+ PinOff,
9
+ Plus,
10
+ X,
11
+ } from "lucide-react";
12
+ import type { DocBrowserDockState, DocBrowserTab } from "./doc-browser-context";
13
+ import { cn } from "@/shared/lib/utils";
14
+ import { t } from "@/shared/lib/i18n";
15
+ import {
16
+ Tooltip,
17
+ TooltipContent,
18
+ TooltipProvider,
19
+ TooltipTrigger,
20
+ } from "@/shared/components/ui/tooltip";
6
21
 
7
22
  type DocBrowserTabStripProps = {
8
23
  tabs: DocBrowserTab[];
@@ -23,6 +38,53 @@ type DocBrowserTabStripProps = {
23
38
  onToggleMode: () => void;
24
39
  };
25
40
 
41
+ type DocBrowserIconActionButtonProps = Omit<
42
+ ButtonHTMLAttributes<HTMLButtonElement>,
43
+ "children" | "type"
44
+ > & {
45
+ disabled?: boolean;
46
+ icon: ReactElement;
47
+ label: string;
48
+ variant?: "tab" | "toolbar";
49
+ };
50
+
51
+ function DocBrowserIconActionButton({
52
+ className,
53
+ disabled = false,
54
+ icon,
55
+ label,
56
+ variant = "toolbar",
57
+ ...buttonProps
58
+ }: DocBrowserIconActionButtonProps) {
59
+ const button = (
60
+ <button
61
+ {...buttonProps}
62
+ type="button"
63
+ disabled={disabled}
64
+ className={cn(
65
+ variant === "toolbar"
66
+ ? "rounded-md p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 disabled:cursor-not-allowed disabled:text-gray-300 disabled:opacity-60 disabled:hover:bg-transparent disabled:hover:text-gray-300"
67
+ : "rounded p-0.5 transition-colors hover:bg-black/10",
68
+ className,
69
+ )}
70
+ aria-label={label}
71
+ >
72
+ {icon}
73
+ </button>
74
+ );
75
+
76
+ return (
77
+ <Tooltip>
78
+ <TooltipTrigger asChild>
79
+ {disabled ? <span className="inline-flex">{button}</span> : button}
80
+ </TooltipTrigger>
81
+ <TooltipContent side="bottom" className="text-xs">
82
+ {label}
83
+ </TooltipContent>
84
+ </Tooltip>
85
+ );
86
+ }
87
+
26
88
  export function DocBrowserTabStrip({
27
89
  tabs,
28
90
  activeTabId,
@@ -41,123 +103,124 @@ export function DocBrowserTabStrip({
41
103
  onDragStart,
42
104
  onToggleMode,
43
105
  }: DocBrowserTabStripProps) {
106
+ const backLabel = t("docBrowserBack");
107
+ const closeLabel = t("docBrowserClose");
108
+ const closeTabLabel = t("docBrowserCloseTab");
109
+ const dockLabel = dockState?.isDocked
110
+ ? dockState.removable
111
+ ? t("sideDockUnpinCurrent")
112
+ : t("sideDockBuiltInDocked")
113
+ : t("sideDockPinCurrent");
114
+ const forwardLabel = t("docBrowserForward");
115
+ const modeLabel = isDocked
116
+ ? t("docBrowserFloatMode")
117
+ : t("docBrowserDockMode");
118
+ const newTabLabel = t("docBrowserNewTab");
119
+
44
120
  return (
45
- <div
46
- data-testid="doc-browser-tab-strip"
47
- className={cn(
48
- 'flex h-11 items-stretch gap-2 px-2.5 bg-background border-b border-[#f1e7d4] shrink-0 select-none',
49
- isFullscreen && 'h-[calc(env(safe-area-inset-top,0px)+2.75rem)] pt-[env(safe-area-inset-top,0px)]',
50
- )}
51
- onPointerDown={!isDocked && !isFullscreen ? onDragStart : undefined}
52
- >
121
+ <TooltipProvider delayDuration={250}>
53
122
  <div
54
- className="doc-browser-tab-scrollbar flex h-full min-w-0 flex-1 items-center gap-1.5 overflow-x-auto"
55
- onPointerDown={(event) => event.stopPropagation()}
123
+ data-testid="doc-browser-tab-strip"
124
+ className={cn(
125
+ "flex h-11 items-stretch gap-2 px-2.5 bg-background border-b border-[#f1e7d4] shrink-0 select-none",
126
+ isFullscreen &&
127
+ "h-[calc(env(safe-area-inset-top,0px)+2.75rem)] pt-[env(safe-area-inset-top,0px)]",
128
+ )}
129
+ onPointerDown={!isDocked && !isFullscreen ? onDragStart : undefined}
56
130
  >
57
- {tabs.map((tab) => {
58
- const isActive = tab.id === activeTabId;
59
- return (
60
- <div
61
- key={tab.id}
62
- className={cn(
63
- 'inline-flex items-center gap-1 h-7 px-1.5 rounded-lg text-xs border max-w-[220px] shrink-0 transition-colors',
64
- isActive
65
- ? 'bg-amber-50/80 border-amber-200 text-amber-900 shadow-[0_1px_2px_rgba(30,20,10,0.04)]'
66
- : 'bg-[#f9f8f5] border-[#eee3d1] text-[#78644d] hover:bg-[#fff7ea] hover:text-[#2f2212]',
67
- )}
68
- >
69
- <button
70
- type="button"
71
- onClick={() => onSetActiveTab(tab.id)}
72
- className="truncate text-left px-1"
73
- title={tab.title}
74
- >
75
- {tab.title || t('docBrowserTabUntitled')}
76
- </button>
77
- <button
78
- type="button"
79
- onClick={(event) => {
80
- event.stopPropagation();
81
- onCloseTab(tab.id);
82
- }}
83
- className="rounded p-0.5 hover:bg-black/10"
84
- aria-label={t('docBrowserCloseTab')}
85
- >
86
- <X className="w-3 h-3" />
87
- </button>
88
- </div>
89
- );
90
- })}
91
- </div>
92
- <div
93
- className="flex h-full items-center gap-1 shrink-0"
94
- data-testid="doc-browser-tab-actions"
95
- onPointerDown={(event) => event.stopPropagation()}
96
- >
97
- <button
98
- type="button"
99
- onClick={onGoBack}
100
- disabled={!canGoBack}
101
- className="rounded-md p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 disabled:cursor-not-allowed disabled:text-gray-300 disabled:opacity-60 disabled:hover:bg-transparent disabled:hover:text-gray-300"
102
- title={t('docBrowserBack')}
103
- >
104
- <ArrowLeft className="w-3.5 h-3.5" />
105
- </button>
106
- <button
107
- type="button"
108
- onClick={onGoForward}
109
- disabled={!canGoForward}
110
- className="rounded-md p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 disabled:cursor-not-allowed disabled:text-gray-300 disabled:opacity-60 disabled:hover:bg-transparent disabled:hover:text-gray-300"
111
- title={t('docBrowserForward')}
131
+ <div
132
+ className="doc-browser-tab-scrollbar flex h-full min-w-0 flex-1 items-center gap-1.5 overflow-x-auto"
133
+ onPointerDown={(event) => event.stopPropagation()}
112
134
  >
113
- <ArrowRight className="w-3.5 h-3.5" />
114
- </button>
115
- <button
116
- type="button"
117
- onClick={onOpenNewTab}
118
- className="hover:bg-gray-100 rounded-md p-1.5 text-gray-500 hover:text-gray-700 transition-colors"
119
- title={t('docBrowserNewTab')}
120
- >
121
- <Plus className="w-3.5 h-3.5" />
122
- </button>
123
- {dockState?.canDock ? (
124
- <button
125
- type="button"
126
- onClick={onToggleDock}
127
- disabled={dockState.isDocked && !dockState.removable}
128
- className="rounded-md p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 disabled:cursor-not-allowed disabled:text-gray-300 disabled:opacity-60 disabled:hover:bg-transparent disabled:hover:text-gray-300"
129
- title={
130
- dockState.isDocked
131
- ? dockState.removable ? t('sideDockUnpinCurrent') : t('sideDockBuiltInDocked')
132
- : t('sideDockPinCurrent')
133
- }
134
- >
135
- {dockState.isDocked && dockState.removable ? (
136
- <PinOff className="w-3.5 h-3.5" />
137
- ) : (
138
- <Pin className="w-3.5 h-3.5" />
139
- )}
140
- </button>
141
- ) : null}
142
- {!isFullscreen ? (
143
- <button
144
- type="button"
145
- onClick={onToggleMode}
146
- className="hover:bg-gray-100 rounded-md p-1.5 text-gray-500 hover:text-gray-700 transition-colors"
147
- title={isDocked ? t('docBrowserFloatMode') : t('docBrowserDockMode')}
148
- >
149
- {isDocked ? <Maximize2 className="w-3.5 h-3.5" /> : <PanelRightOpen className="w-3.5 h-3.5" />}
150
- </button>
151
- ) : null}
152
- <button
153
- type="button"
154
- onClick={onClose}
155
- className="hover:bg-gray-100 rounded-md p-1.5 text-gray-500 hover:text-gray-700 transition-colors"
156
- title={t('docBrowserClose')}
135
+ {tabs.map((tab) => {
136
+ const isActive = tab.id === activeTabId;
137
+ return (
138
+ <div
139
+ key={tab.id}
140
+ className={cn(
141
+ "inline-flex items-center gap-1 h-7 px-1.5 rounded-lg text-xs border max-w-[220px] shrink-0 transition-colors",
142
+ isActive
143
+ ? "bg-amber-50/80 border-amber-200 text-amber-900 shadow-[0_1px_2px_rgba(30,20,10,0.04)]"
144
+ : "bg-[#f9f8f5] border-[#eee3d1] text-[#78644d] hover:bg-[#fff7ea] hover:text-[#2f2212]",
145
+ )}
146
+ >
147
+ <button
148
+ type="button"
149
+ onClick={() => onSetActiveTab(tab.id)}
150
+ className="truncate text-left px-1"
151
+ title={tab.title}
152
+ >
153
+ {tab.title || t("docBrowserTabUntitled")}
154
+ </button>
155
+ <DocBrowserIconActionButton
156
+ icon={<X className="w-3 h-3" />}
157
+ label={closeTabLabel}
158
+ variant="tab"
159
+ onClick={(event) => {
160
+ event.stopPropagation();
161
+ onCloseTab(tab.id);
162
+ }}
163
+ />
164
+ </div>
165
+ );
166
+ })}
167
+ </div>
168
+ <div
169
+ className="flex h-full items-center gap-1 shrink-0"
170
+ data-testid="doc-browser-tab-actions"
171
+ onPointerDown={(event) => event.stopPropagation()}
157
172
  >
158
- <X className="w-3.5 h-3.5" />
159
- </button>
173
+ <DocBrowserIconActionButton
174
+ disabled={!canGoBack}
175
+ icon={<ArrowLeft className="w-3.5 h-3.5" />}
176
+ label={backLabel}
177
+ onClick={onGoBack}
178
+ />
179
+ <DocBrowserIconActionButton
180
+ disabled={!canGoForward}
181
+ icon={<ArrowRight className="w-3.5 h-3.5" />}
182
+ label={forwardLabel}
183
+ onClick={onGoForward}
184
+ />
185
+ <DocBrowserIconActionButton
186
+ icon={<Plus className="w-3.5 h-3.5" />}
187
+ label={newTabLabel}
188
+ onClick={onOpenNewTab}
189
+ />
190
+ {dockState?.canDock ? (
191
+ <DocBrowserIconActionButton
192
+ disabled={dockState.isDocked && !dockState.removable}
193
+ icon={
194
+ dockState.isDocked && dockState.removable ? (
195
+ <PinOff className="w-3.5 h-3.5" />
196
+ ) : (
197
+ <Pin className="w-3.5 h-3.5" />
198
+ )
199
+ }
200
+ label={dockLabel}
201
+ onClick={onToggleDock}
202
+ />
203
+ ) : null}
204
+ {!isFullscreen ? (
205
+ <DocBrowserIconActionButton
206
+ icon={
207
+ isDocked ? (
208
+ <Maximize2 className="w-3.5 h-3.5" />
209
+ ) : (
210
+ <PanelRightOpen className="w-3.5 h-3.5" />
211
+ )
212
+ }
213
+ label={modeLabel}
214
+ onClick={onToggleMode}
215
+ />
216
+ ) : null}
217
+ <DocBrowserIconActionButton
218
+ icon={<X className="w-3.5 h-3.5" />}
219
+ label={closeLabel}
220
+ onClick={onClose}
221
+ />
222
+ </div>
160
223
  </div>
161
- </div>
224
+ </TooltipProvider>
162
225
  );
163
226
  }
@@ -1,7 +1,11 @@
1
1
  import { fireEvent, render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
2
3
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
4
  import { DocBrowser } from "@/shared/components/doc-browser/doc-browser";
4
- import type { DocBrowserContextValue, DocBrowserTab } from "@/shared/components/doc-browser/doc-browser-context";
5
+ import type {
6
+ DocBrowserContextValue,
7
+ DocBrowserTab,
8
+ } from "@/shared/components/doc-browser/doc-browser-context";
5
9
  import { PANEL_APPS_DOC_BROWSER_RENDERERS } from "@/features/panel-apps";
6
10
 
7
11
  const { navigateMock } = vi.hoisted(() => ({
@@ -9,11 +13,13 @@ const { navigateMock } = vi.hoisted(() => ({
9
13
  }));
10
14
 
11
15
  vi.mock("react-router-dom", async () => ({
12
- ...(await vi.importActual("react-router-dom") as object),
16
+ ...((await vi.importActual("react-router-dom")) as object),
13
17
  useNavigate: () => navigateMock,
14
18
  }));
15
19
 
16
- const { docBrowserState } = vi.hoisted<{ docBrowserState: DocBrowserContextValue }>(() => ({
20
+ const { docBrowserState } = vi.hoisted<{
21
+ docBrowserState: DocBrowserContextValue;
22
+ }>(() => ({
17
23
  docBrowserState: {
18
24
  isOpen: true,
19
25
  mode: "docked" as "docked" | "floating",
@@ -29,11 +35,13 @@ const { docBrowserState } = vi.hoisted<{ docBrowserState: DocBrowserContextValue
29
35
  },
30
36
  ],
31
37
  activeTabId: "docs",
32
- activeHistory: [{
33
- kind: "docs" as const,
34
- tabId: "docs",
35
- url: "https://docs.nextclaw.io/en/guide/getting-started",
36
- }],
38
+ activeHistory: [
39
+ {
40
+ kind: "docs" as const,
41
+ tabId: "docs",
42
+ url: "https://docs.nextclaw.io/en/guide/getting-started",
43
+ },
44
+ ],
37
45
  activeHistoryIndex: 0,
38
46
  currentTab: {
39
47
  id: "docs",
@@ -99,11 +107,13 @@ describe("DocBrowser", () => {
99
107
  },
100
108
  ];
101
109
  docBrowserState.activeTabId = "docs";
102
- docBrowserState.activeHistory = [{
103
- kind: "docs" as const,
104
- tabId: "docs",
105
- url: "https://docs.nextclaw.io/en/guide/getting-started",
106
- }];
110
+ docBrowserState.activeHistory = [
111
+ {
112
+ kind: "docs" as const,
113
+ tabId: "docs",
114
+ url: "https://docs.nextclaw.io/en/guide/getting-started",
115
+ },
116
+ ];
107
117
  docBrowserState.activeHistoryIndex = 0;
108
118
  docBrowserState.currentTab = docBrowserState.tabs[0];
109
119
  Object.defineProperty(window, "innerWidth", {
@@ -133,24 +143,41 @@ describe("DocBrowser", () => {
133
143
  expect(panel.className).toContain("inset-0");
134
144
  expect(panel.className).toContain("w-screen");
135
145
  expect(
136
- screen.queryByTitle(/float/i) ?? screen.queryByTitle(/dock/i),
146
+ screen.queryByRole("button", { name: /float/i }) ??
147
+ screen.queryByRole("button", { name: /dock/i }),
137
148
  ).toBeNull();
138
149
  expect(panel.querySelector(".cursor-ew-resize")).toBeNull();
139
150
  expect(panel.querySelector(".cursor-se-resize")).toBeNull();
140
151
  });
141
152
 
142
- it("keeps browser window controls on the tab strip", () => {
143
- render(<DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS} />);
153
+ it("keeps browser window controls on the tab strip", async () => {
154
+ const user = userEvent.setup();
155
+ render(
156
+ <DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS} />,
157
+ );
144
158
 
145
159
  const tabStrip = screen.getByTestId("doc-browser-tab-strip");
146
160
  const tabActions = screen.getByTestId("doc-browser-tab-actions");
161
+ const newTabButton = screen.getByRole("button", { name: "New Tab" });
147
162
 
148
- expect(tabActions.contains(screen.getByTitle("Back"))).toBe(true);
149
- expect(tabActions.contains(screen.getByTitle("Forward"))).toBe(true);
150
- expect(tabStrip.contains(screen.getByTitle("Float Window"))).toBe(true);
151
- expect(tabStrip.contains(screen.getByTitle("Close"))).toBe(true);
152
- expect(tabActions.contains(screen.getByTitle("New Tab"))).toBe(true);
163
+ expect(
164
+ tabActions.contains(screen.getByRole("button", { name: "Back" })),
165
+ ).toBe(true);
166
+ expect(
167
+ tabActions.contains(screen.getByRole("button", { name: "Forward" })),
168
+ ).toBe(true);
169
+ expect(
170
+ tabStrip.contains(screen.getByRole("button", { name: "Float Window" })),
171
+ ).toBe(true);
172
+ expect(
173
+ tabStrip.contains(screen.getByRole("button", { name: "Close" })),
174
+ ).toBe(true);
175
+ expect(tabActions.contains(newTabButton)).toBe(true);
153
176
  expect(screen.queryByText("Embedded Browser")).toBeNull();
177
+
178
+ await user.hover(newTabButton);
179
+
180
+ expect((await screen.findAllByText("New Tab")).length).toBeGreaterThan(0);
154
181
  });
155
182
 
156
183
  it("uses active history to enable browser back and forward actions", () => {
@@ -178,10 +205,12 @@ describe("DocBrowser", () => {
178
205
  docBrowserState.activeHistoryIndex = 1;
179
206
  docBrowserState.currentTab = historyTab;
180
207
 
181
- render(<DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS} />);
208
+ render(
209
+ <DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS} />,
210
+ );
182
211
 
183
- fireEvent.click(screen.getByTitle("Back"));
184
- fireEvent.click(screen.getByTitle("Forward"));
212
+ fireEvent.click(screen.getByRole("button", { name: "Back" }));
213
+ fireEvent.click(screen.getByRole("button", { name: "Forward" }));
185
214
 
186
215
  expect(docBrowserState.goBack).toHaveBeenCalled();
187
216
  expect(docBrowserState.goForward).toHaveBeenCalled();
@@ -212,8 +241,14 @@ describe("DocBrowser", () => {
212
241
 
213
242
  const { rerender } = render(<DocBrowser />);
214
243
 
215
- expect(screen.getByTitle("Back")).toHaveProperty("disabled", false);
216
- expect(screen.getByTitle("Forward")).toHaveProperty("disabled", true);
244
+ expect(screen.getByRole("button", { name: "Back" })).toHaveProperty(
245
+ "disabled",
246
+ false,
247
+ );
248
+ expect(screen.getByRole("button", { name: "Forward" })).toHaveProperty(
249
+ "disabled",
250
+ true,
251
+ );
217
252
 
218
253
  const oldestTab: DocBrowserTab = {
219
254
  ...latestTab,
@@ -225,14 +260,20 @@ describe("DocBrowser", () => {
225
260
  docBrowserState.activeHistoryIndex = 0;
226
261
  rerender(<DocBrowser />);
227
262
 
228
- expect(screen.getByTitle("Back")).toHaveProperty("disabled", true);
229
- expect(screen.getByTitle("Forward")).toHaveProperty("disabled", false);
263
+ expect(screen.getByRole("button", { name: "Back" })).toHaveProperty(
264
+ "disabled",
265
+ true,
266
+ );
267
+ expect(screen.getByRole("button", { name: "Forward" })).toHaveProperty(
268
+ "disabled",
269
+ false,
270
+ );
230
271
  });
231
272
 
232
273
  it("opens the start page from the fixed new tab action", () => {
233
274
  render(<DocBrowser />);
234
275
 
235
- fireEvent.click(screen.getByTitle("New Tab"));
276
+ fireEvent.click(screen.getByRole("button", { name: "New Tab" }));
236
277
 
237
278
  expect(docBrowserState.openNewTab).toHaveBeenCalled();
238
279
  });
@@ -241,13 +282,21 @@ describe("DocBrowser", () => {
241
282
  const pinTab = vi.fn();
242
283
  const unpinTab = vi.fn();
243
284
 
244
- render(<DocBrowser dockControls={{
245
- getDockState: () => ({ canDock: true, isDocked: false, removable: false }),
246
- pinTab,
247
- unpinTab,
248
- }} />);
249
-
250
- fireEvent.click(screen.getByTitle("Pin to SideDock"));
285
+ render(
286
+ <DocBrowser
287
+ dockControls={{
288
+ getDockState: () => ({
289
+ canDock: true,
290
+ isDocked: false,
291
+ removable: false,
292
+ }),
293
+ pinTab,
294
+ unpinTab,
295
+ }}
296
+ />,
297
+ );
298
+
299
+ fireEvent.click(screen.getByRole("button", { name: "Pin to SideDock" }));
251
300
 
252
301
  expect(pinTab).toHaveBeenCalledWith(docBrowserState.currentTab);
253
302
  expect(unpinTab).not.toHaveBeenCalled();
@@ -257,26 +306,46 @@ describe("DocBrowser", () => {
257
306
  const pinTab = vi.fn();
258
307
  const unpinTab = vi.fn();
259
308
 
260
- render(<DocBrowser dockControls={{
261
- getDockState: () => ({ canDock: true, isDocked: true, removable: true }),
262
- pinTab,
263
- unpinTab,
264
- }} />);
265
-
266
- fireEvent.click(screen.getByTitle("Remove from SideDock"));
309
+ render(
310
+ <DocBrowser
311
+ dockControls={{
312
+ getDockState: () => ({
313
+ canDock: true,
314
+ isDocked: true,
315
+ removable: true,
316
+ }),
317
+ pinTab,
318
+ unpinTab,
319
+ }}
320
+ />,
321
+ );
322
+
323
+ fireEvent.click(
324
+ screen.getByRole("button", { name: "Remove from SideDock" }),
325
+ );
267
326
 
268
327
  expect(unpinTab).toHaveBeenCalledWith(docBrowserState.currentTab);
269
328
  expect(pinTab).not.toHaveBeenCalled();
270
329
  });
271
330
 
272
331
  it("disables dock controls for built-in shortcuts", () => {
273
- render(<DocBrowser dockControls={{
274
- getDockState: () => ({ canDock: true, isDocked: true, removable: false }),
275
- pinTab: vi.fn(),
276
- unpinTab: vi.fn(),
277
- }} />);
332
+ render(
333
+ <DocBrowser
334
+ dockControls={{
335
+ getDockState: () => ({
336
+ canDock: true,
337
+ isDocked: true,
338
+ removable: false,
339
+ }),
340
+ pinTab: vi.fn(),
341
+ unpinTab: vi.fn(),
342
+ }}
343
+ />,
344
+ );
278
345
 
279
- expect(screen.getByTitle("Built-in shortcut")).toHaveProperty("disabled", true);
346
+ expect(
347
+ screen.getByRole("button", { name: "Built-in shortcut" }),
348
+ ).toHaveProperty("disabled", true);
280
349
  });
281
350
 
282
351
  it("renders a start page for home tabs", () => {
@@ -293,7 +362,9 @@ describe("DocBrowser", () => {
293
362
  docBrowserState.activeTabId = homeTab.id;
294
363
  docBrowserState.currentTab = homeTab;
295
364
 
296
- render(<DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS} />);
365
+ render(
366
+ <DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS} />,
367
+ );
297
368
 
298
369
  expect(screen.getAllByText("Start Page").length).toBeGreaterThan(0);
299
370
  expect(screen.getByText("Apps")).toBeTruthy();
@@ -306,10 +377,14 @@ describe("DocBrowser", () => {
306
377
 
307
378
  render(<DocBrowser />);
308
379
 
309
- firePointerEvent(screen.getByTestId("doc-browser-resize-right"), "pointerdown", {
310
- clientX: 1160,
311
- pointerId: 1,
312
- });
380
+ firePointerEvent(
381
+ screen.getByTestId("doc-browser-resize-right"),
382
+ "pointerdown",
383
+ {
384
+ clientX: 1160,
385
+ pointerId: 1,
386
+ },
387
+ );
313
388
  firePointerEvent(window, "pointermove", { clientX: 1120, pointerId: 1 });
314
389
 
315
390
  const panel = screen.getByTestId("doc-browser-panel");
@@ -322,10 +397,14 @@ describe("DocBrowser", () => {
322
397
 
323
398
  render(<DocBrowser />);
324
399
 
325
- firePointerEvent(screen.getByTestId("doc-browser-resize-left"), "pointerdown", {
326
- clientX: 680,
327
- pointerId: 1,
328
- });
400
+ firePointerEvent(
401
+ screen.getByTestId("doc-browser-resize-left"),
402
+ "pointerdown",
403
+ {
404
+ clientX: 680,
405
+ pointerId: 1,
406
+ },
407
+ );
329
408
  firePointerEvent(window, "pointermove", { clientX: 620, pointerId: 1 });
330
409
 
331
410
  const panel = screen.getByTestId("doc-browser-panel");
@@ -338,11 +417,15 @@ describe("DocBrowser", () => {
338
417
 
339
418
  render(<DocBrowser />);
340
419
 
341
- firePointerEvent(screen.getByTestId("doc-browser-resize-top"), "pointerdown", {
342
- clientX: 900,
343
- clientY: 80,
344
- pointerId: 1,
345
- });
420
+ firePointerEvent(
421
+ screen.getByTestId("doc-browser-resize-top"),
422
+ "pointerdown",
423
+ {
424
+ clientX: 900,
425
+ clientY: 80,
426
+ pointerId: 1,
427
+ },
428
+ );
346
429
  firePointerEvent(window, "pointermove", {
347
430
  clientX: 900,
348
431
  clientY: 120,