Skip to content

Commit 59543ec

Browse files
szuendDevtools-frontend LUCI CQ
authored and
Devtools-frontend LUCI CQ
committed
[persistence] Migrate WorkspaceSettingsTab to UI vision
This CL converts `WorkspaceSettingsTab` into a presenter with a view function. Conceptually, the view function is rather trivial: It renders the exclude pattern regex setting and one `EditFileSystemView` widget per file system. Note that the number of file systems is generally rather small, so we don't bother in maintaining a list of them in the presenter and surgically add/remove entries. Instead, we re-calculate the full list on every update. Drive-by: Support RegExp settings in `bindToSetting`. R=dsv@chromium.org Fixed: 407736174 Change-Id: Ibfcc332f495ca58af516219183d93e39e8e138b5 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6563568 Commit-Queue: Simon Zünd <szuend@chromium.org> Reviewed-by: Danil Somsikov <dsv@chromium.org>
1 parent e90d116 commit 59543ec

File tree

8 files changed

+272
-133
lines changed

8 files changed

+272
-133
lines changed

front_end/models/persistence/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ ts_library("unittests") {
106106
"PersistenceAction.test.ts",
107107
"PersistenceImpl.test.ts",
108108
"PlatformFileSystem.test.ts",
109+
"WorkspaceSettingsTab.test.ts",
109110
]
110111

