Path: blob/main/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4import assert from 'assert';5import { withAsyncTestCodeEditor } from '../../../../test/browser/testCodeEditor.js';6import { StickyScrollController } from '../../browser/stickyScrollController.js';7import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';8import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js';9import { createTextModel } from '../../../../test/common/testTextModel.js';10import { LanguageFeaturesService } from '../../../../common/services/languageFeaturesService.js';11import { DocumentSymbol, SymbolKind } from '../../../../common/languages.js';12import { StickyLineCandidate, StickyLineCandidateProvider } from '../../browser/stickyScrollProvider.js';13import { EditorOption } from '../../../../common/config/editorOptions.js';14import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';15import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js';16import { mock } from '../../../../../base/test/common/mock.js';17import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js';18import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from '../../../../common/services/languageFeatureDebounce.js';19import { TestLanguageConfigurationService } from '../../../../test/common/modes/testLanguageConfigurationService.js';20import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js';21import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js';22import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js';23import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';24import { DisposableStore } from '../../../../../base/common/lifecycle.js';2526suite('Sticky Scroll Tests', () => {2728const disposables = new DisposableStore();2930const serviceCollection = new ServiceCollection(31[ILanguageFeaturesService, new LanguageFeaturesService()],32[ILogService, new NullLogService()],33[IContextMenuService, new class extends mock<IContextMenuService>() { }],34[ILanguageConfigurationService, new TestLanguageConfigurationService()],35[IEnvironmentService, new class extends mock<IEnvironmentService>() {36override isBuilt: boolean = true;37override isExtensionDevelopment: boolean = false;38}],39[ILanguageFeatureDebounceService, new SyncDescriptor(LanguageFeatureDebounceService)],40);4142const text = [43'function foo() {',44'',45'}',46'/* comment related to TestClass',47' end of the comment */',48'@classDecorator',49'class TestClass {',50'// comment related to the function functionOfClass',51'functionOfClass(){',52'function function1(){',53'}',54'}}',55'function bar() { function insideBar() {}',56'}'57].join('\n');5859setup(() => {60disposables.clear();61});62teardown(() => {63disposables.clear();64});6566ensureNoDisposablesAreLeakedInTestSuite();6768function documentSymbolProviderForTestModel() {69return {70provideDocumentSymbols() {71return [72{73name: 'foo',74detail: 'foo',75kind: SymbolKind.Function,76tags: [],77range: { startLineNumber: 1, endLineNumber: 3, startColumn: 1, endColumn: 1 },78selectionRange: { startLineNumber: 1, endLineNumber: 1, startColumn: 1, endColumn: 1 }79} as DocumentSymbol,80{81name: 'TestClass',82detail: 'TestClass',83kind: SymbolKind.Class,84tags: [],85range: { startLineNumber: 4, endLineNumber: 12, startColumn: 1, endColumn: 1 },86selectionRange: { startLineNumber: 7, endLineNumber: 7, startColumn: 1, endColumn: 1 },87children: [88{89name: 'functionOfClass',90detail: 'functionOfClass',91kind: SymbolKind.Function,92tags: [],93range: { startLineNumber: 8, endLineNumber: 12, startColumn: 1, endColumn: 1 },94selectionRange: { startLineNumber: 9, endLineNumber: 9, startColumn: 1, endColumn: 1 },95children: [96{97name: 'function1',98detail: 'function1',99kind: SymbolKind.Function,100tags: [],101range: { startLineNumber: 10, endLineNumber: 11, startColumn: 1, endColumn: 1 },102selectionRange: { startLineNumber: 10, endLineNumber: 10, startColumn: 1, endColumn: 1 },103}104]105} as DocumentSymbol106]107} as DocumentSymbol,108{109name: 'bar',110detail: 'bar',111kind: SymbolKind.Function,112tags: [],113range: { startLineNumber: 13, endLineNumber: 14, startColumn: 1, endColumn: 1 },114selectionRange: { startLineNumber: 13, endLineNumber: 13, startColumn: 1, endColumn: 1 },115children: [116{117name: 'insideBar',118detail: 'insideBar',119kind: SymbolKind.Function,120tags: [],121range: { startLineNumber: 13, endLineNumber: 13, startColumn: 1, endColumn: 1 },122selectionRange: { startLineNumber: 13, endLineNumber: 13, startColumn: 1, endColumn: 1 },123} as DocumentSymbol124]125} as DocumentSymbol126];127}128};129}130131test('Testing the function getCandidateStickyLinesIntersecting', () => {132return runWithFakedTimers({ useFakeTimers: true }, async () => {133const model = createTextModel(text);134await withAsyncTestCodeEditor(model, {135stickyScroll: {136enabled: true,137maxLineCount: 5,138defaultModel: 'outlineModel'139},140envConfig: {141outerHeight: 500142},143serviceCollection: serviceCollection144}, async (editor, _viewModel, instantiationService) => {145const languageService = instantiationService.get(ILanguageFeaturesService);146const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);147disposables.add(languageService.documentSymbolProvider.register('*', documentSymbolProviderForTestModel()));148const provider: StickyLineCandidateProvider = new StickyLineCandidateProvider(editor, languageService, languageConfigurationService);149await provider.update();150assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 1, endLineNumber: 4 }), [new StickyLineCandidate(1, 2, 0, 19)]);151assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 8, endLineNumber: 10 }), [new StickyLineCandidate(7, 11, 0, 19), new StickyLineCandidate(9, 11, 19, 19), new StickyLineCandidate(10, 10, 38, 19)]);152assert.deepStrictEqual(provider.getCandidateStickyLinesIntersecting({ startLineNumber: 10, endLineNumber: 13 }), [new StickyLineCandidate(7, 11, 0, 19), new StickyLineCandidate(9, 11, 19, 19), new StickyLineCandidate(10, 10, 38, 19), new StickyLineCandidate(13, 13, 0, 19)]);153154provider.dispose();155model.dispose();156});157});158});159160test('issue #157180: Render the correct line corresponding to the scope definition', () => {161return runWithFakedTimers({ useFakeTimers: true }, async () => {162const model = createTextModel(text);163await withAsyncTestCodeEditor(model, {164stickyScroll: {165enabled: true,166maxLineCount: 5,167defaultModel: 'outlineModel'168},169envConfig: {170outerHeight: 500171},172serviceCollection173}, async (editor, _viewModel, instantiationService) => {174175const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController);176const lineHeight: number = editor.getOption(EditorOption.lineHeight);177const languageService: ILanguageFeaturesService = instantiationService.get(ILanguageFeaturesService);178disposables.add(languageService.documentSymbolProvider.register('*', documentSymbolProviderForTestModel()));179await stickyScrollController.stickyScrollCandidateProvider.update();180let state;181182editor.setScrollTop(1);183state = stickyScrollController.findScrollWidgetState();184assert.deepStrictEqual(state.startLineNumbers, [1]);185186editor.setScrollTop(lineHeight + 1);187state = stickyScrollController.findScrollWidgetState();188assert.deepStrictEqual(state.startLineNumbers, [1]);189190editor.setScrollTop(4 * lineHeight + 1);191state = stickyScrollController.findScrollWidgetState();192assert.deepStrictEqual(state.startLineNumbers, []);193194editor.setScrollTop(8 * lineHeight + 1);195state = stickyScrollController.findScrollWidgetState();196assert.deepStrictEqual(state.startLineNumbers, [7, 9]);197198editor.setScrollTop(9 * lineHeight + 1);199state = stickyScrollController.findScrollWidgetState();200assert.deepStrictEqual(state.startLineNumbers, [7, 9]);201202editor.setScrollTop(10 * lineHeight + 1);203state = stickyScrollController.findScrollWidgetState();204assert.deepStrictEqual(state.startLineNumbers, [7]);205206stickyScrollController.dispose();207stickyScrollController.stickyScrollCandidateProvider.dispose();208model.dispose();209});210});211});212213test('issue #156268 : Do not reveal sticky lines when they are in a folded region ', () => {214return runWithFakedTimers({ useFakeTimers: true }, async () => {215const model = createTextModel(text);216await withAsyncTestCodeEditor(model, {217stickyScroll: {218enabled: true,219maxLineCount: 5,220defaultModel: 'outlineModel'221},222envConfig: {223outerHeight: 500224},225serviceCollection226}, async (editor, viewModel, instantiationService) => {227228const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController);229const lineHeight = editor.getOption(EditorOption.lineHeight);230231const languageService = instantiationService.get(ILanguageFeaturesService);232disposables.add(languageService.documentSymbolProvider.register('*', documentSymbolProviderForTestModel()));233await stickyScrollController.stickyScrollCandidateProvider.update();234editor.setHiddenAreas([{ startLineNumber: 2, endLineNumber: 2, startColumn: 1, endColumn: 1 }, { startLineNumber: 10, endLineNumber: 11, startColumn: 1, endColumn: 1 }]);235let state;236237editor.setScrollTop(1);238state = stickyScrollController.findScrollWidgetState();239assert.deepStrictEqual(state.startLineNumbers, [1]);240241editor.setScrollTop(lineHeight + 1);242state = stickyScrollController.findScrollWidgetState();243assert.deepStrictEqual(state.startLineNumbers, []);244245editor.setScrollTop(6 * lineHeight + 1);246state = stickyScrollController.findScrollWidgetState();247assert.deepStrictEqual(state.startLineNumbers, [7, 9]);248249editor.setScrollTop(7 * lineHeight + 1);250state = stickyScrollController.findScrollWidgetState();251assert.deepStrictEqual(state.startLineNumbers, [7]);252253editor.setScrollTop(10 * lineHeight + 1);254state = stickyScrollController.findScrollWidgetState();255assert.deepStrictEqual(state.startLineNumbers, []);256257stickyScrollController.dispose();258stickyScrollController.stickyScrollCandidateProvider.dispose();259model.dispose();260});261});262});263264const textWithScopesWithSameStartingLines = [265'class TestClass { foo() {',266'function bar(){',267'',268'}}',269'}',270''271].join('\n');272273function documentSymbolProviderForSecondTestModel() {274return {275provideDocumentSymbols() {276return [277{278name: 'TestClass',279detail: 'TestClass',280kind: SymbolKind.Class,281tags: [],282range: { startLineNumber: 1, endLineNumber: 5, startColumn: 1, endColumn: 1 },283selectionRange: { startLineNumber: 1, endLineNumber: 1, startColumn: 1, endColumn: 1 },284children: [285{286name: 'foo',287detail: 'foo',288kind: SymbolKind.Function,289tags: [],290range: { startLineNumber: 1, endLineNumber: 4, startColumn: 1, endColumn: 1 },291selectionRange: { startLineNumber: 1, endLineNumber: 1, startColumn: 1, endColumn: 1 },292children: [293{294name: 'bar',295detail: 'bar',296kind: SymbolKind.Function,297tags: [],298range: { startLineNumber: 2, endLineNumber: 4, startColumn: 1, endColumn: 1 },299selectionRange: { startLineNumber: 2, endLineNumber: 2, startColumn: 1, endColumn: 1 },300children: []301} as DocumentSymbol302]303} as DocumentSymbol,304]305} as DocumentSymbol306];307}308};309}310311test('issue #159271 : render the correct widget state when the child scope starts on the same line as the parent scope', () => {312return runWithFakedTimers({ useFakeTimers: true }, async () => {313const model = createTextModel(textWithScopesWithSameStartingLines);314await withAsyncTestCodeEditor(model, {315stickyScroll: {316enabled: true,317maxLineCount: 5,318defaultModel: 'outlineModel'319},320envConfig: {321outerHeight: 500322},323serviceCollection324}, async (editor, _viewModel, instantiationService) => {325326const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController);327await stickyScrollController.stickyScrollCandidateProvider.update();328const lineHeight = editor.getOption(EditorOption.lineHeight);329330const languageService = instantiationService.get(ILanguageFeaturesService);331disposables.add(languageService.documentSymbolProvider.register('*', documentSymbolProviderForSecondTestModel()));332await stickyScrollController.stickyScrollCandidateProvider.update();333let state;334335editor.setScrollTop(1);336state = stickyScrollController.findScrollWidgetState();337assert.deepStrictEqual(state.startLineNumbers, [1, 2]);338339editor.setScrollTop(lineHeight + 1);340state = stickyScrollController.findScrollWidgetState();341assert.deepStrictEqual(state.startLineNumbers, [1, 2]);342343editor.setScrollTop(2 * lineHeight + 1);344state = stickyScrollController.findScrollWidgetState();345assert.deepStrictEqual(state.startLineNumbers, [1]);346347editor.setScrollTop(3 * lineHeight + 1);348state = stickyScrollController.findScrollWidgetState();349assert.deepStrictEqual(state.startLineNumbers, [1]);350351editor.setScrollTop(4 * lineHeight + 1);352state = stickyScrollController.findScrollWidgetState();353assert.deepStrictEqual(state.startLineNumbers, []);354355stickyScrollController.dispose();356stickyScrollController.stickyScrollCandidateProvider.dispose();357model.dispose();358});359});360});361});362363364