@marimo-team/frontend 0.24.1-dev84 → 0.24.1-dev86
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/dist/assets/{CellStatus-GcHBZKLm.js → CellStatus-D40JXMpi.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-BU1NZpRe.js → ConnectedDataExplorerComponent-Dkqk66Mv.js} +1 -1
- package/dist/assets/{JsonOutput-CUJlEdsN.js → JsonOutput-DeeAtdky.js} +1 -1
- package/dist/assets/{MarimoErrorOutput-DCAezqcJ.js → MarimoErrorOutput-Dh-877oV.js} +1 -1
- package/dist/assets/{RenderHTML-D9-75ltP.js → RenderHTML-C7JgAfHC.js} +1 -1
- package/dist/assets/{RunButton-plSsbubG.js → RunButton-C9d_w3qn.js} +1 -1
- package/dist/assets/{add-cell-with-ai-07LjLydm.js → add-cell-with-ai-zrX9HOPy.js} +1 -1
- package/dist/assets/{add-connection-dialog-By0i0Fq5.js → add-connection-dialog-OQxtKyCV.js} +1 -1
- package/dist/assets/{agent-panel-D01f7IIt.js → agent-panel-DF7_IEEH.js} +1 -1
- package/dist/assets/{ai-model-dropdown-DPxSumqI.js → ai-model-dropdown-DIItpu01.js} +1 -1
- package/dist/assets/{app-config-button-DV7ZvBMx.js → app-config-button-CeDIYZUc.js} +1 -1
- package/dist/assets/{blob-yxfhyyvD.js → blob-iewSQmkZ.js} +1 -1
- package/dist/assets/{cell-editor-Bl5DkKIR.js → cell-editor-BkHTcrF-.js} +1 -1
- package/dist/assets/{cell-link-hHrWcozB.js → cell-link-ZLZTXwR8.js} +1 -1
- package/dist/assets/{cells-yuYDzbAy.js → cells-DyE9dSiM.js} +1 -1
- package/dist/assets/{chat-display-BFCcdgia.js → chat-display-B9XOYJaA.js} +1 -1
- package/dist/assets/{chat-panel-CS9bLpq9.js → chat-panel-Drx8ToF5.js} +1 -1
- package/dist/assets/{chat-ui-lssAqY7E.js → chat-ui-CJ1VFz1m.js} +1 -1
- package/dist/assets/{column-preview-BEmcYaKl.js → column-preview-Ls8UnJOt.js} +1 -1
- package/dist/assets/{command-palette-DInNqneC.js → command-palette-DWyHSatV.js} +1 -1
- package/dist/assets/{common-Bs2kaOs2.js → common-03vbDqrN.js} +1 -1
- package/dist/assets/{components-ib4asSo7.js → components-CBlGIIAX.js} +1 -1
- package/dist/assets/{components-ByyAzQxP.js → components-J_vBLfsO.js} +1 -1
- package/dist/assets/{config-DaNyNpZL.js → config-D4frVGQ4.js} +1 -1
- package/dist/assets/{config-Cl3eMWxb.js → config-ycxJQP2b.js} +1 -1
- package/dist/assets/{datasource-DeZYq5UY.js → datasource-B4VfwXg_.js} +1 -1
- package/dist/assets/{dependency-graph-panel-g10osAFc.js → dependency-graph-panel-B24NhlCS.js} +1 -1
- package/dist/assets/{documentation-panel-CZ1mKkDh.js → documentation-panel-C4y_ufGj.js} +1 -1
- package/dist/assets/{download-D1crMjD8.js → download-Kg8_rqQH.js} +1 -1
- package/dist/assets/{edit-page-ZyGzkf7q.js → edit-page-DL_yo5rn.js} +4 -4
- package/dist/assets/{error-entries-B6XTLRyE.js → error-entries-Cy1y8FaI.js} +1 -1
- package/dist/assets/{error-panel-lexB3L6f.js → error-panel-D_pwhWfh.js} +1 -1
- package/dist/assets/file-explorer-panel-QoJu0RbE.js +1 -0
- package/dist/assets/{file-icons-Dx3v6lMu.js → file-icons-BN7fZ7-c.js} +1 -1
- package/dist/assets/file-name-input-qU-dl8Og.js +3 -0
- package/dist/assets/{floating-outline-3rQEbxxk.js → floating-outline-Coq72JrA.js} +1 -1
- package/dist/assets/{glide-data-editor-DZ2Minh8.js → glide-data-editor-DcY3nXI8.js} +1 -1
- package/dist/assets/{home-page-Ck-_oVJT.js → home-page-C6NwS9L9.js} +1 -1
- package/dist/assets/{index-g9ny2vs0.js → index-DMvKFHTV.js} +5 -5
- package/dist/assets/{kiosk-mode-CjT3sqy8.js → kiosk-mode-CzGt3mc2.js} +1 -1
- package/dist/assets/{layout-D0Q03BhB.js → layout-ByAlvlct.js} +3 -3
- package/dist/assets/{logs-panel-CSbUhhRo.js → logs-panel-zYelRd3A.js} +1 -1
- package/dist/assets/{markdown-renderer-BgUTGtM0.js → markdown-renderer-BqMslrdb.js} +1 -1
- package/dist/assets/{mermaid-Nzih1WTG.js → mermaid-CeseGGnS.js} +1 -1
- package/dist/assets/{name-cell-input-pEE4FGAL.js → name-cell-input-jrtswt5e.js} +1 -1
- package/dist/assets/{outline-panel-BgnjEQWg.js → outline-panel-B0P8XNnQ.js} +1 -1
- package/dist/assets/{packages-panel-BBP_xysn.js → packages-panel-C08E4Wjm.js} +1 -1
- package/dist/assets/{pair-with-agent-modal-Gg8MZHWW.js → pair-with-agent-modal-BZBengJw.js} +1 -1
- package/dist/assets/{panel-accordion-state-vpqEc3Kd.js → panel-accordion-state-Cyy4afrx.js} +1 -1
- package/dist/assets/{panels-fKt2XFRB.js → panels-DRtPsJ1u.js} +1 -1
- package/dist/assets/{process-output-siRm_lza.js → process-output-BiA5MKpM.js} +1 -1
- package/dist/assets/{readonly-code-display-CLWV76FT.js → readonly-code-display-B45Jy4vH.js} +1 -1
- package/dist/assets/{readonly-python-code-Bor9b9pV.js → readonly-python-code-BQ5Ti6Hw.js} +1 -1
- package/dist/assets/{renderShortcut-CS4ccH3x.js → renderShortcut-DK_OucLT.js} +1 -1
- package/dist/assets/{reveal-component-B55bWtCO.js → reveal-component-Bna3jncc.js} +1 -1
- package/dist/assets/{run-page-mD_6P_t5.js → run-page-CFC5C_It.js} +1 -1
- package/dist/assets/{scratchpad-panel-B_7IHnPD.js → scratchpad-panel-m2LRBFI5.js} +1 -1
- package/dist/assets/{session-panel-DB5qEYqt.js → session-panel-BV8Whoy8.js} +1 -1
- package/dist/assets/{skeleton-TY77TiqZ.js → skeleton-CWFWp9eU.js} +1 -1
- package/dist/assets/{snippets-panel-B8Yi81tz.js → snippets-panel-DBzdOGwQ.js} +1 -1
- package/dist/assets/state-DPS9Q717.js +3 -0
- package/dist/assets/{state-O8w98G11.js → state-rP7TRCla.js} +1 -1
- package/dist/assets/{switch-Gl6cozkm.js → switch-CtH5Ct94.js} +1 -1
- package/dist/assets/{terminal-DOqqA9Ha.js → terminal-C73iz1fU.js} +1 -1
- package/dist/assets/{tracing-DuLkr7ts.js → tracing-BP1J14GT.js} +1 -1
- package/dist/assets/{tracing-panel-CeLKVG8M.js → tracing-panel-CQasWudc.js} +2 -2
- package/dist/assets/{tree-actions-Bh-Nnaub.js → tree-actions-CrB9REp3.js} +1 -1
- package/dist/assets/{useAddCell-_JRYhAFd.js → useAddCell-DHYPnl7W.js} +1 -1
- package/dist/assets/{useBoolean-Cu7Eado1.js → useBoolean-ZU5StAbr.js} +1 -1
- package/dist/assets/{useCellActionButton-Bi8Lnv3F.js → useCellActionButton-C7bEu9tJ.js} +1 -1
- package/dist/assets/{useDeleteCell-CBQiavzs.js → useDeleteCell-CIzxZKUw.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-BOn3UJ07.js → useDependencyPanelTab-CLmRGbOD.js} +1 -1
- package/dist/assets/{useNotebookActions-C1YIorGp.js → useNotebookActions-Q97EgZxR.js} +1 -1
- package/dist/assets/{useRunCells-BFQfLlUl.js → useRunCells-Csh5VYKu.js} +1 -1
- package/dist/assets/{useSplitCell-Ig9P6lAn.js → useSplitCell-BfT7UcKM.js} +1 -1
- package/dist/assets/{useTheme-D3zvxdF3.js → useTheme-CF-zG9YH.js} +1 -1
- package/dist/assets/{utils-CUZQNAWJ.js → utils-CWSZQEjr.js} +1 -1
- package/dist/assets/{vega-component-B0MZ_r-y.js → vega-component-Bel2PDO-.js} +1 -1
- package/dist/index.html +26 -26
- package/package.json +1 -1
- package/src/__mocks__/requests.ts +1 -0
- package/src/components/editor/chrome/panels/file-explorer-panel.tsx +3 -2
- package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +31 -18
- package/src/components/editor/file-tree/__tests__/file-explorer.test.ts +18 -12
- package/src/components/editor/file-tree/__tests__/requesting-tree.test.ts +364 -387
- package/src/components/editor/file-tree/file-explorer.tsx +137 -97
- package/src/components/editor/file-tree/file-name-input.tsx +5 -1
- package/src/components/editor/file-tree/file-viewer.tsx +2 -2
- package/src/components/editor/file-tree/requesting-tree.tsx +291 -171
- package/src/components/editor/file-tree/state.tsx +1 -0
- package/src/core/config/__tests__/config-schema.test.ts +15 -0
- package/src/core/config/config-schema.ts +15 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/network/__tests__/requests-network.test.ts +7 -0
- package/src/core/network/__tests__/requests-toasting.test.tsx +10 -0
- package/src/core/network/requests-lazy.ts +1 -0
- package/src/core/network/requests-network.ts +4 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +1 -0
- package/src/core/network/types.ts +3 -0
- package/src/core/wasm/bridge.ts +9 -0
- package/src/core/wasm/worker/types.ts +2 -0
- package/dist/assets/file-explorer-panel-DHRH2J-3.js +0 -1
- package/dist/assets/file-name-input-DzL8D_7x.js +0 -3
- package/dist/assets/state-D1N9_41P.js +0 -3
|
@@ -3,8 +3,9 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
|
3
3
|
import { MockModules } from "@/__mocks__/common";
|
|
4
4
|
import { toast } from "@/components/ui/use-toast";
|
|
5
5
|
import type { FilePath } from "@/utils/paths";
|
|
6
|
-
import { RequestingTree } from "../requesting-tree";
|
|
6
|
+
import { fileTreeNodeId, RequestingTree } from "../requesting-tree";
|
|
7
7
|
|
|
8
|
+
const getRoots = vi.fn();
|
|
8
9
|
const sendListFiles = vi.fn();
|
|
9
10
|
const sendCreateFileOrFolder = vi.fn();
|
|
10
11
|
const sendDeleteFileOrFolder = vi.fn();
|
|
@@ -13,499 +14,475 @@ const sendRenameFileOrFolder = vi.fn();
|
|
|
13
14
|
|
|
14
15
|
vi.mock("@/components/ui/use-toast", () => MockModules.toast());
|
|
15
16
|
|
|
17
|
+
const PRIMARY_FILES = [
|
|
18
|
+
{
|
|
19
|
+
id: "/root/file1",
|
|
20
|
+
name: "file1",
|
|
21
|
+
path: "/root/file1",
|
|
22
|
+
isDirectory: false,
|
|
23
|
+
isMarimoFile: false,
|
|
24
|
+
children: [],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "/root/folder1",
|
|
28
|
+
name: "folder1",
|
|
29
|
+
path: "/root/folder1",
|
|
30
|
+
isDirectory: true,
|
|
31
|
+
isMarimoFile: false,
|
|
32
|
+
children: [],
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const PRIMARY_ROOT_ID = fileTreeNodeId("/root", "/root");
|
|
37
|
+
const PRIMARY_FILE_ID = fileTreeNodeId("/root", "/root/file1");
|
|
38
|
+
const EXTERNAL_ROOT_ID = fileTreeNodeId("/external", "/external");
|
|
39
|
+
|
|
16
40
|
describe("RequestingTree", () => {
|
|
17
|
-
let
|
|
18
|
-
const
|
|
41
|
+
let tree: RequestingTree;
|
|
42
|
+
const onChange = vi.fn();
|
|
19
43
|
|
|
20
44
|
beforeEach(async () => {
|
|
21
|
-
|
|
45
|
+
getRoots.mockResolvedValue({
|
|
46
|
+
roots: [
|
|
47
|
+
{ path: "/root", name: "workspace", isPrimary: true },
|
|
48
|
+
{ path: "/external", name: "Shared", isPrimary: false },
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
sendListFiles.mockImplementation(async ({ path }: { path: string }) => ({
|
|
52
|
+
files: path === "/root" ? PRIMARY_FILES : [],
|
|
53
|
+
root: path,
|
|
54
|
+
}));
|
|
55
|
+
sendCreateFileOrFolder.mockResolvedValue({ success: true });
|
|
56
|
+
sendDeleteFileOrFolder.mockResolvedValue({ success: true });
|
|
57
|
+
sendCopyFileOrFolder.mockResolvedValue({ success: true });
|
|
58
|
+
sendRenameFileOrFolder.mockResolvedValue({ success: true });
|
|
59
|
+
|
|
60
|
+
tree = new RequestingTree({
|
|
61
|
+
getRoots,
|
|
22
62
|
listFiles: sendListFiles,
|
|
23
63
|
createFileOrFolder: sendCreateFileOrFolder,
|
|
24
64
|
deleteFileOrFolder: sendDeleteFileOrFolder,
|
|
25
65
|
copyFileOrFolder: sendCopyFileOrFolder,
|
|
26
66
|
renameFileOrFolder: sendRenameFileOrFolder,
|
|
27
67
|
});
|
|
28
|
-
|
|
29
|
-
files: [
|
|
30
|
-
{ id: "1.1", name: "file1", path: "/root/file1" },
|
|
31
|
-
{
|
|
32
|
-
id: "1.2",
|
|
33
|
-
name: "folder1",
|
|
34
|
-
isDirectory: true,
|
|
35
|
-
path: "/root/folder1",
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
id: "1.3",
|
|
39
|
-
name: "folder2",
|
|
40
|
-
isDirectory: true,
|
|
41
|
-
path: "/root/folder2",
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
root: "/root",
|
|
45
|
-
});
|
|
46
|
-
await requestingTree.initialize(mockOnChange);
|
|
68
|
+
await tree.initialize(onChange);
|
|
47
69
|
});
|
|
48
70
|
|
|
49
71
|
afterEach(() => {
|
|
50
72
|
vi.clearAllMocks();
|
|
51
73
|
});
|
|
52
74
|
|
|
53
|
-
test("
|
|
54
|
-
expect(
|
|
55
|
-
expect(
|
|
56
|
-
expect(
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
|
|
75
|
+
test("initializes with visible, unloaded root nodes", () => {
|
|
76
|
+
expect(getRoots).toHaveBeenCalledOnce();
|
|
77
|
+
expect(sendListFiles).not.toHaveBeenCalled();
|
|
78
|
+
expect(tree.getPrimaryRootPath()).toBe("/root");
|
|
79
|
+
expect(onChange).toHaveBeenLastCalledWith([
|
|
80
|
+
expect.objectContaining({
|
|
81
|
+
id: PRIMARY_ROOT_ID,
|
|
82
|
+
name: "workspace",
|
|
83
|
+
isRoot: true,
|
|
84
|
+
isPrimaryRoot: true,
|
|
85
|
+
children: [],
|
|
86
|
+
}),
|
|
87
|
+
expect.objectContaining({
|
|
88
|
+
id: EXTERNAL_ROOT_ID,
|
|
89
|
+
name: "Shared",
|
|
90
|
+
isRoot: true,
|
|
91
|
+
isPrimaryRoot: false,
|
|
92
|
+
children: [],
|
|
93
|
+
}),
|
|
60
94
|
]);
|
|
61
95
|
});
|
|
62
96
|
|
|
63
|
-
test("
|
|
64
|
-
|
|
65
|
-
|
|
97
|
+
test("flattens the primary root when there are no additional roots", async () => {
|
|
98
|
+
getRoots.mockResolvedValueOnce({
|
|
99
|
+
roots: [{ path: "/root", name: "workspace", isPrimary: true }],
|
|
100
|
+
});
|
|
101
|
+
const singleRootOnChange = vi.fn();
|
|
102
|
+
const singleRootTree = new RequestingTree({
|
|
103
|
+
getRoots,
|
|
104
|
+
listFiles: sendListFiles,
|
|
105
|
+
createFileOrFolder: sendCreateFileOrFolder,
|
|
106
|
+
deleteFileOrFolder: sendDeleteFileOrFolder,
|
|
107
|
+
copyFileOrFolder: sendCopyFileOrFolder,
|
|
108
|
+
renameFileOrFolder: sendRenameFileOrFolder,
|
|
66
109
|
});
|
|
67
|
-
const result = await requestingTree.expand("1.2");
|
|
68
|
-
expect(result).toBe(true);
|
|
69
|
-
expect(sendListFiles).toHaveBeenCalledWith({ path: "/root/folder1" });
|
|
70
|
-
expect(mockOnChange).toHaveBeenCalled();
|
|
71
|
-
const lastCall = mockOnChange.mock.calls.at(-1);
|
|
72
|
-
expect(lastCall).toBeDefined();
|
|
73
|
-
expect(lastCall![0]).toMatchInlineSnapshot(`
|
|
74
|
-
[
|
|
75
|
-
{
|
|
76
|
-
"id": "1.1",
|
|
77
|
-
"name": "file1",
|
|
78
|
-
"path": "/root/file1",
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"children": [
|
|
82
|
-
{
|
|
83
|
-
"id": "2",
|
|
84
|
-
"name": "file2",
|
|
85
|
-
"path": "/roo/folder1/file2",
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
"id": "1.2",
|
|
89
|
-
"isDirectory": true,
|
|
90
|
-
"name": "folder1",
|
|
91
|
-
"path": "/root/folder1",
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
"id": "1.3",
|
|
95
|
-
"isDirectory": true,
|
|
96
|
-
"name": "folder2",
|
|
97
|
-
"path": "/root/folder2",
|
|
98
|
-
},
|
|
99
|
-
]
|
|
100
|
-
`);
|
|
101
|
-
});
|
|
102
110
|
|
|
103
|
-
|
|
104
|
-
sendRenameFileOrFolder.mockResolvedValue({ success: true });
|
|
111
|
+
await singleRootTree.initialize(singleRootOnChange);
|
|
105
112
|
|
|
106
|
-
|
|
107
|
-
expect(
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
expect(sendListFiles).toHaveBeenCalledWith({ path: "/root" });
|
|
114
|
+
expect(singleRootOnChange).toHaveBeenLastCalledWith([
|
|
115
|
+
expect.objectContaining({
|
|
116
|
+
id: PRIMARY_FILE_ID,
|
|
117
|
+
path: "/root/file1",
|
|
118
|
+
isRoot: false,
|
|
119
|
+
isPrimaryRoot: true,
|
|
120
|
+
}),
|
|
121
|
+
expect.objectContaining({
|
|
122
|
+
id: fileTreeNodeId("/root", "/root/folder1"),
|
|
123
|
+
path: "/root/folder1",
|
|
124
|
+
isRoot: false,
|
|
125
|
+
isPrimaryRoot: true,
|
|
126
|
+
}),
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
await singleRootTree.createFile({ name: "new.txt", parentId: null });
|
|
130
|
+
expect(sendCreateFileOrFolder).toHaveBeenCalledWith({
|
|
131
|
+
path: "/root",
|
|
132
|
+
type: "file",
|
|
133
|
+
name: "new.txt",
|
|
110
134
|
});
|
|
111
|
-
expect(mockOnChange).toHaveBeenCalled();
|
|
112
|
-
const lastCall = mockOnChange.mock.calls.at(-1);
|
|
113
|
-
expect(lastCall).toBeDefined();
|
|
114
|
-
expect(lastCall![0]).toMatchInlineSnapshot(`
|
|
115
|
-
[
|
|
116
|
-
{
|
|
117
|
-
"id": "1.1",
|
|
118
|
-
"name": "file1",
|
|
119
|
-
"path": "/root/file1",
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
"id": "1.2",
|
|
123
|
-
"isDirectory": true,
|
|
124
|
-
"name": "folder1",
|
|
125
|
-
"path": "/root/folder1",
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
"id": "1.3",
|
|
129
|
-
"isDirectory": true,
|
|
130
|
-
"name": "folder2",
|
|
131
|
-
"path": "/root/folder2",
|
|
132
|
-
},
|
|
133
|
-
]
|
|
134
|
-
`);
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
-
test("
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
await requestingTree.move(["1.1"], "1.2");
|
|
141
|
-
expect(sendRenameFileOrFolder).toHaveBeenCalled();
|
|
142
|
-
expect(mockOnChange).toHaveBeenCalled();
|
|
143
|
-
const lastCall = mockOnChange.mock.calls.at(-1);
|
|
144
|
-
expect(lastCall).toBeDefined();
|
|
145
|
-
expect(lastCall![0]).toMatchInlineSnapshot(`
|
|
146
|
-
[
|
|
147
|
-
{
|
|
148
|
-
"id": "1.1",
|
|
149
|
-
"name": "file1",
|
|
150
|
-
"path": "/root/file1",
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
"children": [
|
|
154
|
-
{
|
|
155
|
-
"id": "1.1",
|
|
156
|
-
"name": "file1",
|
|
157
|
-
"path": "/root/folder1/file1",
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
"id": "1.2",
|
|
161
|
-
"isDirectory": true,
|
|
162
|
-
"name": "folder1",
|
|
163
|
-
"path": "/root/folder1",
|
|
164
|
-
},
|
|
137
|
+
test("expands each root lazily and annotates its descendants", async () => {
|
|
138
|
+
sendListFiles.mockResolvedValueOnce({
|
|
139
|
+
files: [
|
|
165
140
|
{
|
|
166
|
-
|
|
167
|
-
"
|
|
168
|
-
|
|
169
|
-
|
|
141
|
+
id: "/external/data.csv",
|
|
142
|
+
path: "/external/data.csv",
|
|
143
|
+
name: "data.csv",
|
|
144
|
+
isDirectory: false,
|
|
145
|
+
isMarimoFile: false,
|
|
146
|
+
children: [],
|
|
170
147
|
},
|
|
171
|
-
]
|
|
172
|
-
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test("copy should duplicate a file", async () => {
|
|
176
|
-
sendCopyFileOrFolder.mockResolvedValue({ success: true });
|
|
177
|
-
|
|
178
|
-
await requestingTree.copy("1.1", "file1_copy");
|
|
179
|
-
expect(sendCopyFileOrFolder).toHaveBeenCalledWith({
|
|
180
|
-
path: "/root/file1",
|
|
181
|
-
newPath: "/root/file1_copy",
|
|
148
|
+
],
|
|
149
|
+
root: "/external",
|
|
182
150
|
});
|
|
183
|
-
expect(mockOnChange).toHaveBeenCalled();
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("delete should drop a file on success", async () => {
|
|
187
|
-
sendDeleteFileOrFolder.mockResolvedValue({ success: true });
|
|
188
151
|
|
|
189
|
-
await
|
|
190
|
-
expect(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
152
|
+
expect(await tree.expand(EXTERNAL_ROOT_ID)).toBe(true);
|
|
153
|
+
expect(sendListFiles).toHaveBeenCalledWith({ path: "/external" });
|
|
154
|
+
expect(onChange.mock.calls.at(-1)?.[0][1].children[0]).toEqual(
|
|
155
|
+
expect.objectContaining({
|
|
156
|
+
path: "/external/data.csv",
|
|
157
|
+
rootPath: "/external",
|
|
158
|
+
isPrimaryRoot: false,
|
|
159
|
+
isRoot: false,
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
198
162
|
});
|
|
199
163
|
|
|
200
|
-
test("
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
await requestingTree.createFile({ name: "file3", parentId: "1.2" });
|
|
164
|
+
test("uses the primary root for toolbar creation without a selection", async () => {
|
|
165
|
+
await tree.createFile({ name: "new.txt", parentId: null });
|
|
204
166
|
expect(sendCreateFileOrFolder).toHaveBeenCalledWith({
|
|
205
|
-
path: "/root
|
|
167
|
+
path: "/root",
|
|
206
168
|
type: "file",
|
|
207
|
-
name: "
|
|
169
|
+
name: "new.txt",
|
|
208
170
|
});
|
|
209
|
-
expect(mockOnChange).toHaveBeenCalled();
|
|
210
171
|
});
|
|
211
172
|
|
|
212
|
-
test("
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
await requestingTree.createFile({
|
|
216
|
-
name: "notebook1",
|
|
217
|
-
parentId: "1.2",
|
|
218
|
-
type: "notebook",
|
|
219
|
-
});
|
|
173
|
+
test("creates and uploads into an explicitly selected root", async () => {
|
|
174
|
+
await tree.createFolder("exports", EXTERNAL_ROOT_ID);
|
|
220
175
|
expect(sendCreateFileOrFolder).toHaveBeenCalledWith({
|
|
221
|
-
path: "/
|
|
222
|
-
type: "
|
|
223
|
-
name: "
|
|
176
|
+
path: "/external",
|
|
177
|
+
type: "directory",
|
|
178
|
+
name: "exports",
|
|
224
179
|
});
|
|
225
|
-
expect(mockOnChange).toHaveBeenCalled();
|
|
226
180
|
});
|
|
227
181
|
|
|
228
|
-
test("
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
await
|
|
232
|
-
|
|
233
|
-
expect(
|
|
182
|
+
test("prevents mutations of structural root nodes", async () => {
|
|
183
|
+
await tree.rename(EXTERNAL_ROOT_ID, "renamed");
|
|
184
|
+
await tree.copy(EXTERNAL_ROOT_ID, "copy");
|
|
185
|
+
await tree.delete(EXTERNAL_ROOT_ID);
|
|
186
|
+
|
|
187
|
+
expect(sendRenameFileOrFolder).not.toHaveBeenCalled();
|
|
188
|
+
expect(sendCopyFileOrFolder).not.toHaveBeenCalled();
|
|
189
|
+
expect(sendDeleteFileOrFolder).not.toHaveBeenCalled();
|
|
190
|
+
expect(toast).toHaveBeenCalledWith(
|
|
191
|
+
expect.objectContaining({
|
|
192
|
+
description: "File browser roots cannot be modified",
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
234
195
|
});
|
|
235
196
|
|
|
236
|
-
test("
|
|
237
|
-
await
|
|
238
|
-
expect(sendListFiles).toHaveBeenCalled();
|
|
239
|
-
expect(mockOnChange).toHaveBeenCalled();
|
|
240
|
-
const lastCall = mockOnChange.mock.calls.at(-1);
|
|
241
|
-
expect(lastCall).toBeDefined();
|
|
242
|
-
expect(lastCall![0]).toMatchInlineSnapshot(`
|
|
243
|
-
[
|
|
244
|
-
{
|
|
245
|
-
"id": "1.1",
|
|
246
|
-
"name": "file1",
|
|
247
|
-
"path": "/root/file1",
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
"id": "1.2",
|
|
251
|
-
"isDirectory": true,
|
|
252
|
-
"name": "folder1",
|
|
253
|
-
"path": "/root/folder1",
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
"id": "1.3",
|
|
257
|
-
"isDirectory": true,
|
|
258
|
-
"name": "folder2",
|
|
259
|
-
"path": "/root/folder2",
|
|
260
|
-
},
|
|
261
|
-
]
|
|
262
|
-
`);
|
|
263
|
-
});
|
|
197
|
+
test("supports copying, renaming, and deleting files by absolute path", async () => {
|
|
198
|
+
await tree.expand(PRIMARY_ROOT_ID);
|
|
264
199
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
files: [
|
|
270
|
-
{
|
|
271
|
-
id: "uploaded",
|
|
272
|
-
name: "uploaded.csv",
|
|
273
|
-
path: "/root/folder1/uploaded.csv",
|
|
274
|
-
},
|
|
275
|
-
],
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
return {
|
|
279
|
-
files: [
|
|
280
|
-
{ id: "1.1", name: "file1", path: "/root/file1" },
|
|
281
|
-
{
|
|
282
|
-
id: "1.2",
|
|
283
|
-
name: "folder1",
|
|
284
|
-
isDirectory: true,
|
|
285
|
-
path: "/root/folder1",
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
id: "1.3",
|
|
289
|
-
name: "folder2",
|
|
290
|
-
isDirectory: true,
|
|
291
|
-
path: "/root/folder2",
|
|
292
|
-
},
|
|
293
|
-
],
|
|
294
|
-
};
|
|
200
|
+
await tree.copy(PRIMARY_FILE_ID, "file1_copy");
|
|
201
|
+
expect(sendCopyFileOrFolder).toHaveBeenCalledWith({
|
|
202
|
+
path: "/root/file1",
|
|
203
|
+
newPath: "/root/file1_copy",
|
|
295
204
|
});
|
|
296
205
|
|
|
297
|
-
await
|
|
206
|
+
await tree.rename(PRIMARY_FILE_ID, "file2");
|
|
207
|
+
expect(sendRenameFileOrFolder).toHaveBeenCalledWith({
|
|
208
|
+
path: "/root/file1",
|
|
209
|
+
newPath: "/root/file2",
|
|
210
|
+
});
|
|
298
211
|
|
|
299
|
-
|
|
300
|
-
expect(
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
id: "1.2",
|
|
304
|
-
name: "folder1",
|
|
305
|
-
isDirectory: true,
|
|
306
|
-
path: "/root/folder1",
|
|
307
|
-
children: [
|
|
308
|
-
{
|
|
309
|
-
id: "uploaded",
|
|
310
|
-
name: "uploaded.csv",
|
|
311
|
-
path: "/root/folder1/uploaded.csv",
|
|
312
|
-
},
|
|
313
|
-
],
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
id: "1.3",
|
|
317
|
-
name: "folder2",
|
|
318
|
-
isDirectory: true,
|
|
319
|
-
path: "/root/folder2",
|
|
320
|
-
},
|
|
321
|
-
]);
|
|
212
|
+
await tree.delete(PRIMARY_FILE_ID);
|
|
213
|
+
expect(sendDeleteFileOrFolder).toHaveBeenCalledWith({
|
|
214
|
+
path: "/root/file1",
|
|
215
|
+
});
|
|
322
216
|
});
|
|
323
217
|
|
|
324
|
-
describe("
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
deleteFileOrFolder: sendDeleteFileOrFolder,
|
|
330
|
-
copyFileOrFolder: sendCopyFileOrFolder,
|
|
331
|
-
renameFileOrFolder: sendRenameFileOrFolder,
|
|
332
|
-
});
|
|
333
|
-
sendListFiles.mockRejectedValue(new Error("Network error"));
|
|
334
|
-
await requestingTree.initialize(mockOnChange);
|
|
335
|
-
expect(toast).toHaveBeenCalledWith({
|
|
336
|
-
title: "Failed",
|
|
337
|
-
description: expect.any(String),
|
|
338
|
-
});
|
|
218
|
+
describe("failed mutations", () => {
|
|
219
|
+
beforeEach(async () => {
|
|
220
|
+
await tree.expand(PRIMARY_ROOT_ID);
|
|
221
|
+
sendListFiles.mockClear();
|
|
222
|
+
onChange.mockClear();
|
|
339
223
|
});
|
|
340
224
|
|
|
341
|
-
test("
|
|
342
|
-
|
|
225
|
+
test("does not update or refresh after a failed copy", async () => {
|
|
226
|
+
sendCopyFileOrFolder.mockResolvedValueOnce({
|
|
343
227
|
success: false,
|
|
344
|
-
message: "
|
|
228
|
+
message: "Permission denied",
|
|
345
229
|
});
|
|
346
230
|
|
|
347
|
-
await
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
});
|
|
231
|
+
await tree.copy(PRIMARY_FILE_ID, "file1_copy");
|
|
232
|
+
|
|
233
|
+
expect(sendListFiles).not.toHaveBeenCalled();
|
|
234
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
352
235
|
expect(toast).toHaveBeenCalledWith({
|
|
353
236
|
title: "Failed",
|
|
354
|
-
description: "
|
|
237
|
+
description: "Permission denied",
|
|
355
238
|
});
|
|
356
239
|
});
|
|
357
240
|
|
|
358
|
-
test("
|
|
359
|
-
sendRenameFileOrFolder.
|
|
241
|
+
test("does not update or refresh after a failed rename", async () => {
|
|
242
|
+
sendRenameFileOrFolder.mockResolvedValueOnce({
|
|
360
243
|
success: false,
|
|
361
|
-
message: "
|
|
244
|
+
message: "Permission denied",
|
|
362
245
|
});
|
|
363
|
-
const changesBefore = mockOnChange.mock.calls.length;
|
|
364
|
-
|
|
365
|
-
await requestingTree.rename("1.1", "file2");
|
|
366
246
|
|
|
367
|
-
|
|
368
|
-
// tree stays in sync with the backend.
|
|
369
|
-
expect(mockOnChange.mock.calls.length).toBe(changesBefore);
|
|
370
|
-
});
|
|
247
|
+
await tree.rename(PRIMARY_FILE_ID, "file2");
|
|
371
248
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
success: false,
|
|
375
|
-
message: "Error deleting",
|
|
376
|
-
});
|
|
377
|
-
const changesBefore = mockOnChange.mock.calls.length;
|
|
378
|
-
|
|
379
|
-
await requestingTree.delete("1.1");
|
|
380
|
-
expect(sendDeleteFileOrFolder).toHaveBeenCalledWith({
|
|
381
|
-
path: "/root/file1",
|
|
382
|
-
});
|
|
249
|
+
expect(sendListFiles).not.toHaveBeenCalled();
|
|
250
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
383
251
|
expect(toast).toHaveBeenCalledWith({
|
|
384
252
|
title: "Failed",
|
|
385
|
-
description: "
|
|
253
|
+
description: "Permission denied",
|
|
386
254
|
});
|
|
387
|
-
expect(mockOnChange.mock.calls.length).toBe(changesBefore);
|
|
388
255
|
});
|
|
389
256
|
|
|
390
|
-
test("
|
|
391
|
-
|
|
257
|
+
test("does not update or refresh after a failed delete", async () => {
|
|
258
|
+
sendDeleteFileOrFolder.mockResolvedValueOnce({
|
|
392
259
|
success: false,
|
|
393
|
-
message: "
|
|
260
|
+
message: "Permission denied",
|
|
394
261
|
});
|
|
395
262
|
|
|
396
|
-
await
|
|
263
|
+
await tree.delete(PRIMARY_FILE_ID);
|
|
397
264
|
|
|
265
|
+
expect(sendListFiles).not.toHaveBeenCalled();
|
|
266
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
398
267
|
expect(toast).toHaveBeenCalledWith({
|
|
399
268
|
title: "Failed",
|
|
400
|
-
description: "
|
|
269
|
+
description: "Permission denied",
|
|
401
270
|
});
|
|
402
|
-
// The last emitted state should still have file1 at the top level, not
|
|
403
|
-
// moved under folder1.
|
|
404
|
-
const lastCall = mockOnChange.mock.calls.at(-1);
|
|
405
|
-
expect(lastCall?.[0]).toEqual([
|
|
406
|
-
{ id: "1.1", name: "file1", path: "/root/file1" },
|
|
407
|
-
{
|
|
408
|
-
id: "1.2",
|
|
409
|
-
name: "folder1",
|
|
410
|
-
isDirectory: true,
|
|
411
|
-
path: "/root/folder1",
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
id: "1.3",
|
|
415
|
-
name: "folder2",
|
|
416
|
-
isDirectory: true,
|
|
417
|
-
path: "/root/folder2",
|
|
418
|
-
},
|
|
419
|
-
]);
|
|
420
271
|
});
|
|
421
272
|
|
|
422
|
-
test("
|
|
423
|
-
|
|
273
|
+
test("does not update or refresh after a failed move", async () => {
|
|
274
|
+
sendRenameFileOrFolder.mockResolvedValueOnce({
|
|
424
275
|
success: false,
|
|
425
|
-
message: "
|
|
276
|
+
message: "Permission denied",
|
|
426
277
|
});
|
|
427
278
|
|
|
428
|
-
await
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
});
|
|
279
|
+
await tree.move([PRIMARY_FILE_ID], EXTERNAL_ROOT_ID);
|
|
280
|
+
|
|
281
|
+
expect(sendListFiles).not.toHaveBeenCalled();
|
|
282
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
433
283
|
expect(toast).toHaveBeenCalledWith({
|
|
434
284
|
title: "Failed",
|
|
435
|
-
description: "
|
|
285
|
+
description: "Permission denied",
|
|
436
286
|
});
|
|
437
287
|
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("moves files across roots and refreshes both parents", async () => {
|
|
291
|
+
await tree.expand(PRIMARY_ROOT_ID);
|
|
292
|
+
sendListFiles.mockClear();
|
|
293
|
+
|
|
294
|
+
await tree.move([PRIMARY_FILE_ID], EXTERNAL_ROOT_ID);
|
|
438
295
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
expect(mockOnChange).toHaveBeenCalledTimes(3);
|
|
296
|
+
expect(sendRenameFileOrFolder).toHaveBeenCalledWith({
|
|
297
|
+
path: "/root/file1",
|
|
298
|
+
newPath: "/external/file1",
|
|
443
299
|
});
|
|
300
|
+
expect(sendListFiles).toHaveBeenCalledWith({ path: "/root" });
|
|
301
|
+
expect(sendListFiles).toHaveBeenCalledWith({ path: "/external" });
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("refreshes configured roots when every node is collapsed", async () => {
|
|
305
|
+
await tree.refreshAll([]);
|
|
306
|
+
expect(sendListFiles).toHaveBeenCalledWith({ path: "/root" });
|
|
307
|
+
expect(sendListFiles).toHaveBeenCalledWith({ path: "/external" });
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("refreshes supplied open folders in addition to configured roots", async () => {
|
|
311
|
+
await tree.expand(PRIMARY_ROOT_ID);
|
|
312
|
+
sendListFiles.mockClear();
|
|
313
|
+
|
|
314
|
+
await tree.refreshAll([fileTreeNodeId("/root", "/root/folder1")]);
|
|
315
|
+
|
|
316
|
+
expect(sendListFiles).toHaveBeenCalledWith({ path: "/root" });
|
|
317
|
+
expect(sendListFiles).toHaveBeenCalledWith({ path: "/external" });
|
|
318
|
+
expect(sendListFiles).toHaveBeenCalledWith({ path: "/root/folder1" });
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("keeps existing children when a refresh fails", async () => {
|
|
322
|
+
await tree.expand(PRIMARY_ROOT_ID);
|
|
323
|
+
const beforeRefresh = onChange.mock.calls.at(-1)?.[0];
|
|
324
|
+
sendListFiles.mockRejectedValueOnce(new Error("Network error"));
|
|
325
|
+
|
|
326
|
+
await tree.refreshPath("/root" as FilePath);
|
|
327
|
+
|
|
328
|
+
expect(onChange.mock.calls.at(-1)?.[0]).toEqual(beforeRefresh);
|
|
329
|
+
});
|
|
444
330
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
331
|
+
test("namespaces duplicate paths under overlapping roots", async () => {
|
|
332
|
+
getRoots.mockResolvedValueOnce({
|
|
333
|
+
roots: [
|
|
334
|
+
{ path: "/repo", name: "repo", isPrimary: true },
|
|
335
|
+
{ path: "/repo/data", name: "Data", isPrimary: false },
|
|
336
|
+
],
|
|
451
337
|
});
|
|
338
|
+
sendListFiles.mockImplementation(async ({ path }: { path: string }) => ({
|
|
339
|
+
root: path,
|
|
340
|
+
files:
|
|
341
|
+
path === "/repo"
|
|
342
|
+
? [
|
|
343
|
+
{
|
|
344
|
+
id: "/repo/data",
|
|
345
|
+
name: "data",
|
|
346
|
+
path: "/repo/data",
|
|
347
|
+
isDirectory: true,
|
|
348
|
+
isMarimoFile: false,
|
|
349
|
+
children: [],
|
|
350
|
+
},
|
|
351
|
+
]
|
|
352
|
+
: [
|
|
353
|
+
{
|
|
354
|
+
id: "/repo/data/file.csv",
|
|
355
|
+
name: "file.csv",
|
|
356
|
+
path: "/repo/data/file.csv",
|
|
357
|
+
isDirectory: false,
|
|
358
|
+
isMarimoFile: false,
|
|
359
|
+
children: [],
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
}));
|
|
363
|
+
const overlapOnChange = vi.fn();
|
|
364
|
+
const overlapTree = new RequestingTree({
|
|
365
|
+
getRoots,
|
|
366
|
+
listFiles: sendListFiles,
|
|
367
|
+
createFileOrFolder: sendCreateFileOrFolder,
|
|
368
|
+
deleteFileOrFolder: sendDeleteFileOrFolder,
|
|
369
|
+
copyFileOrFolder: sendCopyFileOrFolder,
|
|
370
|
+
renameFileOrFolder: sendRenameFileOrFolder,
|
|
371
|
+
});
|
|
372
|
+
await overlapTree.initialize(overlapOnChange);
|
|
373
|
+
|
|
374
|
+
const primaryRootId = fileTreeNodeId("/repo", "/repo");
|
|
375
|
+
const nestedDirectoryId = fileTreeNodeId("/repo", "/repo/data");
|
|
376
|
+
const additionalRootId = fileTreeNodeId("/repo/data", "/repo/data");
|
|
377
|
+
expect(nestedDirectoryId).not.toBe(additionalRootId);
|
|
378
|
+
|
|
379
|
+
await overlapTree.expand(primaryRootId);
|
|
380
|
+
await overlapTree.expand(nestedDirectoryId);
|
|
381
|
+
await overlapTree.expand(additionalRootId);
|
|
382
|
+
|
|
383
|
+
const roots = overlapOnChange.mock.calls.at(-1)?.[0];
|
|
384
|
+
expect(roots[0].children[0].id).toBe(nestedDirectoryId);
|
|
385
|
+
expect(roots[1].id).toBe(additionalRootId);
|
|
386
|
+
expect(roots[0].children[0].children[0].id).toBe(
|
|
387
|
+
fileTreeNodeId("/repo", "/repo/data/file.csv"),
|
|
388
|
+
);
|
|
389
|
+
expect(roots[1].children[0].id).toBe(
|
|
390
|
+
fileTreeNodeId("/repo/data", "/repo/data/file.csv"),
|
|
391
|
+
);
|
|
452
392
|
});
|
|
453
393
|
|
|
454
|
-
describe("
|
|
455
|
-
test("
|
|
456
|
-
|
|
394
|
+
describe("primary-root paths", () => {
|
|
395
|
+
test("returns relative paths only inside the primary root", () => {
|
|
396
|
+
expect(tree.getPrimaryRelativePath("/root/src/file.py" as FilePath)).toBe(
|
|
397
|
+
"src/file.py",
|
|
398
|
+
);
|
|
399
|
+
expect(
|
|
400
|
+
tree.getPrimaryRelativePath("/rooted/file.py" as FilePath),
|
|
401
|
+
).toBeNull();
|
|
402
|
+
expect(
|
|
403
|
+
tree.getPrimaryRelativePath("/external/file.py" as FilePath),
|
|
404
|
+
).toBeNull();
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test("supports Windows path boundaries case-insensitively", async () => {
|
|
408
|
+
getRoots.mockResolvedValueOnce({
|
|
409
|
+
roots: [
|
|
410
|
+
{
|
|
411
|
+
path: "C:\\Users\\Test\\Project",
|
|
412
|
+
name: "Project",
|
|
413
|
+
isPrimary: true,
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
});
|
|
417
|
+
const windowsTree = new RequestingTree({
|
|
418
|
+
getRoots,
|
|
457
419
|
listFiles: sendListFiles,
|
|
458
420
|
createFileOrFolder: sendCreateFileOrFolder,
|
|
459
421
|
deleteFileOrFolder: sendDeleteFileOrFolder,
|
|
460
422
|
copyFileOrFolder: sendCopyFileOrFolder,
|
|
461
423
|
renameFileOrFolder: sendRenameFileOrFolder,
|
|
462
424
|
});
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
425
|
+
await windowsTree.initialize(vi.fn());
|
|
426
|
+
|
|
427
|
+
expect(
|
|
428
|
+
windowsTree.getPrimaryRelativePath(
|
|
429
|
+
"c:\\users\\test\\project\\src\\file.py" as FilePath,
|
|
430
|
+
),
|
|
431
|
+
).toBe("src\\file.py");
|
|
432
|
+
expect(
|
|
433
|
+
windowsTree.getPrimaryRelativePath(
|
|
434
|
+
"C:\\Users\\Test\\Project-old\\file.py" as FilePath,
|
|
435
|
+
),
|
|
436
|
+
).toBeNull();
|
|
475
437
|
});
|
|
476
438
|
|
|
477
|
-
test("
|
|
478
|
-
const
|
|
439
|
+
test("treats backslashes in POSIX paths as filename characters", async () => {
|
|
440
|
+
const rootPath = "/root/data\\archive";
|
|
441
|
+
const filePath = `${rootPath}/file.txt`;
|
|
442
|
+
getRoots.mockResolvedValueOnce({
|
|
443
|
+
roots: [{ path: rootPath, name: "archive", isPrimary: true }],
|
|
444
|
+
});
|
|
445
|
+
sendListFiles.mockResolvedValueOnce({
|
|
446
|
+
root: rootPath,
|
|
447
|
+
files: [
|
|
448
|
+
{
|
|
449
|
+
id: filePath,
|
|
450
|
+
path: filePath,
|
|
451
|
+
name: "file.txt",
|
|
452
|
+
isDirectory: false,
|
|
453
|
+
isMarimoFile: false,
|
|
454
|
+
children: [],
|
|
455
|
+
},
|
|
456
|
+
],
|
|
457
|
+
});
|
|
458
|
+
const posixTree = new RequestingTree({
|
|
459
|
+
getRoots,
|
|
479
460
|
listFiles: sendListFiles,
|
|
480
461
|
createFileOrFolder: sendCreateFileOrFolder,
|
|
481
462
|
deleteFileOrFolder: sendDeleteFileOrFolder,
|
|
482
463
|
copyFileOrFolder: sendCopyFileOrFolder,
|
|
483
464
|
renameFileOrFolder: sendRenameFileOrFolder,
|
|
484
465
|
});
|
|
466
|
+
await posixTree.initialize(vi.fn());
|
|
485
467
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
root: "C:\\Users\\test\\project",
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
await tree.initialize(vi.fn());
|
|
492
|
-
|
|
493
|
-
const relativePath = tree.relativeFromRoot(
|
|
494
|
-
"C:\\Users\\test\\project\\src\\file.py" as FilePath,
|
|
468
|
+
expect(posixTree.getPrimaryRelativePath(filePath as FilePath)).toBe(
|
|
469
|
+
"file.txt",
|
|
495
470
|
);
|
|
496
|
-
|
|
471
|
+
await posixTree.copy(fileTreeNodeId(rootPath, filePath), "copy.txt");
|
|
472
|
+
expect(sendCopyFileOrFolder).toHaveBeenCalledWith({
|
|
473
|
+
path: filePath,
|
|
474
|
+
newPath: `${rootPath}/copy.txt`,
|
|
475
|
+
});
|
|
497
476
|
});
|
|
498
477
|
|
|
499
|
-
test("
|
|
500
|
-
|
|
501
|
-
"
|
|
478
|
+
test("uses configured names in root-qualified display paths", () => {
|
|
479
|
+
expect(tree.getDisplayPath("/root/data.csv" as FilePath)).toBe(
|
|
480
|
+
"data.csv",
|
|
502
481
|
);
|
|
503
|
-
expect(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const relativePath = requestingTree.relativeFromRoot("/root" as FilePath);
|
|
508
|
-
expect(relativePath).toBe("/root");
|
|
482
|
+
expect(tree.getDisplayPath("/external/data.csv" as FilePath)).toBe(
|
|
483
|
+
"Shared/data.csv",
|
|
484
|
+
);
|
|
485
|
+
expect(tree.getDisplayPath("/external" as FilePath)).toBe("Shared");
|
|
509
486
|
});
|
|
510
487
|
});
|
|
511
488
|
});
|