From 7942350474c0480f63487793188e04cd35c89e07 Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 14 Apr 2023 10:14:28 -0700 Subject: [PATCH 1/3] Add tests for notebook search Fixes #179985 --- .../search/test/browser/searchActions.test.ts | 45 +--- .../search/test/browser/searchModel.test.ts | 23 +- .../browser/searchNotebookHelpers.test.ts | 208 +++++++++++++++--- .../search/test/browser/searchResult.test.ts | 28 +-- .../search/test/browser/searchTestCommon.ts | 54 +++++ .../search/test/browser/searchViewlet.test.ts | 23 +- 6 files changed, 239 insertions(+), 142 deletions(-) create mode 100644 src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts diff --git a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index f27540fe7c831..f3a58a4d9a067 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -5,12 +5,9 @@ import * as assert from 'assert'; import { Keybinding } from 'vs/base/common/keybindings'; -import { isWindows, OS } from 'vs/base/common/platform'; +import { OS } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IModelService } from 'vs/editor/common/services/model'; -import { ModelService } from 'vs/editor/common/services/modelService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; @@ -18,13 +15,9 @@ import { IFileMatch, QueryType } from 'vs/workbench/services/search/common/searc import { getElementToFocusAfterRemoved, getLastNodeFromSameType } from 'vs/workbench/contrib/search/browser/searchActionsRemoveReplace'; import { FileMatch, FileMatchOrMatch, FolderMatch, Match, SearchModel } from 'vs/workbench/contrib/search/browser/searchModel'; import { MockObjectTree } from 'vs/workbench/contrib/search/test/browser/mockSearchTree'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { ILabelService } from 'vs/platform/label/common/label'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; -import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { createFileUriFromPathFromRoot, stubModelService, stubNotebookEditorService } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; suite('Search Actions', () => { @@ -126,27 +119,6 @@ suite('Search Actions', () => { }, undefined, undefined, folderMatch, rawMatch, null); } - function createFileUriFromPathFromRoot(path?: string): URI { - const rootName = getRootName(); - if (path) { - return URI.file(`${rootName}${path}`); - } else { - if (isWindows) { - return URI.file(`${rootName}/`); - } else { - return URI.file(rootName); - } - } - } - - function getRootName(): string { - if (isWindows) { - return 'c:'; - } else { - return ''; - } - } - function aMatch(fileMatch: FileMatch): Match { const line = ++counter; const match = new Match( @@ -172,17 +144,4 @@ suite('Search Actions', () => { function aTree(elements: FileMatchOrMatch[]): any { return new MockObjectTree(elements); } - - function stubModelService(instantiationService: TestInstantiationService): IModelService { - instantiationService.stub(IThemeService, new TestThemeService()); - const config = new TestConfigurationService(); - config.setUserConfiguration('search', { searchOnType: true, experimental: { notebookSearch: false } }); - instantiationService.stub(IConfigurationService, config); - return instantiationService.createInstance(ModelService); - } - - function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { - instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); - return instantiationService.createInstance(NotebookEditorWidgetService); - } }); diff --git a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index ca89c3854ae3e..082ab273eff1a 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -23,12 +23,12 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { isWindows } from 'vs/base/common/platform'; import { ILabelService } from 'vs/platform/label/common/label'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; +import { createFileUriFromPathFromRoot, getRootName } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; const nullEvent = new class { id: number = -1; @@ -327,27 +327,6 @@ suite('SearchModel', () => { return { resource: createFileUriFromPathFromRoot(resource), results }; } - function createFileUriFromPathFromRoot(path?: string): URI { - const rootName = getRootName(); - if (path) { - return URI.file(`${rootName}${path}`); - } else { - if (isWindows) { - return URI.file(`${rootName}/`); - } else { - return URI.file(rootName); - } - } - } - - function getRootName(): string { - if (isWindows) { - return 'c:'; - } else { - return ''; - } - } - function stub(arg1: any, arg2: any, arg3: any): sinon.SinonStub { const stub = sinon.stub(arg1, arg2).callsFake(arg3); restoreStubs.push(stub); diff --git a/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts b/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts index 7bd66dfc9a6bc..2df451b04d4c8 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts @@ -6,25 +6,123 @@ import * as assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch, IReadonlyTextBuffer } from 'vs/editor/common/model'; -import { ISearchRange } from 'vs/workbench/services/search/common/search'; -import { CellFindMatchWithIndex, ICellViewModel, CellWebviewFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { IFileMatch, ISearchRange, ITextSearchMatch, QueryType } from 'vs/workbench/services/search/common/search'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { contentMatchesToTextSearchMatches } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; +import { contentMatchesToTextSearchMatches, webviewMatchesToTextSearchMatches } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; +import { CellFindMatchModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; +import { CellMatch, FileMatch, FolderMatch, SearchModel, textSearchMatchesToNotebookMatches } from 'vs/workbench/contrib/search/browser/searchModel'; +import { URI } from 'vs/base/common/uri'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { createFileUriFromPathFromRoot, stubModelService, stubNotebookEditorService } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; +import { IModelService } from 'vs/editor/common/services/model'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; suite('searchNotebookHelpers', () => { + let instantiationService: TestInstantiationService; + let mdCellFindMatch: CellFindMatchModel; + let codeCellFindMatch: CellFindMatchModel; + let mdInputCell: ICellViewModel; + let codeCell: ICellViewModel; + + let markdownContentResults: ITextSearchMatch[]; + let codeContentResults: ITextSearchMatch[]; + let codeWebviewResults: ITextSearchMatch[]; + let counter: number = 0; setup(() => { + + instantiationService = new TestInstantiationService(); + instantiationService.stub(IModelService, stubModelService(instantiationService)); + instantiationService.stub(INotebookEditorService, stubNotebookEditorService(instantiationService)); + mdInputCell = { + cellKind: CellKind.Markup, textBuffer: { + getLineContent(lineNumber: number): string { + if (lineNumber === 1) { + return '# Hello World Test'; + } else { + return ''; + } + } + } + } as ICellViewModel; + + const findMatchMds = [new FindMatch(new Range(1, 15, 1, 19), ['Test'])]; + codeCell = { + cellKind: CellKind.Code, textBuffer: { + getLineContent(lineNumber: number): string { + if (lineNumber === 1) { + return 'print("test! testing!!")'; + } else if (lineNumber === 2) { + return 'print("this is a Test")'; + } else { + return ''; + } + } + } + } as ICellViewModel; + const findMatchCodeCells = + [new FindMatch(new Range(1, 8, 1, 12), ['test']), + new FindMatch(new Range(1, 14, 1, 18), ['test']), + new FindMatch(new Range(2, 18, 2, 22), ['Test']) + ]; + + const webviewMatches = [{ + index: 0, + searchPreviewInfo: { + line: 'test! testing!!', + range: { + start: 1, + end: 5 + } + } + }, + { + index: 1, + searchPreviewInfo: { + line: 'test! testing!!', + range: { + start: 7, + end: 11 + } + } + }, + { + index: 3, + searchPreviewInfo: { + line: 'this is a Test', + range: { + start: 11, + end: 15 + } + } + } + + ]; + + + mdCellFindMatch = new CellFindMatchModel( + mdInputCell, + 0, + findMatchMds, + [], + ); + + codeCellFindMatch = new CellFindMatchModel( + codeCell, + 5, + findMatchCodeCells, + webviewMatches + ); + }); suite('notebookEditorMatchesToTextSearchResults', () => { function assertRangesEqual(actual: ISearchRange | ISearchRange[], expected: ISearchRange[]) { if (!Array.isArray(actual)) { - // All of these tests are for arrays... - throw new Error('Expected array of ranges'); + actual = [actual]; } assert.strictEqual(actual.length, expected.length); - - // These are sometimes Range, sometimes SearchRange actual.forEach((r, i) => { const expectedRange = expected[i]; assert.deepStrictEqual( @@ -33,33 +131,79 @@ suite('searchNotebookHelpers', () => { }); } - test('simple', () => { - const cell = { - cellKind: CellKind.Code, textBuffer: { - getLineContent(lineNumber: number): string { - return 'test'; - } - } - } as ICellViewModel; - - const findMatch = new FindMatch(new Range(5, 1, 5, 2), null); - const cellFindMatchWithIndex: CellFindMatchWithIndex = { - cell, - index: 0, - length: 5, - getMatch(index: number): FindMatch | CellWebviewFindMatch { - return findMatch; - }, - contentMatches: [findMatch], - webviewMatches: [] - }; + test('convert CellFindMatchModel to ITextSearchMatch and check results', () => { + markdownContentResults = contentMatchesToTextSearchMatches(mdCellFindMatch.contentMatches, mdInputCell); + codeContentResults = contentMatchesToTextSearchMatches(codeCellFindMatch.contentMatches, codeCell); + codeWebviewResults = webviewMatchesToTextSearchMatches(codeCellFindMatch.webviewMatches); + + assert.strictEqual(markdownContentResults.length, 1); + assert.strictEqual(markdownContentResults[0].preview.text, '# Hello World Test\n'); + assertRangesEqual(markdownContentResults[0].preview.matches, [new Range(0, 14, 0, 18)]); + assertRangesEqual(markdownContentResults[0].ranges, [new Range(0, 14, 0, 18)]); + - const results = contentMatchesToTextSearchMatches(cellFindMatchWithIndex.contentMatches, cell); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].preview.text, 'test\n'); - assertRangesEqual(results[0].preview.matches, [new Range(0, 0, 0, 1)]); - assertRangesEqual(results[0].ranges, [new Range(4, 0, 4, 1)]); + assert.strictEqual(codeContentResults.length, 2); + assert.strictEqual(codeContentResults[0].preview.text, 'print("test! testing!!")\n'); + assert.strictEqual(codeContentResults[1].preview.text, 'print("this is a Test")\n'); + assertRangesEqual(codeContentResults[0].preview.matches, [new Range(0, 7, 0, 11), new Range(0, 13, 0, 17)]); + assertRangesEqual(codeContentResults[0].ranges, [new Range(0, 7, 0, 11), new Range(0, 13, 0, 17)]); + + assert.strictEqual(codeWebviewResults.length, 3); + assert.strictEqual(codeWebviewResults[0].preview.text, 'test! testing!!'); + assert.strictEqual(codeWebviewResults[1].preview.text, 'test! testing!!'); + assert.strictEqual(codeWebviewResults[2].preview.text, 'this is a Test'); + + assertRangesEqual(codeWebviewResults[0].preview.matches, [new Range(0, 1, 0, 5)]); + assertRangesEqual(codeWebviewResults[1].preview.matches, [new Range(0, 7, 0, 11)]); + assertRangesEqual(codeWebviewResults[2].preview.matches, [new Range(0, 11, 0, 15)]); + assertRangesEqual(codeWebviewResults[0].ranges, [new Range(0, 1, 0, 5)]); + assertRangesEqual(codeWebviewResults[1].ranges, [new Range(0, 7, 0, 11)]); + assertRangesEqual(codeWebviewResults[2].ranges, [new Range(0, 11, 0, 15)]); }); + test('convert ITextSearchMatch to MatchInNotebook', () => { + const mdCellMatch = new CellMatch(aFileMatch(), mdInputCell, 0); + const markdownCellContentMatchObjs = textSearchMatchesToNotebookMatches(markdownContentResults, mdCellMatch); + + const codeCellMatch = new CellMatch(aFileMatch(), codeCell, 0); + const codeCellContentMatchObjs = textSearchMatchesToNotebookMatches(codeContentResults, codeCellMatch); + const codeWebviewContentMatchObjs = textSearchMatchesToNotebookMatches(codeWebviewResults, codeCellMatch); + + + assert.strictEqual(markdownCellContentMatchObjs[0].cell.id, mdCellMatch.id); + assertRangesEqual(markdownCellContentMatchObjs[0].range(), [new Range(1, 15, 1, 19)]); + + assert.strictEqual(codeCellContentMatchObjs[0].cell.id, codeCellMatch.id); + assert.strictEqual(codeCellContentMatchObjs[1].cell.id, codeCellMatch.id); + assertRangesEqual(codeCellContentMatchObjs[0].range(), [new Range(1, 8, 1, 12)]); + assertRangesEqual(codeCellContentMatchObjs[1].range(), [new Range(1, 14, 1, 18)]); + assertRangesEqual(codeCellContentMatchObjs[2].range(), [new Range(2, 18, 2, 22)]); + + assert.strictEqual(codeWebviewContentMatchObjs[0].cell.id, codeCellMatch.id); + assert.strictEqual(codeWebviewContentMatchObjs[1].cell.id, codeCellMatch.id); + assert.strictEqual(codeWebviewContentMatchObjs[2].cell.id, codeCellMatch.id); + assertRangesEqual(codeWebviewContentMatchObjs[0].range(), [new Range(1, 2, 1, 6)]); + assertRangesEqual(codeWebviewContentMatchObjs[1].range(), [new Range(1, 8, 1, 12)]); + assertRangesEqual(codeWebviewContentMatchObjs[2].range(), [new Range(1, 12, 1, 16)]); + + }); + + + function aFileMatch(): FileMatch { + const rawMatch: IFileMatch = { + resource: URI.file('somepath' + ++counter), + results: [] + }; + + const searchModel = instantiationService.createInstance(SearchModel); + const folderMatch = instantiationService.createInstance(FolderMatch, URI.file('somepath'), '', 0, { + type: QueryType.Text, folderQueries: [{ folder: createFileUriFromPathFromRoot() }], contentPattern: { + pattern: '' + } + }, searchModel.searchResult, searchModel, null); + return instantiationService.createInstance(FileMatch, { + pattern: '' + }, undefined, undefined, folderMatch, rawMatch, null); + } }); }); diff --git a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index 3c9f814ae0eaf..e42e7c6edd55d 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -24,7 +24,6 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { ILabelService } from 'vs/platform/label/common/label'; import { MockLabelService } from 'vs/workbench/services/label/test/common/mockLabelService'; -import { isWindows } from 'vs/base/common/platform'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; @@ -32,6 +31,7 @@ import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/brows import { ICellMatch, IFileMatchWithCells } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { createFileUriFromPathFromRoot, getRootName } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; const lineOneRange = new OneLineRange(1, 0, 1); @@ -505,7 +505,10 @@ suite('SearchResult', () => { }); - function aFileMatch(path: string, searchResult: SearchResult, ...lineMatches: ITextSearchMatch[]): FileMatch { + function aFileMatch(path: string, searchResult: SearchResult | undefined, ...lineMatches: ITextSearchMatch[]): FileMatch { + if (!searchResult) { + searchResult = aSearchResult(); + } const rawMatch: IFileMatch = { resource: URI.file('/' + path), results: lineMatches @@ -526,27 +529,6 @@ suite('SearchResult', () => { return searchModel.searchResult; } - function createFileUriFromPathFromRoot(path?: string): URI { - const rootName = getRootName(); - if (path) { - return URI.file(`${rootName}${path}`); - } else { - if (isWindows) { - return URI.file(`${rootName}/`); - } else { - return URI.file(rootName); - } - } - } - - function getRootName(): string { - if (isWindows) { - return 'c:'; - } else { - return ''; - } - } - function aRawMatch(resource: string, ...results: ITextSearchMatch[]): IFileMatch { return { resource: createFileUriFromPathFromRoot(resource), results }; } diff --git a/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts b/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts new file mode 100644 index 0000000000000..b9f6331e94f3a --- /dev/null +++ b/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isWindows } from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ModelService } from 'vs/editor/common/services/modelService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; + +export function createFileUriFromPathFromRoot(path?: string): URI { + const rootName = getRootName(); + if (path) { + return URI.file(`${rootName}${path}`); + } else { + if (isWindows) { + return URI.file(`${rootName}/`); + } else { + return URI.file(rootName); + } + } +} + +export function getRootName(): string { + if (isWindows) { + return 'c:'; + } else { + return ''; + } +} + + + +export function stubModelService(instantiationService: TestInstantiationService): IModelService { + instantiationService.stub(IThemeService, new TestThemeService()); + const config = new TestConfigurationService(); + config.setUserConfiguration('search', { searchOnType: true, experimental: { notebookSearch: false } }); + instantiationService.stub(IConfigurationService, config); + return instantiationService.createInstance(ModelService); +} + +export function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { + instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); + return instantiationService.createInstance(NotebookEditorWidgetService); +} diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index 1da5e284d825e..03c5a528f300b 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { IModelService } from 'vs/editor/common/services/model'; @@ -29,6 +28,7 @@ import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/se import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; +import { createFileUriFromPathFromRoot, getRootName } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; suite('Search - Viewlet', () => { let instantiation: TestInstantiationService; @@ -213,25 +213,4 @@ suite('Search - Viewlet', () => { instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); return instantiationService.createInstance(NotebookEditorWidgetService); } - - function createFileUriFromPathFromRoot(path?: string): URI { - const rootName = getRootName(); - if (path) { - return URI.file(`${rootName}${path}`); - } else { - if (isWindows) { - return URI.file(`${rootName}/`); - } else { - return URI.file(rootName); - } - } - } - - function getRootName(): string { - if (isWindows) { - return 'c:'; - } else { - return ''; - } - } }); From 7fbb14409309fedce90462aed485d47ade315761 Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 14 Apr 2023 13:08:22 -0700 Subject: [PATCH 2/3] add model notebook search tests --- .../search/test/browser/searchModel.test.ts | 158 +++++++++++++++++- .../search/test/browser/searchResult.test.ts | 64 ++++--- 2 files changed, 186 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index 082ab273eff1a..0bf2e7eb54872 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -16,7 +16,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { IFileMatch, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService, ITextSearchMatch, OneLineRange, QueryType, TextSearchMatch } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { SearchModel } from 'vs/workbench/contrib/search/browser/searchModel'; +import { CellMatch, MatchInNotebook, SearchModel } from 'vs/workbench/contrib/search/browser/searchModel'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -29,6 +29,11 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; import { createFileUriFromPathFromRoot, getRootName } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; +import { ICellMatch, IFileMatchWithCells, contentMatchesToTextSearchMatches, webviewMatchesToTextSearchMatches } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { FindMatch, IReadonlyTextBuffer } from 'vs/editor/common/model'; +import { ResourceMap, ResourceSet } from 'vs/base/common/map'; const nullEvent = new class { id: number = -1; @@ -94,7 +99,7 @@ suite('SearchModel', () => { function searchServiceWithResults(results: IFileMatch[], complete: ISearchComplete | null = null): ISearchService { return { - textSearch(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise { + textSearch(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void, notebookURIs?: ResourceSet): Promise { return new Promise(resolve => { queueMicrotask(() => { results.forEach(onProgress!); @@ -158,6 +163,140 @@ suite('SearchModel', () => { assert.ok(new Range(2, 1, 2, 2).equalsRange(actuaMatches[0].range())); }); + + test('Search Model: Search can return notebook results', async () => { + const notebookUri = createFileUriFromPathFromRoot('/1'); + + const results = [ + aRawMatch('/2', + new TextSearchMatch('test', new OneLineRange(1, 1, 5)), + new TextSearchMatch('this is a test', new OneLineRange(1, 11, 15))), + aRawMatch('/3', new TextSearchMatch('test', lineOneRange))]; + const searchService = instantiationService.stub(ISearchService, searchServiceWithResults(results)); + sinon.stub(CellMatch.prototype, 'addContext'); + + const textSearch = sinon.spy(searchService, 'textSearch'); + const mdInputCell = { + cellKind: CellKind.Markup, textBuffer: { + getLineContent(lineNumber: number): string { + if (lineNumber === 1) { + return '# Test'; + } else { + return ''; + } + } + }, + id: 'mdInputCell' + } as ICellViewModel; + + const findMatchMds = [new FindMatch(new Range(1, 3, 1, 7), ['Test'])]; + + const codeCell = { + cellKind: CellKind.Code, textBuffer: { + getLineContent(lineNumber: number): string { + if (lineNumber === 1) { + return 'print("test! testing!!")'; + } else { + return ''; + } + } + }, + id: 'codeCell' + } as ICellViewModel; + + const findMatchCodeCells = + [new FindMatch(new Range(1, 8, 1, 12), ['test']), + new FindMatch(new Range(1, 14, 1, 18), ['test']), + ]; + const webviewMatches = [{ + index: 0, + searchPreviewInfo: { + line: 'test! testing!!', + range: { + start: 1, + end: 5 + } + } + }, + { + index: 1, + searchPreviewInfo: { + line: 'test! testing!!', + range: { + start: 7, + end: 11 + } + } + } + ]; + const cellMatchMd: ICellMatch = { + cell: mdInputCell, + index: 0, + contentResults: contentMatchesToTextSearchMatches(findMatchMds, mdInputCell), + webviewResults: [] + }; + + const cellMatchCode: ICellMatch = { + cell: codeCell, + index: 1, + contentResults: contentMatchesToTextSearchMatches(findMatchCodeCells, codeCell), + webviewResults: webviewMatchesToTextSearchMatches(webviewMatches), + }; + + const model: SearchModel = instantiationService.createInstance(SearchModel); + const notebookSearch = sinon.stub(model, "getLocalNotebookResults").callsFake(() => { + const localResults = new ResourceMap(uri => uri.path); + const fileMatch = aRawMatchWithCells('/1', cellMatchMd, cellMatchCode); + localResults.set(notebookUri, fileMatch); + return Promise.resolve( + { + results: localResults, + limitHit: false + }); + }); + + await model.search({ contentPattern: { pattern: 'test' }, type: QueryType.Text, folderQueries }); + const actual = model.searchResult.matches(); + + assert(notebookSearch.calledOnce); + assert(textSearch.getCall(0).args[3]?.size === 1); + assert(textSearch.getCall(0).args[3]?.has(notebookUri)); // ensure that the textsearch knows not to re-source the notebooks + + assert.strictEqual(3, actual.length); + assert.strictEqual(URI.file(`${getRootName()}/1`).toString(), actual[0].resource.toString()); + const notebookFileMatches = actual[0].matches(); + + assert.ok(notebookFileMatches[0].range().equalsRange(new Range(1, 3, 1, 7))); + assert.ok(notebookFileMatches[1].range().equalsRange(new Range(1, 8, 1, 12))); + assert.ok(notebookFileMatches[2].range().equalsRange(new Range(1, 14, 1, 18))); + assert.ok(notebookFileMatches[3].range().equalsRange(new Range(1, 2, 1, 6))); + assert.ok(notebookFileMatches[4].range().equalsRange(new Range(1, 8, 1, 12))); + + notebookFileMatches.forEach(match => match instanceof MatchInNotebook); + // assert(notebookFileMatches[0] instanceof MatchInNotebook); + assert((notebookFileMatches[0] as MatchInNotebook).cell.id === 'mdInputCell'); + assert((notebookFileMatches[1] as MatchInNotebook).cell.id === 'codeCell'); + assert((notebookFileMatches[2] as MatchInNotebook).cell.id === 'codeCell'); + assert((notebookFileMatches[3] as MatchInNotebook).cell.id === 'codeCell'); + assert((notebookFileMatches[4] as MatchInNotebook).cell.id === 'codeCell'); + + const mdCellMatchProcessed = (notebookFileMatches[0] as MatchInNotebook).cellParent; + const codeCellMatchProcessed = (notebookFileMatches[1] as MatchInNotebook).cellParent; + + assert(mdCellMatchProcessed.contentMatches.length === 1); + assert(codeCellMatchProcessed.contentMatches.length === 2); + assert(codeCellMatchProcessed.webviewMatches.length === 2); + + assert(mdCellMatchProcessed.contentMatches[0] === notebookFileMatches[0]); + assert(codeCellMatchProcessed.contentMatches[0] === notebookFileMatches[1]); + assert(codeCellMatchProcessed.contentMatches[1] === notebookFileMatches[2]); + assert(codeCellMatchProcessed.webviewMatches[0] === notebookFileMatches[3]); + assert(codeCellMatchProcessed.webviewMatches[1] === notebookFileMatches[4]); + + assert.strictEqual(URI.file(`${getRootName()}/2`).toString(), actual[1].resource.toString()); + assert.strictEqual(URI.file(`${getRootName()}/3`).toString(), actual[2].resource.toString()); + }); + test('Search Model: Search reports telemetry on search completed', async () => { const target = instantiationService.spy(ITelemetryService, 'publicLog'); const results = [ @@ -284,6 +423,15 @@ suite('SearchModel', () => { const tokenSource = new CancellationTokenSource(); instantiationService.stub(ISearchService, canceleableSearchService(tokenSource)); const testObject: SearchModel = instantiationService.createInstance(SearchModel); + sinon.stub(testObject, "notebookSearch").callsFake((_, token) => { + token?.onCancellationRequested(() => tokenSource.cancel()); + + return new Promise(resolve => { + queueMicrotask(() => { + resolve({}); + }); + }); + }); testObject.search({ contentPattern: { pattern: 'somestring' }, type: QueryType.Text, folderQueries }); instantiationService.stub(ISearchService, searchServiceWithResults([])); @@ -327,6 +475,10 @@ suite('SearchModel', () => { return { resource: createFileUriFromPathFromRoot(resource), results }; } + function aRawMatchWithCells(resource: string, ...cells: ICellMatch[]) { + return { resource: createFileUriFromPathFromRoot(resource), cellResults: cells }; + } + function stub(arg1: any, arg2: any, arg3: any): sinon.SinonStub { const stub = sinon.stub(arg1, arg2).callsFake(arg3); restoreStubs.push(stub); @@ -336,7 +488,7 @@ suite('SearchModel', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IThemeService, new TestThemeService()); const config = new TestConfigurationService(); - config.setUserConfiguration('search', { searchOnType: true, experimental: { notebookSearch: false } }); + config.setUserConfiguration('search', { searchOnType: true, experimental: { notebookSearch: true } }); instantiationService.stub(IConfigurationService, config); return instantiationService.createInstance(ModelService); } diff --git a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index e42e7c6edd55d..fe50a70a96e14 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -223,47 +223,45 @@ suite('SearchResult', () => { assert.ok(new Range(2, 1, 2, 2).equalsRange(actuaMatches[0].range())); }); - test('Adding multiple raw notebook matches', function () { + test('Test that notebook matches get added correctly', function () { const testObject = aSearchResult(); - const modelTarget = instantiationService.spy(IModelService, 'getModel'); const cell1 = { cellKind: CellKind.Code } as ICellViewModel; const cell2 = { cellKind: CellKind.Code } as ICellViewModel; sinon.stub(CellMatch.prototype, 'addContext'); - const target = [ - aRawFileMatchWithCells('/1', - { - cell: cell1, - index: 0, - contentResults: [ - new TextSearchMatch('preview 1', new OneLineRange(1, 1, 4)), - ], - webviewResults: [ - new TextSearchMatch('preview 1', new OneLineRange(1, 4, 11)), - new TextSearchMatch('preview 2', lineOneRange) - ] - },), - aRawFileMatchWithCells('/2', - { - cell: cell2, - index: 0, - contentResults: [ - new TextSearchMatch('preview 1', new OneLineRange(1, 1, 4)), - ], - webviewResults: [ - new TextSearchMatch('preview 1', new OneLineRange(1, 4, 11)), - new TextSearchMatch('preview 2', lineOneRange) - ] - }) - ]; + const addFileMatch = sinon.spy(FolderMatch.prototype, "addFileMatch"); + const fileMatch1 = aRawFileMatchWithCells('/1', + { + cell: cell1, + index: 0, + contentResults: [ + new TextSearchMatch('preview 1', new OneLineRange(1, 1, 4)), + ], + webviewResults: [ + new TextSearchMatch('preview 1', new OneLineRange(1, 4, 11)), + new TextSearchMatch('preview 2', lineOneRange) + ] + },); + const fileMatch2 = aRawFileMatchWithCells('/2', + { + cell: cell2, + index: 0, + contentResults: [ + new TextSearchMatch('preview 1', new OneLineRange(1, 1, 4)), + ], + webviewResults: [ + new TextSearchMatch('preview 1', new OneLineRange(1, 4, 11)), + new TextSearchMatch('preview 2', lineOneRange) + ] + }); + const target = [fileMatch1, fileMatch2]; testObject.add(target); assert.strictEqual(6, testObject.count()); - - // when a model is binded, the results are queried once again. - assert.ok(modelTarget.calledTwice); - assert.ok(modelTarget.calledWith(testObject.matches()[0].resource)); - assert.ok(modelTarget.calledWith(testObject.matches()[1].resource)); + assert.deepStrictEqual(fileMatch1.cellResults[0].contentResults, (addFileMatch.getCall(0).args[0][0] as IFileMatchWithCells).cellResults[0].contentResults); + assert.deepStrictEqual(fileMatch1.cellResults[0].webviewResults, (addFileMatch.getCall(0).args[0][0] as IFileMatchWithCells).cellResults[0].webviewResults); + assert.deepStrictEqual(fileMatch2.cellResults[0].contentResults, (addFileMatch.getCall(0).args[0][1] as IFileMatchWithCells).cellResults[0].contentResults); + assert.deepStrictEqual(fileMatch2.cellResults[0].webviewResults, (addFileMatch.getCall(0).args[0][1] as IFileMatchWithCells).cellResults[0].webviewResults); }); test('Dispose disposes matches', function () { From c57999fe602c6fde57b181c1ba10feb29c1ea3d8 Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 14 Apr 2023 13:31:56 -0700 Subject: [PATCH 3/3] fix sinon error --- .../workbench/contrib/search/test/browser/searchModel.test.ts | 3 ++- .../contrib/search/test/browser/searchResult.test.ts | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index 0bf2e7eb54872..8a1fbaa01c521 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -173,7 +173,8 @@ suite('SearchModel', () => { new TextSearchMatch('this is a test', new OneLineRange(1, 11, 15))), aRawMatch('/3', new TextSearchMatch('test', lineOneRange))]; const searchService = instantiationService.stub(ISearchService, searchServiceWithResults(results)); - sinon.stub(CellMatch.prototype, 'addContext'); + const addContext = sinon.stub(CellMatch.prototype, 'addContext'); + restoreStubs.push(addContext); const textSearch = sinon.spy(searchService, 'textSearch'); const mdInputCell = { diff --git a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index fe50a70a96e14..b327fbe8edbda 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -228,7 +228,8 @@ suite('SearchResult', () => { const cell1 = { cellKind: CellKind.Code } as ICellViewModel; const cell2 = { cellKind: CellKind.Code } as ICellViewModel; - sinon.stub(CellMatch.prototype, 'addContext'); + const addContext = sinon.stub(CellMatch.prototype, 'addContext'); + const addFileMatch = sinon.spy(FolderMatch.prototype, "addFileMatch"); const fileMatch1 = aRawFileMatchWithCells('/1', { @@ -262,6 +263,7 @@ suite('SearchResult', () => { assert.deepStrictEqual(fileMatch1.cellResults[0].webviewResults, (addFileMatch.getCall(0).args[0][0] as IFileMatchWithCells).cellResults[0].webviewResults); assert.deepStrictEqual(fileMatch2.cellResults[0].contentResults, (addFileMatch.getCall(0).args[0][1] as IFileMatchWithCells).cellResults[0].contentResults); assert.deepStrictEqual(fileMatch2.cellResults[0].webviewResults, (addFileMatch.getCall(0).args[0][1] as IFileMatchWithCells).cellResults[0].webviewResults); + addContext.restore(); }); test('Dispose disposes matches', function () {