Skip to content

Commit 26ac4a0

Browse files
danilsomsikovDevtools-frontend LUCI CQ
authored andcommitted
Render empty states declaratively in the SearchView
SearchResultsPane would be migrated in the next CL Bug: 407750483 Change-Id: I304698c09fd9f958b2b4945b6ed3dd817fe7fc28 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6959471 Auto-Submit: Danil Somsikov <[email protected]> Commit-Queue: Philip Pfaffe <[email protected]> Reviewed-by: Philip Pfaffe <[email protected]>
1 parent aca4faa commit 26ac4a0

File tree

2 files changed

+47
-58
lines changed

2 files changed

+47
-58
lines changed

front_end/panels/search/SearchView.test.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55
import * as Common from '../../core/common/common.js';
66
import type * as Workspace from '../../models/workspace/workspace.js';
7-
import {dispatchClickEvent, dispatchInputEvent, dispatchKeyDownEvent} from '../../testing/DOMHelpers.js';
7+
import {
8+
dispatchClickEvent,
9+
dispatchInputEvent,
10+
dispatchKeyDownEvent,
11+
renderElementIntoDOM
12+
} from '../../testing/DOMHelpers.js';
813
import {describeWithEnvironment} from '../../testing/EnvironmentHelpers.js';
914
import * as UI from '../../ui/legacy/legacy.js';
1015

@@ -100,6 +105,17 @@ class TestSearchView extends Search.SearchView.SearchView {
100105
}
101106