111112
deps = [

front_end/models/persistence/EditFileSystemView.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describeWithEnvironment('EditFileSystemView view', () => {
5959
describeWithEnvironment('EditFileSystemView widget', () => {
6060
async function setup(initialExcludedFolders: Set<string>) {
6161
const view = createViewFunctionStub(EditFileSystemView);
62-
const widget = new EditFileSystemView(view);
62+
const widget = new EditFileSystemView(undefined, view);
6363
const fileSystem = sinon.createStubInstance(Persistence.IsolatedFileSystem.IsolatedFileSystem);
6464
fileSystem.path.returns(urlString`file:///home/user`);
6565
fileSystem.excludedFolders.returns(initialExcludedFolders as Set<Platform.DevToolsPath.EncodedPathString>);

front_end/models/persistence/EditFileSystemView.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ export class EditFileSystemView extends UI.Widget.VBox {
135135
#excludedFolderPaths: PathWithStatus[] = [];
136136
readonly #view: View;
137137

138-
constructor(view: View = DEFAULT_VIEW) {
139-
super();
138+
constructor(element: HTMLElement|undefined, view: View = DEFAULT_VIEW) {
139+
super(undefined, undefined, element);
140140
this.#view = view;
141141
}
142142

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright 2025 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as Platform from '../../core/platform/platform.js';
6+
import {assertScreenshot, renderElementIntoDOM} from '../../testing/DOMHelpers.js';
7+
import {createFakeRegExpSetting, describeWithEnvironment} from '../../testing/EnvironmentHelpers.js';
8+
import {createViewFunctionStub} from '../../testing/ViewFunctionHelpers.js';
9+
10+
import * as Persistence from './persistence.js';
11+
12+
const {DEFAULT_VIEW, WorkspaceSettingsTab} = Persistence.WorkspaceSettingsTab;
13+
const {urlString} = Platform.DevToolsPath;
14+
15+
describeWithEnvironment('WorkspaceSettingsTab view', () => {
16+
it('renders the exclude regex setting and one card per mapped file system', async () => {
17+
const target = document.createElement('div');
18+
renderElementIntoDOM(target);
19+
20+
const fileSystem = sinon.createStubInstance(Persistence.IsolatedFileSystem.IsolatedFileSystem);
21+
fileSystem.path.returns(urlString`file:///home/user/foo`);
22+
fileSystem.excludedFolders.returns(new Set());
23+
24+
DEFAULT_VIEW(
25+
{
26+
excludePatternSetting: createFakeRegExpSetting('fake-exclude-pattern', 'node_modules|.git'),
27+
fileSystems: [{displayName: 'foo', fileSystem}],
28+
onAddClicked: () => {},
29+
onRemoveClicked: () => {},
30+
},
31+
{}, target);
32+
33+
await assertScreenshot('persistence/workspace_settings_tab.png');
34+
});
35+
});
36+
37+
describeWithEnvironment('WorkspaceSettingsTab widget', () => {
38+
function createStubFileSystem(path: string) {
39+
const fileSystem = sinon.createStubInstance(Persistence.IsolatedFileSystem.IsolatedFileSystem);
40+
fileSystem.path.returns(urlString(path));
41+
return fileSystem;
42+
}
43+
44+
async function setup(initialFileSystemPaths: string[] = []) {
45+
const fileSystemManager =
46+
Persistence.IsolatedFileSystemManager.IsolatedFileSystemManager.instance({forceNew: true});
47+
const fileSystems = initialFileSystemPaths.map(createStubFileSystem);
48+
sinon.stub(fileSystemManager, 'fileSystems').returns(fileSystems);
49+
const addFileSystemStub = sinon.stub(fileSystemManager, 'addFileSystem');
50+
const removeFileSystemStub = sinon.stub(fileSystemManager, 'removeFileSystem');
51+
const networkPersistenceManager =
52+
sinon.createStubInstance(Persistence.NetworkPersistenceManager.NetworkPersistenceManager);
53+
sinon.stub(Persistence.NetworkPersistenceManager.NetworkPersistenceManager, 'instance')
54+
.returns(networkPersistenceManager);
55+
56+
const view = createViewFunctionStub(WorkspaceSettingsTab);
57+
const widget = new WorkspaceSettingsTab(view);
58+
59+
const container = document.createElement('div');
60+
renderElementIntoDOM(container);
61+
widget.markAsRoot();
62+
widget.show(container);
63+
await view.nextInput;
64+
65+
return {view, fileSystemManager, fileSystems, addFileSystemStub, removeFileSystemStub, networkPersistenceManager};
66+
}
67+
68+
it('uses the exclude folder regex pattern', async () => {
69+
const {view, fileSystemManager} = await setup();
70+
71+
assert.strictEqual(view.input.excludePatternSetting, fileSystemManager.workspaceFolderExcludePatternSetting());
72+
});
73+
74+
it('shows the directory name as the card heading', async () => {
75+
const {view} = await setup(['file:///home/user/foo']);
76+
77+
assert.lengthOf(view.input.fileSystems, 1);
78+
assert.strictEqual(view.input.fileSystems[0].displayName, 'foo');
79+
});
80+
81+
it('sorts file systems alphabetically', async () => {
82+
const {view} = await setup(['file:///home/user/zoo', 'file:///home/user/foo']);
83+
84+
assert.lengthOf(view.input.fileSystems, 2);
85+
assert.strictEqual(view.input.fileSystems[0].fileSystem.path(), 'file:///home/user/foo');
86+
assert.strictEqual(view.input.fileSystems[1].fileSystem.path(), 'file:///home/user/zoo');
87+
});
88+
89+
it('listens to FileSystemAdded events', async () => {
90+
const {view, fileSystems, fileSystemManager} = await setup();
91+
const fileSystem = createStubFileSystem('file:///home/user/dev/foo');
92+
fileSystems.push(fileSystem);
93+
94+
fileSystemManager.dispatchEventToListeners(
95+
Persistence.IsolatedFileSystemManager.Events.FileSystemAdded, fileSystem);
96+
await view.nextInput;
97+
98+
assert.lengthOf(view.input.fileSystems, 1);
99+
assert.strictEqual(view.input.fileSystems[0].fileSystem, fileSystem);
100+
});
101+
102+
it('listens to FileSystemRemoved events', async () => {
103+
const {view, fileSystems, fileSystemManager} = await setup(['file:///home/user/foo']);
104+
105+
const fileSystem = fileSystems[0];
106+
fileSystems.splice(0, 1);
107+
fileSystemManager.dispatchEventToListeners(
108+
Persistence.IsolatedFileSystemManager.Events.FileSystemRemoved, fileSystem);
109+
await view.nextInput;
110+
111+
assert.lengthOf(view.input.fileSystems, 0);
112+
});
113+
114+
it('filters out the network persistence file system project', async () => {
115+
const {view, fileSystems, fileSystemManager, networkPersistenceManager} = await setup();
116+
const fileSystem = createStubFileSystem('file:///special-network-fs');
117+
fileSystems.push(fileSystem);
118+
const project = sinon.createStubInstance(Persistence.FileSystemWorkspaceBinding.FileSystem);
119+
project.fileSystemPath.returns(urlString`file:///special-network-fs`);
120+
networkPersistenceManager.project.returns(project);
121+
sinon.stub(fileSystemManager, 'fileSystem').returns(fileSystem);
122+
123+
fileSystemManager.dispatchEventToListeners(
124+
Persistence.IsolatedFileSystemManager.Events.FileSystemAdded, fileSystem);
125+
await view.nextInput;
126+
127+
assert.lengthOf(view.input.fileSystems, 0);
128+
});
129+
130+
it('wires up addFileSystem', async () => {
131+
const {view, addFileSystemStub} = await setup();
132+
133+
view.input.onAddClicked();
134+
135+
sinon.assert.calledOnce(addFileSystemStub);
136+
});
137+
138+
it('wires up removeFileSystem', async () => {
139+
const {view, fileSystemManager, removeFileSystemStub} = await setup(['file:///home/user/foo']);
140+
const fileSystem = fileSystemManager.fileSystems()[0];
141+
142+
view.input.onRemoveClicked(fileSystem as Persistence.IsolatedFileSystem.IsolatedFileSystem);
143+
144+
sinon.assert.calledOnceWithExactly(removeFileSystemStub, fileSystem);
145+
});
146+
});

0 commit comments

Comments
 (0)