@nextclaw/ui 0.15.23 → 0.15.25
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.
- package/CHANGELOG.md +38 -0
- package/dist/assets/{api-Glw2OGXK.js → api-FK7tikI1.js} +4 -4
- package/dist/assets/appearance-settings-page-BYbvl2wj.js +1 -0
- package/dist/assets/{channels-list-page-C7E4Sxwd.js → channels-list-page-CocdcIko.js} +2 -2
- package/dist/assets/{chat-page-BT6dNI-4.js → chat-page-C5E-LLVd.js} +1 -1
- package/dist/assets/config-split-page-CA_a0AJ5.js +1 -0
- package/dist/assets/confirm-dialog-CxZfenjK.js +5 -0
- package/dist/assets/desktop-update-config-BEKC8Ly9.js +1 -0
- package/dist/assets/{dist-dv_SUR54.js → dist-BH7W5yCi.js} +1 -1
- package/dist/assets/{dist-CtLyEJeO.js → dist-CaYdVVeO.js} +1 -1
- package/dist/assets/doc-browser-Cl2ldE3f.js +1 -0
- package/dist/assets/doc-browser-DXCSd63E.js +1 -0
- package/dist/assets/{doc-browser-context-BIBjkwfV.js → doc-browser-context-FDPs3gJf.js} +1 -1
- package/dist/assets/form-actions-0c0aw_bl.js +1 -0
- package/dist/assets/i18n-provider-DkITJNz8.js +1 -0
- package/dist/assets/index-CQ2LcI2p.css +1 -0
- package/dist/assets/index-CrdfvBVg.js +107 -0
- package/dist/assets/loader-circle-DHtUxCIA.js +1 -0
- package/dist/assets/mcp-marketplace-page-Bc_jI5iH.js +9 -0
- package/dist/assets/mcp-marketplace-page-CzCieD9r.js +1 -0
- package/dist/assets/model-config-page-C4jw-pcj.js +1 -0
- package/dist/assets/{navigation-link-CB0OQbnY.js → navigation-link-CVy0_kVl.js} +2 -2
- package/dist/assets/plus-CG1RwZQq.js +1 -0
- package/dist/assets/provider-scoped-model-input-CmH4BFvc.js +1 -0
- package/dist/assets/providers-config-page-CJQjwzaw.js +1 -0
- package/dist/assets/react-DjFBVA0n.js +8 -0
- package/dist/assets/{refresh-cw-Ba0LZou4.js → refresh-cw-j2bl79Ea.js} +1 -1
- package/dist/assets/remote-BIlpD3yL.js +1 -0
- package/dist/assets/rotate-cw-bKG8zH8M.js +1 -0
- package/dist/assets/runtime-config-page-B_x2Glz5.js +1 -0
- package/dist/assets/{save-DTOAvqzt.js → save-BKNrtK81.js} +1 -1
- package/dist/assets/{search-D4X5DNMe.js → search-CkOm65W_.js} +1 -1
- package/dist/assets/{search-config-page-Cr6254K5.js → search-config-page-BaIijtmO.js} +1 -1
- package/dist/assets/{secrets-config-page-W2Hx332d.js → secrets-config-page-CJjtdFAl.js} +2 -2
- package/dist/assets/security-config-DB8apj09.js +1 -0
- package/dist/assets/{select-CDv0g2Pj.js → select-Ea7ffKCE.js} +2 -2
- package/dist/assets/{settings-2-CoxzNABp.js → settings-2-CtyN3m_V.js} +1 -1
- package/dist/assets/skeleton-Bt6zxcAB.js +1 -0
- package/dist/assets/{tag-chip-CjkZEoS2.js → tag-chip-C9l4xsKp.js} +1 -1
- package/dist/assets/x-CowtCu9Z.js +1 -0
- package/dist/index.html +20 -20
- package/package.json +8 -8
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +30 -1
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +1 -1
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-provider-model-catalog.test.tsx +54 -2
- package/src/features/chat/features/conversation/hooks/use-chat-composer-reference-intent.ts +10 -1
- package/src/features/chat/features/conversation/hooks/use-session-provider-model-catalog.ts +76 -1
- package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +6 -1
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +29 -0
- package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +12 -3
- package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +5 -0
- package/src/features/chat/features/input/utils/chat-conversation-excerpt-token.utils.ts +59 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +42 -16
- package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +50 -14
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +40 -0
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +5 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +40 -3
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.selection.test.tsx +66 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +13 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +573 -90
- package/src/features/chat/features/workspace/components/__tests__/workspace-text-selection-menu.test.tsx +11 -4
- package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +389 -288
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +54 -35
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +79 -148
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +149 -94
- package/src/features/chat/features/workspace/components/project-files/__tests__/workspace-directory-entry-menu.test.tsx +50 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-directory-entry-menu.tsx +209 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-directory-tree.tsx +411 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-new-folder-tree-item.tsx +112 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-project-files-toolbar.tsx +64 -0
- package/src/features/chat/features/workspace/components/workspace-text-selection-menu.tsx +17 -195
- package/src/features/chat/features/workspace/hooks/use-workspace-explorer-layout.ts +88 -0
- package/src/features/chat/features/workspace/hooks/use-workspace-file-actions.ts +335 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-path-mutation.utils.ts +58 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +23 -22
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -4
- package/src/features/chat/managers/__tests__/chat-composer-intent.manager.test.ts +94 -45
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +103 -99
- package/src/features/chat/managers/chat-composer-intent.manager.ts +83 -34
- package/src/features/chat/managers/chat-thread.manager.ts +132 -128
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +20 -40
- package/src/features/chat/stores/chat-thread.store.ts +47 -74
- package/src/features/chat/types/chat-input-bar.types.ts +3 -0
- package/src/features/side-dock/components/__tests__/side-dock.test.tsx +1 -0
- package/src/features/side-dock/configs/side-dock-built-in-items.config.ts +0 -9
- package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +35 -38
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +25 -19
- package/src/shared/components/ui/context-menu/__tests__/context-menu.test.tsx +60 -34
- package/src/shared/components/ui/context-menu/context-menu.tsx +95 -68
- package/src/shared/lib/api/managers/client.manager.ts +31 -17
- package/src/shared/lib/api/server-path/server-path.types.ts +66 -0
- package/src/shared/lib/api/types.ts +145 -116
- package/src/shared/lib/api/utils/server-path.utils.ts +35 -11
- package/src/shared/lib/i18n/locales/en-US/chat.json +49 -1
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +49 -1
- package/dist/assets/appearance-settings-page-Bl_W_9m7.js +0 -1
- package/dist/assets/config-split-page-D5BgtKrm.js +0 -1
- package/dist/assets/confirm-dialog-Bf907Azu.js +0 -5
- package/dist/assets/desktop-update-config-BwuyBPJb.js +0 -1
- package/dist/assets/doc-browser-C23NDGh5.js +0 -1
- package/dist/assets/doc-browser-DlOOhBxI.js +0 -1
- package/dist/assets/form-actions-DAzi68Cn.js +0 -1
- package/dist/assets/index-BLNg2yg0.css +0 -1
- package/dist/assets/index-D3EKjEyK.js +0 -107
- package/dist/assets/loader-circle-BSg5u0r4.js +0 -1
- package/dist/assets/mcp-marketplace-page-ByhvQQj5.js +0 -9
- package/dist/assets/mcp-marketplace-page-CytXwnUs.js +0 -1
- package/dist/assets/model-config-page-bgEx3MjQ.js +0 -1
- package/dist/assets/plus-CNjWdLhZ.js +0 -1
- package/dist/assets/provider-scoped-model-input-8F6JMd2n.js +0 -1
- package/dist/assets/providers-config-page-CxpTjR81.js +0 -1
- package/dist/assets/react-Bk2ydNTe.js +0 -8
- package/dist/assets/remote-Dv38PuMV.js +0 -1
- package/dist/assets/rotate-cw-IkYVk9JX.js +0 -1
- package/dist/assets/runtime-config-page-C0ByyUVr.js +0 -1
- package/dist/assets/security-config-1E8OF4kR.js +0 -1
- package/dist/assets/skeleton-DW018fJv.js +0 -1
- package/dist/assets/use-confirm-dialog-CavtY1vJ.js +0 -1
- package/dist/assets/x-PTXYTEwz.js +0 -1
|
@@ -1,83 +1,200 @@
|
|
|
1
|
-
import { fireEvent, render, screen } from
|
|
2
|
-
import userEvent from
|
|
3
|
-
import { expect, it, vi } from
|
|
4
|
-
import { ChatSessionWorkspacePanelContent } from
|
|
5
|
-
import type { ResolvedChildSessionTab } from
|
|
1
|
+
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { beforeEach, expect, it, vi } from 'vitest';
|
|
4
|
+
import { ChatSessionWorkspacePanelContent } from '@/features/chat/features/workspace/components/chat-session-workspace-panel-content';
|
|
5
|
+
import type { ResolvedChildSessionTab } from '@/features/chat/features/ncp/hooks/use-ncp-child-session-tabs-view';
|
|
6
|
+
import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
|
|
7
|
+
import { CHAT_WORKSPACE_EXPLORER_DEFAULT_WIDTH } from '@/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils';
|
|
6
8
|
|
|
7
9
|
const mocks = vi.hoisted(() => ({
|
|
10
|
+
browseEntries: [
|
|
11
|
+
{
|
|
12
|
+
name: 'src',
|
|
13
|
+
path: '/Users/peiwang/Projects/nextbot/src',
|
|
14
|
+
kind: 'directory' as const,
|
|
15
|
+
hidden: false,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'package.json',
|
|
19
|
+
path: '/private/resolved-worktree/package.json',
|
|
20
|
+
kind: 'file' as const,
|
|
21
|
+
hidden: false,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
8
24
|
materializeSideChatDraft: vi.fn(),
|
|
25
|
+
createServerPathDirectory: vi.fn(),
|
|
26
|
+
createServerPathFile: vi.fn(),
|
|
27
|
+
deleteServerPathEntry: vi.fn(),
|
|
28
|
+
renameServerPathEntry: vi.fn(),
|
|
29
|
+
uploadServerPathFiles: vi.fn(),
|
|
30
|
+
refetchBrowse: vi.fn(),
|
|
31
|
+
requestDirectoryReference: vi.fn(),
|
|
9
32
|
requestFileReference: vi.fn(),
|
|
10
33
|
openChildSessions: vi.fn(),
|
|
11
34
|
openFilePreview: vi.fn(),
|
|
12
35
|
openProjectFiles: vi.fn(),
|
|
13
36
|
openSessionCronPanel: vi.fn(),
|
|
37
|
+
removeWorkspacePath: vi.fn(),
|
|
38
|
+
renameWorkspacePath: vi.fn(),
|
|
14
39
|
selectChildSessionDetail: vi.fn(),
|
|
40
|
+
setWorkspaceExplorerWidth: vi.fn(),
|
|
41
|
+
scrollIntoView: vi.fn(),
|
|
15
42
|
}));
|
|
16
43
|
|
|
17
|
-
|
|
44
|
+
function firePointerEvent(
|
|
45
|
+
target: Window | Document | Node | Element,
|
|
46
|
+
type: string,
|
|
47
|
+
point: { clientX: number; pointerId?: number },
|
|
48
|
+
) {
|
|
49
|
+
const event = new Event(type, { bubbles: true });
|
|
50
|
+
Object.defineProperties(event, {
|
|
51
|
+
clientX: { value: point.clientX },
|
|
52
|
+
pointerId: { value: point.pointerId ?? 1 },
|
|
53
|
+
});
|
|
54
|
+
fireEvent(target, event);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
vi.mock('@/features/chat/components/providers/chat-presenter.provider', () => ({
|
|
18
58
|
usePresenter: () => ({
|
|
19
59
|
chatThreadManager: {
|
|
20
60
|
...mocks,
|
|
21
61
|
},
|
|
22
62
|
chatComposerIntentManager: {
|
|
63
|
+
requestDirectoryReference: mocks.requestDirectoryReference,
|
|
23
64
|
requestFileReference: mocks.requestFileReference,
|
|
24
65
|
},
|
|
25
66
|
}),
|
|
26
67
|
}));
|
|
27
68
|
|
|
28
|
-
vi.mock(
|
|
69
|
+
vi.mock('@/shared/lib/api', async (importOriginal) => ({
|
|
70
|
+
...(await importOriginal()),
|
|
71
|
+
createServerPathDirectory: mocks.createServerPathDirectory,
|
|
72
|
+
createServerPathFile: mocks.createServerPathFile,
|
|
73
|
+
deleteServerPathEntry: mocks.deleteServerPathEntry,
|
|
74
|
+
renameServerPathEntry: mocks.renameServerPathEntry,
|
|
75
|
+
uploadServerPathFiles: mocks.uploadServerPathFiles,
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
vi.mock('@/features/chat/features/conversation/components/session-conversation-area', () => ({
|
|
29
79
|
SessionConversationArea: () => <div data-testid="session-conversation-area" />,
|
|
30
80
|
}));
|
|
31
81
|
|
|
32
|
-
vi.mock(
|
|
33
|
-
useServerPathBrowse: () => ({
|
|
82
|
+
vi.mock('@/shared/hooks/use-server-path-browse', () => ({
|
|
83
|
+
useServerPathBrowse: ({ path }: { path?: string | null }) => ({
|
|
34
84
|
isLoading: false,
|
|
35
85
|
error: null,
|
|
36
|
-
data:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
path: "/Users/peiwang/Projects/nextbot/src",
|
|
45
|
-
kind: "directory",
|
|
46
|
-
hidden: false,
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
name: "package.json",
|
|
50
|
-
path: "/private/resolved-worktree/package.json",
|
|
51
|
-
kind: "file",
|
|
52
|
-
hidden: false,
|
|
86
|
+
data: path?.endsWith('package.json')
|
|
87
|
+
? undefined
|
|
88
|
+
: {
|
|
89
|
+
currentPath: '/Users/peiwang/Projects/nextbot',
|
|
90
|
+
parentPath: '/Users/peiwang/Projects',
|
|
91
|
+
homePath: '/Users/peiwang',
|
|
92
|
+
breadcrumbs: [],
|
|
93
|
+
entries: mocks.browseEntries,
|
|
53
94
|
},
|
|
54
|
-
|
|
95
|
+
refetch: mocks.refetchBrowse,
|
|
96
|
+
}),
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
vi.mock('@/shared/hooks/use-server-path-read', () => ({
|
|
100
|
+
useServerPathRead: ({ path }: { path: string }) => ({
|
|
101
|
+
isLoading: false,
|
|
102
|
+
error: null,
|
|
103
|
+
data: {
|
|
104
|
+
kind: 'text',
|
|
105
|
+
languageHint: 'json',
|
|
106
|
+
resolvedPath: path,
|
|
107
|
+
startLine: 1,
|
|
108
|
+
text: '{}',
|
|
109
|
+
truncated: false,
|
|
55
110
|
},
|
|
56
111
|
}),
|
|
57
112
|
}));
|
|
58
113
|
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
vi.clearAllMocks();
|
|
116
|
+
HTMLElement.prototype.getBoundingClientRect = () => ({
|
|
117
|
+
bottom: 600,
|
|
118
|
+
height: 600,
|
|
119
|
+
left: 0,
|
|
120
|
+
right: 800,
|
|
121
|
+
top: 0,
|
|
122
|
+
width: 800,
|
|
123
|
+
x: 0,
|
|
124
|
+
y: 0,
|
|
125
|
+
toJSON: () => ({}),
|
|
126
|
+
});
|
|
127
|
+
mocks.browseEntries.splice(
|
|
128
|
+
0,
|
|
129
|
+
mocks.browseEntries.length,
|
|
130
|
+
{
|
|
131
|
+
name: 'src',
|
|
132
|
+
path: '/Users/peiwang/Projects/nextbot/src',
|
|
133
|
+
kind: 'directory',
|
|
134
|
+
hidden: false,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'package.json',
|
|
138
|
+
path: '/private/resolved-worktree/package.json',
|
|
139
|
+
kind: 'file',
|
|
140
|
+
hidden: false,
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
HTMLElement.prototype.scrollIntoView = mocks.scrollIntoView;
|
|
144
|
+
mocks.createServerPathDirectory.mockResolvedValue({
|
|
145
|
+
path: '/Users/peiwang/Projects/nextbot/new-folder',
|
|
146
|
+
});
|
|
147
|
+
mocks.createServerPathFile.mockResolvedValue({
|
|
148
|
+
path: '/Users/peiwang/Projects/nextbot/new-file.md',
|
|
149
|
+
});
|
|
150
|
+
mocks.renameServerPathEntry.mockResolvedValue({
|
|
151
|
+
path: '/Users/peiwang/Projects/nextbot/renamed.md',
|
|
152
|
+
oldPath: '/Users/peiwang/Projects/nextbot/package.json',
|
|
153
|
+
name: 'renamed.md',
|
|
154
|
+
kind: 'file',
|
|
155
|
+
});
|
|
156
|
+
mocks.deleteServerPathEntry.mockResolvedValue({
|
|
157
|
+
path: '/private/resolved-worktree/package.json',
|
|
158
|
+
kind: 'file',
|
|
159
|
+
});
|
|
160
|
+
mocks.uploadServerPathFiles.mockResolvedValue({
|
|
161
|
+
files: [
|
|
162
|
+
{
|
|
163
|
+
name: 'brief.md',
|
|
164
|
+
path: '/Users/peiwang/Projects/nextbot/brief.md',
|
|
165
|
+
sizeBytes: 5,
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
overwritten: false,
|
|
169
|
+
});
|
|
170
|
+
mocks.refetchBrowse.mockResolvedValue(undefined);
|
|
171
|
+
useChatThreadStore.getState().setSnapshot({
|
|
172
|
+
workspaceExplorerWidth: CHAT_WORKSPACE_EXPLORER_DEFAULT_WIDTH,
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
59
176
|
function createChildTab(): ResolvedChildSessionTab {
|
|
60
177
|
return {
|
|
61
|
-
sessionKey:
|
|
62
|
-
parentSessionKey:
|
|
63
|
-
title:
|
|
64
|
-
agentId:
|
|
178
|
+
sessionKey: 'child-1',
|
|
179
|
+
parentSessionKey: 'parent-1',
|
|
180
|
+
title: 'Child title',
|
|
181
|
+
agentId: 'agent-1',
|
|
65
182
|
updatedAt: null,
|
|
66
183
|
lastMessageAt: null,
|
|
67
184
|
readAt: null,
|
|
68
185
|
runStatus: undefined,
|
|
69
|
-
sessionTypeLabel:
|
|
70
|
-
preferredModel:
|
|
71
|
-
projectName:
|
|
72
|
-
projectRoot:
|
|
186
|
+
sessionTypeLabel: '原生',
|
|
187
|
+
preferredModel: 'minimax/MiniMax-M3',
|
|
188
|
+
projectName: 'nextbot',
|
|
189
|
+
projectRoot: '/Users/peiwang/Projects/nextbot',
|
|
73
190
|
};
|
|
74
191
|
}
|
|
75
192
|
|
|
76
|
-
it(
|
|
193
|
+
it('shows compact child session metadata without repeating the tab title', () => {
|
|
77
194
|
render(
|
|
78
195
|
<ChatSessionWorkspacePanelContent
|
|
79
196
|
activeSelection={{
|
|
80
|
-
kind:
|
|
197
|
+
kind: 'child-session',
|
|
81
198
|
tab: createChildTab(),
|
|
82
199
|
}}
|
|
83
200
|
childSessionTabs={[createChildTab()]}
|
|
@@ -89,20 +206,20 @@ it("shows compact child session metadata without repeating the tab title", () =>
|
|
|
89
206
|
/>,
|
|
90
207
|
);
|
|
91
208
|
|
|
92
|
-
expect(screen.queryByText(
|
|
93
|
-
expect(screen.getByText(
|
|
94
|
-
expect(screen.getByText(
|
|
95
|
-
expect(screen.getByText(
|
|
96
|
-
expect(screen.getByTitle(
|
|
209
|
+
expect(screen.queryByText('Child title')).toBeNull();
|
|
210
|
+
expect(screen.getByText('原生')).toBeTruthy();
|
|
211
|
+
expect(screen.getByText('minimax/MiniMax-M3')).toBeTruthy();
|
|
212
|
+
expect(screen.getByText('nextbot')).toBeTruthy();
|
|
213
|
+
expect(screen.getByTitle('/Users/peiwang/Projects/nextbot')).toBeTruthy();
|
|
97
214
|
});
|
|
98
215
|
|
|
99
|
-
it(
|
|
216
|
+
it('shows all session workspace entries in the overview', async () => {
|
|
100
217
|
const user = userEvent.setup();
|
|
101
218
|
const childTab = createChildTab();
|
|
102
219
|
|
|
103
220
|
render(
|
|
104
221
|
<ChatSessionWorkspacePanelContent
|
|
105
|
-
activeSelection={{ kind:
|
|
222
|
+
activeSelection={{ kind: 'overview' }}
|
|
106
223
|
childSessionTabs={[childTab]}
|
|
107
224
|
filePreviewRefreshVersion={0}
|
|
108
225
|
sessionKey="parent-1"
|
|
@@ -112,11 +229,11 @@ it("shows all session workspace entries in the overview", async () => {
|
|
|
112
229
|
/>,
|
|
113
230
|
);
|
|
114
231
|
|
|
115
|
-
expect(screen.getByText(
|
|
116
|
-
const childSessionsButton = screen.getByRole(
|
|
232
|
+
expect(screen.getByText('Overview')).toBeTruthy();
|
|
233
|
+
const childSessionsButton = screen.getByRole('button', {
|
|
117
234
|
name: /Child sessions/,
|
|
118
235
|
});
|
|
119
|
-
const cronJobsButton = screen.getByRole(
|
|
236
|
+
const cronJobsButton = screen.getByRole('button', {
|
|
120
237
|
name: /Scheduled tasks/,
|
|
121
238
|
});
|
|
122
239
|
|
|
@@ -125,17 +242,17 @@ it("shows all session workspace entries in the overview", async () => {
|
|
|
125
242
|
|
|
126
243
|
await user.click(childSessionsButton);
|
|
127
244
|
await user.click(cronJobsButton);
|
|
128
|
-
await user.click(screen.getByRole(
|
|
245
|
+
await user.click(screen.getByRole('button', { name: /Project files/ }));
|
|
129
246
|
|
|
130
|
-
expect(mocks.openChildSessions).toHaveBeenCalledWith(
|
|
131
|
-
expect(mocks.openSessionCronPanel).toHaveBeenCalledWith(
|
|
132
|
-
expect(mocks.openProjectFiles).toHaveBeenCalledWith(
|
|
247
|
+
expect(mocks.openChildSessions).toHaveBeenCalledWith('parent-1');
|
|
248
|
+
expect(mocks.openSessionCronPanel).toHaveBeenCalledWith('parent-1');
|
|
249
|
+
expect(mocks.openProjectFiles).toHaveBeenCalledWith('parent-1');
|
|
133
250
|
});
|
|
134
251
|
|
|
135
|
-
it(
|
|
252
|
+
it('shows an empty child sessions page instead of disabling the entry', () => {
|
|
136
253
|
render(
|
|
137
254
|
<ChatSessionWorkspacePanelContent
|
|
138
|
-
activeSelection={{ kind:
|
|
255
|
+
activeSelection={{ kind: 'child-sessions' }}
|
|
139
256
|
childSessionTabs={[]}
|
|
140
257
|
filePreviewRefreshVersion={0}
|
|
141
258
|
sessionKey="parent-1"
|
|
@@ -145,13 +262,13 @@ it("shows an empty child sessions page instead of disabling the entry", () => {
|
|
|
145
262
|
/>,
|
|
146
263
|
);
|
|
147
264
|
|
|
148
|
-
expect(screen.getByText(
|
|
265
|
+
expect(screen.getByText('No child sessions yet.')).toBeTruthy();
|
|
149
266
|
});
|
|
150
267
|
|
|
151
|
-
it(
|
|
268
|
+
it('shows the selected session project as a hierarchical file tree', () => {
|
|
152
269
|
render(
|
|
153
270
|
<ChatSessionWorkspacePanelContent
|
|
154
|
-
activeSelection={{ kind:
|
|
271
|
+
activeSelection={{ kind: 'project-files' }}
|
|
155
272
|
childSessionTabs={[]}
|
|
156
273
|
filePreviewRefreshVersion={0}
|
|
157
274
|
sessionKey="parent-1"
|
|
@@ -161,20 +278,18 @@ it("shows the selected session project as a hierarchical file tree", () => {
|
|
|
161
278
|
/>,
|
|
162
279
|
);
|
|
163
280
|
|
|
164
|
-
expect(screen.getByRole(
|
|
165
|
-
expect(screen.getByText(
|
|
166
|
-
expect(
|
|
167
|
-
screen.getByRole("treeitem", { name: "Open directory: src" }),
|
|
168
|
-
).toBeTruthy();
|
|
281
|
+
expect(screen.getByRole('tree', { name: 'Project files' })).toBeTruthy();
|
|
282
|
+
expect(screen.getByText('nextbot')).toBeTruthy();
|
|
283
|
+
expect(screen.getByRole('treeitem', { name: 'Open directory: src' })).toBeTruthy();
|
|
169
284
|
expect(document.querySelector('[data-file-type-icon="npm"]')).toBeTruthy();
|
|
170
285
|
});
|
|
171
286
|
|
|
172
|
-
it(
|
|
287
|
+
it('adds a project file to the targeted chat from its context menu', async () => {
|
|
173
288
|
const user = userEvent.setup();
|
|
174
289
|
mocks.requestFileReference.mockReset();
|
|
175
290
|
render(
|
|
176
291
|
<ChatSessionWorkspacePanelContent
|
|
177
|
-
activeSelection={{ kind:
|
|
292
|
+
activeSelection={{ kind: 'project-files' }}
|
|
178
293
|
childSessionTabs={[]}
|
|
179
294
|
filePreviewRefreshVersion={0}
|
|
180
295
|
sessionKey={null}
|
|
@@ -184,53 +299,421 @@ it("adds a project file to the targeted chat from its context menu", async () =>
|
|
|
184
299
|
/>,
|
|
185
300
|
);
|
|
186
301
|
|
|
187
|
-
fireEvent.contextMenu(
|
|
188
|
-
screen.getByRole("button", { name: "package.json" }),
|
|
189
|
-
);
|
|
302
|
+
fireEvent.contextMenu(screen.getByRole('button', { name: 'package.json' }));
|
|
190
303
|
|
|
191
|
-
expect(screen.getByRole(
|
|
192
|
-
expect(screen.getByRole(
|
|
193
|
-
await user.click(screen.getByRole(
|
|
304
|
+
expect(screen.getByRole('menuitem', { name: 'Copy path' })).toBeTruthy();
|
|
305
|
+
expect(screen.getByRole('menuitem', { name: 'Copy relative path' })).toBeTruthy();
|
|
306
|
+
await user.click(screen.getByRole('menuitem', { name: 'Add to chat' }));
|
|
194
307
|
|
|
195
308
|
expect(mocks.requestFileReference).toHaveBeenCalledWith({
|
|
196
309
|
targetSessionKey: null,
|
|
197
|
-
tokenKey:
|
|
198
|
-
label:
|
|
310
|
+
tokenKey: 'package.json',
|
|
311
|
+
label: 'package.json',
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('adds a project folder to chat with the directory reference protocol', async () => {
|
|
316
|
+
const user = userEvent.setup();
|
|
317
|
+
render(
|
|
318
|
+
<ChatSessionWorkspacePanelContent
|
|
319
|
+
activeSelection={{ kind: 'project-files' }}
|
|
320
|
+
childSessionTabs={[]}
|
|
321
|
+
filePreviewRefreshVersion={0}
|
|
322
|
+
sessionKey="parent-1"
|
|
323
|
+
sessionCronJobs={[]}
|
|
324
|
+
sessionProjectRoot="/Users/peiwang/Projects/nextbot"
|
|
325
|
+
sessionWorkingDir="/Users/peiwang/Projects/nextbot"
|
|
326
|
+
/>,
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
fireEvent.contextMenu(screen.getByRole('button', { name: 'src' }));
|
|
330
|
+
await user.click(screen.getByRole('menuitem', { name: 'Add to chat' }));
|
|
331
|
+
|
|
332
|
+
expect(mocks.requestDirectoryReference).toHaveBeenCalledWith({
|
|
333
|
+
targetSessionKey: 'parent-1',
|
|
334
|
+
tokenKey: 'src',
|
|
335
|
+
label: 'src',
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('keeps the five Explorer actions distinct and exposes upload from the root menu', async () => {
|
|
340
|
+
const user = userEvent.setup();
|
|
341
|
+
render(
|
|
342
|
+
<ChatSessionWorkspacePanelContent
|
|
343
|
+
activeSelection={{ kind: 'project-files' }}
|
|
344
|
+
childSessionTabs={[]}
|
|
345
|
+
filePreviewRefreshVersion={0}
|
|
346
|
+
sessionKey="parent-1"
|
|
347
|
+
sessionCronJobs={[]}
|
|
348
|
+
sessionProjectRoot="/Users/peiwang/Projects/nextbot"
|
|
349
|
+
sessionWorkingDir="/Users/peiwang/Projects/nextbot"
|
|
350
|
+
/>,
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
for (const name of ['New file', 'New folder', 'Refresh project files', 'Collapse all', 'More project file actions']) {
|
|
354
|
+
expect(screen.getByRole('button', { name })).toBeTruthy();
|
|
355
|
+
}
|
|
356
|
+
expect(screen.getByRole('button', { name: 'More actions for package.json' })).toBeTruthy();
|
|
357
|
+
|
|
358
|
+
await user.click(screen.getByRole('button', { name: 'More project file actions' }));
|
|
359
|
+
expect(screen.getByRole('menuitem', { name: 'Upload files here' })).toBeTruthy();
|
|
360
|
+
await user.keyboard('{Escape}');
|
|
361
|
+
|
|
362
|
+
fireEvent.contextMenu(screen.getByRole('button', { name: 'package.json' }));
|
|
363
|
+
const fileMenu = screen.getByRole('menu', {
|
|
364
|
+
name: 'Project file actions',
|
|
365
|
+
});
|
|
366
|
+
for (const name of ['Open', 'Add to chat', 'Download', 'Copy path', 'Copy relative path', 'Rename', 'Delete']) {
|
|
367
|
+
expect(within(fileMenu).getByRole('menuitem', { name })).toBeTruthy();
|
|
368
|
+
}
|
|
369
|
+
expect(within(fileMenu).getAllByRole('group')).toHaveLength(5);
|
|
370
|
+
const download = screen.getByRole('menuitem', { name: 'Download' });
|
|
371
|
+
expect(download.tagName).toBe('A');
|
|
372
|
+
expect(download.getAttribute('download')).toBe('package.json');
|
|
373
|
+
expect(download.getAttribute('href')).toContain(
|
|
374
|
+
'/api/server-paths/content?path=%2Fprivate%2Fresolved-worktree%2Fpackage.json',
|
|
375
|
+
);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('creates a project file inline from the distinct toolbar action', async () => {
|
|
379
|
+
const user = userEvent.setup();
|
|
380
|
+
render(
|
|
381
|
+
<ChatSessionWorkspacePanelContent
|
|
382
|
+
activeSelection={{ kind: 'project-files' }}
|
|
383
|
+
childSessionTabs={[]}
|
|
384
|
+
filePreviewRefreshVersion={0}
|
|
385
|
+
sessionKey="parent-1"
|
|
386
|
+
sessionCronJobs={[]}
|
|
387
|
+
sessionProjectRoot="/Users/peiwang/Projects/nextbot"
|
|
388
|
+
sessionWorkingDir="/Users/peiwang/Projects/nextbot"
|
|
389
|
+
/>,
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
await user.click(screen.getByRole('button', { name: 'New file' }));
|
|
393
|
+
const input = screen.getByRole('textbox', { name: 'File name' });
|
|
394
|
+
await user.type(input, 'notes.md');
|
|
395
|
+
await user.click(screen.getByRole('button', { name: 'Create' }));
|
|
396
|
+
|
|
397
|
+
await waitFor(() => {
|
|
398
|
+
expect(mocks.createServerPathFile).toHaveBeenCalledWith({
|
|
399
|
+
basePath: '/Users/peiwang/Projects/nextbot',
|
|
400
|
+
parentPath: '/Users/peiwang/Projects/nextbot',
|
|
401
|
+
name: 'notes.md',
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('renames a project file inline and synchronizes open workspace tabs', async () => {
|
|
407
|
+
const user = userEvent.setup();
|
|
408
|
+
render(
|
|
409
|
+
<ChatSessionWorkspacePanelContent
|
|
410
|
+
activeSelection={{ kind: 'project-files' }}
|
|
411
|
+
childSessionTabs={[]}
|
|
412
|
+
filePreviewRefreshVersion={0}
|
|
413
|
+
sessionKey="parent-1"
|
|
414
|
+
sessionCronJobs={[]}
|
|
415
|
+
sessionProjectRoot="/Users/peiwang/Projects/nextbot"
|
|
416
|
+
sessionWorkingDir="/Users/peiwang/Projects/nextbot"
|
|
417
|
+
/>,
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
fireEvent.contextMenu(screen.getByRole('button', { name: 'package.json' }));
|
|
421
|
+
await user.click(screen.getByRole('menuitem', { name: 'Rename' }));
|
|
422
|
+
const input = screen.getByRole('textbox', { name: 'New name' });
|
|
423
|
+
await user.clear(input);
|
|
424
|
+
await user.type(input, 'renamed.md');
|
|
425
|
+
await user.click(screen.getByRole('button', { name: 'Rename' }));
|
|
426
|
+
|
|
427
|
+
await waitFor(() => {
|
|
428
|
+
expect(mocks.renameServerPathEntry).toHaveBeenCalledWith({
|
|
429
|
+
basePath: '/Users/peiwang/Projects/nextbot',
|
|
430
|
+
path: '/private/resolved-worktree/package.json',
|
|
431
|
+
name: 'renamed.md',
|
|
432
|
+
});
|
|
433
|
+
expect(mocks.renameWorkspacePath).toHaveBeenCalledWith({
|
|
434
|
+
previousPath: '/Users/peiwang/Projects/nextbot/package.json',
|
|
435
|
+
path: '/Users/peiwang/Projects/nextbot/renamed.md',
|
|
436
|
+
label: 'renamed.md',
|
|
437
|
+
});
|
|
199
438
|
});
|
|
200
439
|
});
|
|
201
440
|
|
|
202
|
-
it(
|
|
441
|
+
it('creates a project folder from the root toolbar and refreshes the tree', async () => {
|
|
442
|
+
const user = userEvent.setup();
|
|
443
|
+
mocks.createServerPathDirectory.mockImplementationOnce(async () => {
|
|
444
|
+
const path = '/Users/peiwang/Projects/nextbot/research';
|
|
445
|
+
mocks.browseEntries.push({
|
|
446
|
+
name: 'research',
|
|
447
|
+
path,
|
|
448
|
+
kind: 'directory',
|
|
449
|
+
hidden: false,
|
|
450
|
+
});
|
|
451
|
+
return { path };
|
|
452
|
+
});
|
|
453
|
+
render(
|
|
454
|
+
<ChatSessionWorkspacePanelContent
|
|
455
|
+
activeSelection={{ kind: 'project-files' }}
|
|
456
|
+
childSessionTabs={[]}
|
|
457
|
+
filePreviewRefreshVersion={0}
|
|
458
|
+
sessionKey="parent-1"
|
|
459
|
+
sessionCronJobs={[]}
|
|
460
|
+
sessionProjectRoot="/Users/peiwang/Projects/nextbot"
|
|
461
|
+
sessionWorkingDir="/Users/peiwang/Projects/nextbot"
|
|
462
|
+
/>,
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
await user.click(screen.getByRole('button', { name: 'New folder' }));
|
|
466
|
+
expect(document.activeElement).toBe(screen.getByRole('textbox', { name: 'Folder name' }));
|
|
467
|
+
expect(mocks.scrollIntoView).toHaveBeenCalledWith({ block: 'nearest' });
|
|
468
|
+
await user.type(screen.getByRole('textbox', { name: 'Folder name' }), 'research');
|
|
469
|
+
await user.click(screen.getByRole('button', { name: 'Create' }));
|
|
470
|
+
|
|
471
|
+
await waitFor(() => {
|
|
472
|
+
expect(mocks.createServerPathDirectory).toHaveBeenCalledWith({
|
|
473
|
+
basePath: '/Users/peiwang/Projects/nextbot',
|
|
474
|
+
parentPath: '/Users/peiwang/Projects/nextbot',
|
|
475
|
+
name: 'research',
|
|
476
|
+
});
|
|
477
|
+
expect(mocks.refetchBrowse).toHaveBeenCalled();
|
|
478
|
+
expect(document.activeElement).toBe(screen.getByRole('button', { name: 'research' }));
|
|
479
|
+
expect(mocks.scrollIntoView).toHaveBeenCalledTimes(2);
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('uses the selected directory as the toolbar target and creates inline', async () => {
|
|
484
|
+
const user = userEvent.setup();
|
|
485
|
+
render(
|
|
486
|
+
<ChatSessionWorkspacePanelContent
|
|
487
|
+
activeSelection={{ kind: 'project-files' }}
|
|
488
|
+
childSessionTabs={[]}
|
|
489
|
+
filePreviewRefreshVersion={0}
|
|
490
|
+
sessionKey="parent-1"
|
|
491
|
+
sessionCronJobs={[]}
|
|
492
|
+
sessionProjectRoot="/Users/peiwang/Projects/nextbot"
|
|
493
|
+
sessionWorkingDir="/Users/peiwang/Projects/nextbot"
|
|
494
|
+
/>,
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
await user.click(screen.getByRole('button', { name: 'src' }));
|
|
498
|
+
await user.click(screen.getByRole('button', { name: 'New folder' }));
|
|
499
|
+
|
|
500
|
+
const input = screen.getByRole('textbox', { name: 'Folder name' });
|
|
501
|
+
expect(input.closest('[role="group"]')?.parentElement?.getAttribute('aria-label')).toBe('Open directory: src');
|
|
502
|
+
await user.type(input, 'nested');
|
|
503
|
+
await user.click(screen.getByRole('button', { name: 'Create' }));
|
|
504
|
+
|
|
505
|
+
await waitFor(() => {
|
|
506
|
+
expect(mocks.createServerPathDirectory).toHaveBeenCalledWith({
|
|
507
|
+
basePath: '/Users/peiwang/Projects/nextbot',
|
|
508
|
+
parentPath: '/Users/peiwang/Projects/nextbot/src',
|
|
509
|
+
name: 'nested',
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('opens root actions from blank tree space and reveals an inline draft', async () => {
|
|
515
|
+
const user = userEvent.setup();
|
|
516
|
+
render(
|
|
517
|
+
<ChatSessionWorkspacePanelContent
|
|
518
|
+
activeSelection={{ kind: 'project-files' }}
|
|
519
|
+
childSessionTabs={[]}
|
|
520
|
+
filePreviewRefreshVersion={0}
|
|
521
|
+
sessionKey="parent-1"
|
|
522
|
+
sessionCronJobs={[]}
|
|
523
|
+
sessionProjectRoot="/Users/peiwang/Projects/nextbot"
|
|
524
|
+
sessionWorkingDir="/Users/peiwang/Projects/nextbot"
|
|
525
|
+
/>,
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
fireEvent.contextMenu(screen.getByRole('tree', { name: 'Project files' }));
|
|
529
|
+
expect(screen.getByRole('menu', { name: 'Project root actions' })).toBeTruthy();
|
|
530
|
+
expect(screen.getByRole('menuitem', { name: 'Upload files here' })).toBeTruthy();
|
|
531
|
+
await user.click(screen.getByRole('menuitem', { name: 'New folder' }));
|
|
532
|
+
|
|
533
|
+
const input = screen.getByRole('textbox', { name: 'Folder name' });
|
|
534
|
+
expect(document.activeElement).toBe(input);
|
|
535
|
+
expect(mocks.scrollIntoView).toHaveBeenCalledWith({ block: 'nearest' });
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('uploads multiple project files from the root actions menu', async () => {
|
|
539
|
+
const user = userEvent.setup();
|
|
540
|
+
render(
|
|
541
|
+
<ChatSessionWorkspacePanelContent
|
|
542
|
+
activeSelection={{ kind: 'project-files' }}
|
|
543
|
+
childSessionTabs={[]}
|
|
544
|
+
filePreviewRefreshVersion={0}
|
|
545
|
+
sessionKey="parent-1"
|
|
546
|
+
sessionCronJobs={[]}
|
|
547
|
+
sessionProjectRoot="/Users/peiwang/Projects/nextbot"
|
|
548
|
+
sessionWorkingDir="/Users/peiwang/Projects/nextbot"
|
|
549
|
+
/>,
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
await user.click(screen.getByRole('button', { name: 'More project file actions' }));
|
|
553
|
+
await user.click(screen.getByRole('menuitem', { name: 'Upload files here' }));
|
|
554
|
+
const file = new File(['brief'], 'brief.md', { type: 'text/markdown' });
|
|
555
|
+
fireEvent.change(screen.getByLabelText('Upload files'), {
|
|
556
|
+
target: { files: [file] },
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
await waitFor(() => {
|
|
560
|
+
expect(mocks.uploadServerPathFiles).toHaveBeenCalledWith({
|
|
561
|
+
basePath: '/Users/peiwang/Projects/nextbot',
|
|
562
|
+
targetPath: '/Users/peiwang/Projects/nextbot',
|
|
563
|
+
files: [file],
|
|
564
|
+
overwrite: undefined,
|
|
565
|
+
});
|
|
566
|
+
expect(mocks.refetchBrowse).toHaveBeenCalled();
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('requires destructive confirmation before deleting a project file', async () => {
|
|
571
|
+
const user = userEvent.setup();
|
|
572
|
+
render(
|
|
573
|
+
<ChatSessionWorkspacePanelContent
|
|
574
|
+
activeSelection={{ kind: 'project-files' }}
|
|
575
|
+
childSessionTabs={[]}
|
|
576
|
+
filePreviewRefreshVersion={0}
|
|
577
|
+
sessionKey="parent-1"
|
|
578
|
+
sessionCronJobs={[]}
|
|
579
|
+
sessionProjectRoot="/Users/peiwang/Projects/nextbot"
|
|
580
|
+
sessionWorkingDir="/Users/peiwang/Projects/nextbot"
|
|
581
|
+
/>,
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
fireEvent.contextMenu(screen.getByRole('button', { name: 'package.json' }));
|
|
585
|
+
await user.click(screen.getByRole('menuitem', { name: 'Delete' }));
|
|
586
|
+
expect(mocks.deleteServerPathEntry).not.toHaveBeenCalled();
|
|
587
|
+
expect(screen.getByText('Delete package.json?')).toBeTruthy();
|
|
588
|
+
|
|
589
|
+
await user.click(screen.getByRole('button', { name: 'Delete' }));
|
|
590
|
+
await waitFor(() => {
|
|
591
|
+
expect(mocks.deleteServerPathEntry).toHaveBeenCalledWith({
|
|
592
|
+
basePath: '/Users/peiwang/Projects/nextbot',
|
|
593
|
+
path: '/private/resolved-worktree/package.json',
|
|
594
|
+
});
|
|
595
|
+
expect(mocks.removeWorkspacePath).toHaveBeenCalledWith('/private/resolved-worktree/package.json');
|
|
596
|
+
expect(mocks.refetchBrowse).toHaveBeenCalled();
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('preserves project tree expansion and scroll while visiting another workspace tab', async () => {
|
|
203
601
|
const user = userEvent.setup();
|
|
204
602
|
const projectFilesProps = {
|
|
205
|
-
activeSelection: { kind:
|
|
603
|
+
activeSelection: { kind: 'project-files' as const },
|
|
206
604
|
childSessionTabs: [],
|
|
207
605
|
filePreviewRefreshVersion: 0,
|
|
208
|
-
sessionKey:
|
|
606
|
+
sessionKey: 'parent-1',
|
|
209
607
|
sessionCronJobs: [],
|
|
210
|
-
sessionProjectRoot:
|
|
211
|
-
sessionWorkingDir:
|
|
608
|
+
sessionProjectRoot: '/Users/peiwang/Projects/nextbot',
|
|
609
|
+
sessionWorkingDir: '/Users/peiwang/Projects/nextbot',
|
|
212
610
|
};
|
|
213
|
-
const { rerender } = render(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const sourceDirectory = screen.getByRole("treeitem", {
|
|
218
|
-
name: "Open directory: src",
|
|
611
|
+
const { rerender } = render(<ChatSessionWorkspacePanelContent {...projectFilesProps} />);
|
|
612
|
+
const tree = screen.getByRole('tree', { name: 'Project files' });
|
|
613
|
+
const sourceDirectory = screen.getByRole('treeitem', {
|
|
614
|
+
name: 'Open directory: src',
|
|
219
615
|
});
|
|
220
|
-
await user.click(screen.getByRole(
|
|
616
|
+
await user.click(screen.getByRole('button', { name: 'src' }));
|
|
221
617
|
tree.scrollTop = 128;
|
|
222
|
-
expect(sourceDirectory.getAttribute(
|
|
618
|
+
expect(sourceDirectory.getAttribute('aria-expanded')).toBe('true');
|
|
619
|
+
|
|
620
|
+
rerender(<ChatSessionWorkspacePanelContent {...projectFilesProps} activeSelection={{ kind: 'overview' }} />);
|
|
621
|
+
rerender(<ChatSessionWorkspacePanelContent {...projectFilesProps} />);
|
|
622
|
+
|
|
623
|
+
expect(screen.getByRole('tree', { name: 'Project files' })).toBe(tree);
|
|
624
|
+
expect(sourceDirectory.isConnected).toBe(true);
|
|
625
|
+
expect(sourceDirectory.getAttribute('aria-expanded')).toBe('true');
|
|
626
|
+
expect(tree.scrollTop).toBe(128);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it('reuses the Explorer beside a file and persists a dragged compact width', () => {
|
|
630
|
+
const projectFilesProps = {
|
|
631
|
+
activeSelection: { kind: 'project-files' as const },
|
|
632
|
+
childSessionTabs: [],
|
|
633
|
+
filePreviewRefreshVersion: 0,
|
|
634
|
+
sessionKey: 'parent-1',
|
|
635
|
+
sessionCronJobs: [],
|
|
636
|
+
sessionProjectRoot: '/Users/peiwang/Projects/nextbot',
|
|
637
|
+
sessionWorkingDir: '/Users/peiwang/Projects/nextbot',
|
|
638
|
+
};
|
|
639
|
+
const { rerender } = render(<ChatSessionWorkspacePanelContent {...projectFilesProps} />);
|
|
640
|
+
const tree = screen.getByRole('tree', { name: 'Project files' });
|
|
223
641
|
|
|
224
642
|
rerender(
|
|
225
643
|
<ChatSessionWorkspacePanelContent
|
|
226
644
|
{...projectFilesProps}
|
|
227
|
-
activeSelection={{
|
|
645
|
+
activeSelection={{
|
|
646
|
+
kind: 'file',
|
|
647
|
+
file: {
|
|
648
|
+
key: 'parent-1::preview::package.json',
|
|
649
|
+
parentSessionKey: 'parent-1',
|
|
650
|
+
path: '/Users/peiwang/Projects/nextbot/package.json',
|
|
651
|
+
label: 'package.json',
|
|
652
|
+
viewMode: 'preview',
|
|
653
|
+
previewViewer: 'source',
|
|
654
|
+
},
|
|
655
|
+
}}
|
|
228
656
|
/>,
|
|
229
657
|
);
|
|
230
|
-
rerender(<ChatSessionWorkspacePanelContent {...projectFilesProps} />);
|
|
231
658
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
expect(tree
|
|
659
|
+
const explorer = screen.getByTestId('workspace-shared-explorer');
|
|
660
|
+
const breadcrumbs = screen.getByTestId('workspace-file-breadcrumbs');
|
|
661
|
+
const toggle = screen.getByTestId('workspace-explorer-toggle');
|
|
662
|
+
expect(screen.getByRole('tree', { name: 'Project files' })).toBe(tree);
|
|
663
|
+
expect(explorer.getAttribute('data-mode')).toBe('side');
|
|
664
|
+
expect(explorer.style.width).toBe('224px');
|
|
665
|
+
expect(breadcrumbs.contains(toggle)).toBe(true);
|
|
666
|
+
expect(toggle.parentElement?.className).not.toContain('border-r');
|
|
667
|
+
|
|
668
|
+
const handle = screen.getByRole('separator', {
|
|
669
|
+
name: 'Resize project files',
|
|
670
|
+
});
|
|
671
|
+
firePointerEvent(handle, 'pointerdown', { clientX: 224, pointerId: 1 });
|
|
672
|
+
firePointerEvent(window, 'pointermove', { clientX: 300, pointerId: 1 });
|
|
673
|
+
firePointerEvent(window, 'pointerup', { clientX: 300, pointerId: 1 });
|
|
674
|
+
expect(mocks.setWorkspaceExplorerWidth).toHaveBeenCalledWith(300);
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it('uses an overlay only below the compact threshold and closes it without unmounting', () => {
|
|
678
|
+
HTMLElement.prototype.getBoundingClientRect = () => ({
|
|
679
|
+
bottom: 600,
|
|
680
|
+
height: 600,
|
|
681
|
+
left: 0,
|
|
682
|
+
right: 540,
|
|
683
|
+
top: 0,
|
|
684
|
+
width: 540,
|
|
685
|
+
x: 0,
|
|
686
|
+
y: 0,
|
|
687
|
+
toJSON: () => ({}),
|
|
688
|
+
});
|
|
689
|
+
render(
|
|
690
|
+
<ChatSessionWorkspacePanelContent
|
|
691
|
+
activeSelection={{
|
|
692
|
+
kind: 'file',
|
|
693
|
+
file: {
|
|
694
|
+
key: 'parent-1::preview::package.json',
|
|
695
|
+
parentSessionKey: 'parent-1',
|
|
696
|
+
path: '/Users/peiwang/Projects/nextbot/package.json',
|
|
697
|
+
label: 'package.json',
|
|
698
|
+
viewMode: 'preview',
|
|
699
|
+
previewViewer: 'source',
|
|
700
|
+
},
|
|
701
|
+
}}
|
|
702
|
+
childSessionTabs={[]}
|
|
703
|
+
filePreviewRefreshVersion={0}
|
|
704
|
+
sessionKey="parent-1"
|
|
705
|
+
sessionCronJobs={[]}
|
|
706
|
+
sessionProjectRoot="/Users/peiwang/Projects/nextbot"
|
|
707
|
+
sessionWorkingDir="/Users/peiwang/Projects/nextbot"
|
|
708
|
+
/>,
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
const explorer = screen.getByTestId('workspace-shared-explorer');
|
|
712
|
+
const tree = screen.getByRole('tree', { name: 'Project files' });
|
|
713
|
+
expect(explorer.getAttribute('data-mode')).toBe('overlay');
|
|
714
|
+
expect(screen.queryByTestId('workspace-explorer-resize-handle')).toBeNull();
|
|
715
|
+
|
|
716
|
+
fireEvent.click(screen.getByTestId('workspace-explorer-scrim'));
|
|
717
|
+
expect(explorer.getAttribute('aria-hidden')).toBe('true');
|
|
718
|
+
expect(tree.isConnected).toBe(true);
|
|
236
719
|
});
|