102107
describeWithEnvironment('SearchView', () => {
108+
it('has a standard placeholder when nothing has been searched yet', async () => {
109+
const fakeScope = new FakeSearchScope();
110+
const searchView = new TestSearchView(() => fakeScope);
111+
renderElementIntoDOM(searchView);
112+
113+
await searchView.updateComplete;
114+
assert.deepEqual(searchView.contentElement.querySelector('.empty-state-header')?.textContent, 'No search results');
115+
assert.isTrue(
116+
searchView.contentElement.querySelector('.empty-state-description')?.textContent?.includes('Type and press '));
117+
});
118+
103119
it('calls the search scope with the search config provided by the user via the UI', async () => {
104120
const fakeScope = new FakeSearchScope();
105121
const searchView = new TestSearchView(() => fakeScope);
@@ -115,44 +131,40 @@ describeWithEnvironment('SearchView', () => {
115131
it('notifies the user when no search results were found', async () => {
116132
const fakeScope = new FakeSearchScope();
117133
const searchView = new TestSearchView(() => fakeScope);
134+
renderElementIntoDOM(searchView);
118135

119136
searchView.triggerSearch('a query', true, true);
120137

121138
const {searchFinishedCallback} = await fakeScope.performSearchCalledPromise;
122139
searchFinishedCallback(/* finished */ true);
123140

141+
await searchView.updateComplete;
124142
assert.deepEqual(searchView.contentElement.querySelector('.empty-state-header')?.textContent, 'No matches found');
125143
assert.deepEqual(
126144
searchView.contentElement.querySelector('.empty-state-description')?.textContent,
127145
'Nothing matched your search query');
128146
});
129147

130-
it('has a standard placeholder when nothing has been searched yet', async () => {
131-
const fakeScope = new FakeSearchScope();
132-
const searchView = new TestSearchView(() => fakeScope);
133-
134-
assert.deepEqual(searchView.contentElement.querySelector('.empty-state-header')?.textContent, 'No search results');
135-
assert.isTrue(
136-
searchView.contentElement.querySelector('.empty-state-description')?.textContent?.includes('Type and press '));
137-
});
138-
139148
it('has a standard placeholder when search has been cleared', async () => {
140149
const fakeScope = new FakeSearchScope();
141150
const searchView = new TestSearchView(() => fakeScope);
151+
renderElementIntoDOM(searchView);
142152

143153
searchView.triggerSearch('a query', true, true);
144154

145155
const {searchFinishedCallback} = await fakeScope.performSearchCalledPromise;
146156
searchFinishedCallback(/* finished */ true);
147157

148158
// After search, shows that no matches were found.
159+
await searchView.updateComplete;
149160
assert.deepEqual(searchView.contentElement.querySelector('.empty-state-header')?.textContent, 'No matches found');
150161

151162
const clearButton = searchView.contentElement.querySelector('.clear-button');
152163
assert.exists(clearButton);
153164
dispatchClickEvent(clearButton);
154165

155166
// After clearing, shows standard placeholder.
167+
await searchView.updateComplete;
156168
assert.deepEqual(searchView.contentElement.querySelector('.empty-state-header')?.textContent, 'No search results');
157169
assert.isTrue(
158170
searchView.contentElement.querySelector('.empty-state-description')?.textContent?.includes('Type and press '));

front_end/panels/search/SearchView.ts

Lines changed: 25 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ const UIStrings = {
107107
const str_ = i18n.i18n.registerUIStrings('panels/search/SearchView.ts', UIStrings);
108108
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
109109
const {ref} = Directives;
110+
const {widgetConfig} = UI.Widget;
110111

111112
interface SearchViewInput {
112113
query: string;
@@ -115,6 +116,7 @@ interface SearchViewInput {
115116
isRegex: boolean;
116117
searchMessage: string;
117118
searchResultsMessage: string;
119+
searchResultsPane: SearchResultsPane|null;
118120
progress: Common.Progress.Progress|null;
119121
onQueryChange: (query: string) => void;
120122
onQueryKeyDown: (evt: KeyboardEvent) => void;
@@ -126,11 +128,7 @@ interface SearchViewInput {
126128
onClearSearch: () => void;
127129
}
128130

129-
interface SearchViewOutput {
130-
searchResultsElement?: HTMLElement;
131-
}
132-
133-
type View = (input: SearchViewInput, output: SearchViewOutput, target: HTMLElement) => void;
131+
type View = (input: SearchViewInput, output: object, target: HTMLElement) => void;
134132

135133
export const DEFAULT_VIEW: View = (input, output, target) => {
136134
const {
@@ -139,6 +137,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
139137
matchCase,
140138
isRegex,
141139
searchMessage,
140+
searchResultsPane,
142141
searchResultsMessage,
143142
progress,
144143
onQueryChange,
@@ -150,6 +149,18 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
150149
onRefresh,
151150
onClearSearch,
152151
} = input;
152+
let header = '', text = '';
153+
if (!query) {
154+
header = i18nString(UIStrings.noSearchResult);
155+
text = i18nString(
156+
UIStrings.typeAndPressSToSearch,
157+
{PH1: UI.KeyboardShortcut.KeyboardShortcut.shortcutToString(UI.KeyboardShortcut.Keys.Enter)});
158+
} else if (progress) {
159+
header = i18nString(UIStrings.searching);
160+
} else if (!searchResultsPane) {
161+
header = i18nString(UIStrings.noMatchesFound);
162+
text = i18nString(UIStrings.nothingMatchedTheQuery);
163+
}
153164
// clang-format off
154165
render(html`
155166
<style>${searchViewStyles}</style>
@@ -224,8 +235,13 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
224235
} as Buttons.Button.ButtonData}></devtools-button>
225236
</devtools-toolbar>
226237
</div>
227-
<div class="search-results" @keydown=${onPanelKeyDown}
228-
${ref(e => {output.searchResultsElement = e as HTMLElement;})}>
238+
<div class="search-results" @keydown=${onPanelKeyDown}>
239+
${searchResultsPane
240+
? html`<devtools-widget .widgetConfig=${widgetConfig(UI.Widget.VBox)}>
241+
${searchResultsPane.element}
242+
</devtools-widget>`
243+
: html`<devtools-widget .widgetConfig=${widgetConfig(UI.EmptyWidget.EmptyWidget, {header, text})}>
244+
</devtools-widget>`}
229245
</div>
230246
<div class="search-toolbar-summary" @keydown=${onPanelKeyDown}>
231247
<div class="search-message">${searchMessage}</div>
@@ -249,13 +265,10 @@ export class SearchView extends UI.Widget.VBox {
249265
#searchResultsCount: number;
250266
#nonEmptySearchResultsCount: number;
251267
#searchingView: UI.Widget.Widget|null;
252-
#notFoundView: UI.Widget.Widget|null;
253268
#searchConfig: Workspace.SearchConfig.SearchConfig|null;
254269
#pendingSearchConfig: Workspace.SearchConfig.SearchConfig|null;
255270
#searchResultsPane: SearchResultsPane|null;
256271
#progress: Common.Progress.Progress|null;
257-
#visiblePane: UI.Widget.Widget|null;
258-
#searchResultsElement!: HTMLElement;
259272
#query: string;
260273
#matchCase = false;
261274
#isRegex = false;
@@ -272,7 +285,6 @@ export class SearchView extends UI.Widget.VBox {
272285
// result added.
273286
#throttler: Common.Throttler.Throttler;
274287
#pendingSearchResults: SearchResult[] = [];
275-
#emptyStartView: UI.EmptyWidget.EmptyWidget;
276288

277289
constructor(settingKey: string, throttler: Common.Throttler.Throttler, view = DEFAULT_VIEW) {
278290
super({
@@ -290,12 +302,10 @@ export class SearchView extends UI.Widget.VBox {
290302
this.#searchResultsCount = 0;
291303
this.#nonEmptySearchResultsCount = 0;
292304
this.#searchingView = null;
293-
this.#notFoundView = null;
294305
this.#searchConfig = null;
295306
this.#pendingSearchConfig = null;
296307
this.#searchResultsPane = null;
297308
this.#progress = null;
298-
this.#visiblePane = null;
299309
this.#throttler = throttler;
300310

301311
this.#advancedSearchConfig = Common.Settings.Settings.instance().createLocalSetting(
@@ -305,12 +315,6 @@ export class SearchView extends UI.Widget.VBox {
305315
this.#load();
306316
this.performUpdate();
307317
this.#searchScope = null;
308-
309-
this.#emptyStartView = new UI.EmptyWidget.EmptyWidget(
310-
i18nString(UIStrings.noSearchResult), i18nString(UIStrings.typeAndPressSToSearch, {
311-
PH1: UI.KeyboardShortcut.KeyboardShortcut.shortcutToString(UI.KeyboardShortcut.Keys.Enter)
312-
}));
313-
this.#showPane(this.#emptyStartView);
314318
}
315319

316320
override performUpdate(): void {
@@ -320,6 +324,7 @@ export class SearchView extends UI.Widget.VBox {
320324
matchCase: this.#matchCase,
321325
isRegex: this.#isRegex,
322326
searchMessage: this.#searchMessage,
327+
searchResultsPane: this.#searchResultsPane,
323328
searchResultsMessage: this.#searchResultsMessage,
324329
progress: this.#progress,
325330
onQueryChange: (query: string) => {
@@ -333,12 +338,9 @@ export class SearchView extends UI.Widget.VBox {
333338
onRefresh: this.#onRefresh.bind(this),
334339
onClearSearch: this.#onClearSearch.bind(this),
335340
};
336-
const output: SearchViewOutput = {};
341+
const output = {};
337342
this.#view(input, output, this.contentElement);
338343
this.#focusSearchInput = false;
339-
if (output.searchResultsElement) {
340-
this.#searchResultsElement = output.searchResultsElement;
341-
}
342344
}
343345

344346
#onToggleRegex(): void {
@@ -416,7 +418,6 @@ export class SearchView extends UI.Widget.VBox {
416418
this.requestUpdate();
417419
this.#save();
418420
this.focus();
419-
this.#showPane(this.#emptyStartView);
420421
}
421422

422423
#onSearchResult(searchId: number, searchResult: SearchResult): void {
@@ -429,7 +430,6 @@ export class SearchView extends UI.Widget.VBox {
429430
}
430431
if (!this.#searchResultsPane) {
431432
this.#searchResultsPane = this.createSearchResultsPane();
432-
this.#showPane(this.#searchResultsPane);
433433
}
434434
this.#pendingSearchResults.push(searchResult);
435435
void this.#throttler.schedule(async () => this.#addPendingSearchResults());
@@ -453,9 +453,6 @@ export class SearchView extends UI.Widget.VBox {
453453
if (searchId !== this.#searchId || !this.#progress) {
454454
return;
455455
}
456-
if (!this.#searchResultsPane) {
457-
this.#nothingFound();
458-
}
459456
this.#progress = null;
460457
this.#searchFinished(finished);
461458
this.#searchConfig = null;
@@ -479,7 +476,6 @@ export class SearchView extends UI.Widget.VBox {
479476

480477
#resetSearch(): void {
481478
this.#stopSearch();
482-
this.#showPane(null);
483479
this.#searchResultsPane = null;
484480
this.#searchMessage = '';
485481
this.#searchResultsMessage = '';
@@ -503,7 +499,6 @@ export class SearchView extends UI.Widget.VBox {
503499
if (!this.#searchingView) {
504500
this.#searchingView = new UI.EmptyWidget.EmptyWidget(i18nString(UIStrings.searching), '');
505501
}
506-
this.#showPane(this.#searchingView);
507502
this.#searchMessage = i18nString(UIStrings.searching);
508503
this.performUpdate();
509504
this.#updateSearchResultsMessage();
@@ -526,24 +521,6 @@ export class SearchView extends UI.Widget.VBox {
526521
this.performUpdate();
527522
}
528523

529-
#showPane(panel: UI.Widget.Widget|null): void {
530-
if (this.#visiblePane) {
531-
this.#visiblePane.detach();
532-
}
533-
if (panel) {
534-
panel.show(this.#searchResultsElement);
535-
}
536-
this.#visiblePane = panel;
537-
}
538-
539-
#nothingFound(): void {
540-
if (!this.#notFoundView) {
541-
this.#notFoundView = new UI.EmptyWidget.EmptyWidget(
542-
i18nString(UIStrings.noMatchesFound), i18nString(UIStrings.nothingMatchedTheQuery));
543-
}
544-
this.#showPane(this.#notFoundView);
545-
}
546-
547524
#addSearchResult(searchResult: SearchResult): void {
548525
const matchesCount = searchResult.matchesCount();
549526
this.#searchMatchesCount += matchesCount;

0 commit comments

Comments
 (0)