Path: blob/main/src/vs/workbench/test/browser/quickAccess.test.ts
4778 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*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { Registry } from '../../../platform/registry/common/platform.js';7import { IQuickAccessRegistry, Extensions, IQuickAccessProvider, QuickAccessRegistry } from '../../../platform/quickinput/common/quickAccess.js';8import { IQuickPick, IQuickPickItem, IQuickInputService } from '../../../platform/quickinput/common/quickInput.js';9import { CancellationToken } from '../../../base/common/cancellation.js';10import { TestServiceAccessor, workbenchInstantiationService, createEditorPart } from './workbenchTestServices.js';11import { DisposableStore, toDisposable, IDisposable } from '../../../base/common/lifecycle.js';12import { timeout } from '../../../base/common/async.js';13import { PickerQuickAccessProvider, FastAndSlowPicks } from '../../../platform/quickinput/browser/pickerQuickAccess.js';14import { URI } from '../../../base/common/uri.js';15import { IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js';16import { IEditorService } from '../../services/editor/common/editorService.js';17import { EditorService } from '../../services/editor/browser/editorService.js';18import { PickerEditorState } from '../../browser/quickaccess.js';19import { EditorsOrder } from '../../common/editor.js';20import { Range } from '../../../editor/common/core/range.js';21import { TestInstantiationService } from '../../../platform/instantiation/test/common/instantiationServiceMock.js';22import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../base/test/common/utils.js';23import { IContextKeyService, ContextKeyExpr } from '../../../platform/contextkey/common/contextkey.js';24import { ContextKeyService } from '../../../platform/contextkey/browser/contextKeyService.js';25import { TestConfigurationService } from '../../../platform/configuration/test/common/testConfigurationService.js';2627suite('QuickAccess', () => {2829const disposables = ensureNoDisposablesAreLeakedInTestSuite();30let instantiationService: TestInstantiationService;31let accessor: TestServiceAccessor;3233let providerDefaultCalled = false;34let providerDefaultCanceled = false;35let providerDefaultDisposed = false;3637let provider1Called = false;38let provider1Canceled = false;39let provider1Disposed = false;4041let provider2Called = false;42let provider2Canceled = false;43let provider2Disposed = false;4445let provider3Called = false;46let provider3Canceled = false;47let provider3Disposed = false;4849class TestProviderDefault implements IQuickAccessProvider {5051constructor(@IQuickInputService private readonly quickInputService: IQuickInputService, disposables: DisposableStore) { }5253provide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken): IDisposable {54assert.ok(picker);55providerDefaultCalled = true;56const store = new DisposableStore();57store.add(toDisposable(() => providerDefaultDisposed = true));58store.add(token.onCancellationRequested(() => providerDefaultCanceled = true));5960// bring up provider #361setTimeout(() => this.quickInputService.quickAccess.show(providerDescriptor3.prefix));6263return store;64}65}6667class TestProvider1 implements IQuickAccessProvider {68provide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken): IDisposable {69assert.ok(picker);70provider1Called = true;71const store = new DisposableStore();72store.add(token.onCancellationRequested(() => provider1Canceled = true));7374store.add(toDisposable(() => provider1Disposed = true));75return store;76}77}7879class TestProvider2 implements IQuickAccessProvider {80provide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken): IDisposable {81assert.ok(picker);82provider2Called = true;83const store = new DisposableStore();84store.add(token.onCancellationRequested(() => provider2Canceled = true));8586store.add(toDisposable(() => provider2Disposed = true));87return store;88}89}9091class TestProvider3 implements IQuickAccessProvider {92provide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken): IDisposable {93assert.ok(picker);94provider3Called = true;95const store = new DisposableStore();96store.add(token.onCancellationRequested(() => provider3Canceled = true));9798// hide without picking99setTimeout(() => picker.hide());100101store.add(toDisposable(() => provider3Disposed = true));102return store;103}104}105106const providerDescriptorDefault = { ctor: TestProviderDefault, prefix: '', helpEntries: [] };107const providerDescriptor1 = { ctor: TestProvider1, prefix: 'test', helpEntries: [] };108const providerDescriptor2 = { ctor: TestProvider2, prefix: 'test something', helpEntries: [] };109const providerDescriptor3 = { ctor: TestProvider3, prefix: 'changed', helpEntries: [] };110111setup(() => {112instantiationService = workbenchInstantiationService(undefined, disposables);113accessor = instantiationService.createInstance(TestServiceAccessor);114});115116test('registry', () => {117const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));118const restore = (registry as QuickAccessRegistry).clear();119const contextKeyService = instantiationService.get(IContextKeyService);120121assert.ok(!registry.getQuickAccessProvider('test', contextKeyService));122123const disposables = new DisposableStore();124125disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault));126assert(registry.getQuickAccessProvider('', contextKeyService) === providerDescriptorDefault);127assert(registry.getQuickAccessProvider('test', contextKeyService) === providerDescriptorDefault);128129const disposable = disposables.add(registry.registerQuickAccessProvider(providerDescriptor1));130assert(registry.getQuickAccessProvider('test', contextKeyService) === providerDescriptor1);131132const providers = registry.getQuickAccessProviders(contextKeyService);133assert(providers.some(provider => provider.prefix === 'test'));134135disposable.dispose();136assert(registry.getQuickAccessProvider('test', contextKeyService) === providerDescriptorDefault);137138disposables.dispose();139assert.ok(!registry.getQuickAccessProvider('test', contextKeyService));140141restore();142});143144test('registry - when condition', () => {145const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));146const restore = (registry as QuickAccessRegistry).clear();147148// Use real ContextKeyService that properly evaluates rules149const contextKeyService = disposables.add(new ContextKeyService(new TestConfigurationService()));150const localDisposables = new DisposableStore();151152// Create a context key that starts as undefined (falsy)153const contextKey = contextKeyService.createKey<boolean | undefined>('testQuickAccessContextKey', undefined);154155// Register a provider with a when condition that requires testQuickAccessContextKey to be truthy156const providerWithWhen = {157ctor: TestProvider1,158prefix: 'whentest',159helpEntries: [],160when: ContextKeyExpr.has('testQuickAccessContextKey')161};162localDisposables.add(registry.registerQuickAccessProvider(providerWithWhen));163164// Verify the expression works with the context key service165assert.strictEqual(contextKeyService.contextMatchesRules(providerWithWhen.when), false);166167// Provider with false when condition should not be found168assert.strictEqual(registry.getQuickAccessProvider('whentest', contextKeyService), undefined);169170// Should not appear in the list of providers171let providers = registry.getQuickAccessProviders(contextKeyService);172assert.ok(!providers.some(p => p.prefix === 'whentest'));173174// Set the context key to true175contextKey.set(true);176177// Verify the expression now matches178assert.strictEqual(contextKeyService.contextMatchesRules(providerWithWhen.when), true);179180// Now the provider should be found181assert.strictEqual(registry.getQuickAccessProvider('whentest', contextKeyService), providerWithWhen);182183// Should appear in the list of providers184providers = registry.getQuickAccessProviders(contextKeyService);185assert.ok(providers.some(p => p.prefix === 'whentest'));186187// Set context key back to undefined (falsy)188contextKey.set(undefined);189190// Provider should not be found again191assert.strictEqual(registry.getQuickAccessProvider('whentest', contextKeyService), undefined);192193localDisposables.dispose();194195restore();196});197198test('provider', async () => {199const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));200const restore = (registry as QuickAccessRegistry).clear();201202const disposables = new DisposableStore();203204disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault));205disposables.add(registry.registerQuickAccessProvider(providerDescriptor1));206disposables.add(registry.registerQuickAccessProvider(providerDescriptor2));207disposables.add(registry.registerQuickAccessProvider(providerDescriptor3));208209accessor.quickInputService.quickAccess.show('test');210assert.strictEqual(providerDefaultCalled, false);211assert.strictEqual(provider1Called, true);212assert.strictEqual(provider2Called, false);213assert.strictEqual(provider3Called, false);214assert.strictEqual(providerDefaultCanceled, false);215assert.strictEqual(provider1Canceled, false);216assert.strictEqual(provider2Canceled, false);217assert.strictEqual(provider3Canceled, false);218assert.strictEqual(providerDefaultDisposed, false);219assert.strictEqual(provider1Disposed, false);220assert.strictEqual(provider2Disposed, false);221assert.strictEqual(provider3Disposed, false);222provider1Called = false;223224accessor.quickInputService.quickAccess.show('test something');225assert.strictEqual(providerDefaultCalled, false);226assert.strictEqual(provider1Called, false);227assert.strictEqual(provider2Called, true);228assert.strictEqual(provider3Called, false);229assert.strictEqual(providerDefaultCanceled, false);230assert.strictEqual(provider1Canceled, true);231assert.strictEqual(provider2Canceled, false);232assert.strictEqual(provider3Canceled, false);233assert.strictEqual(providerDefaultDisposed, false);234assert.strictEqual(provider1Disposed, true);235assert.strictEqual(provider2Disposed, false);236assert.strictEqual(provider3Disposed, false);237provider2Called = false;238provider1Canceled = false;239provider1Disposed = false;240241accessor.quickInputService.quickAccess.show('usedefault');242assert.strictEqual(providerDefaultCalled, true);243assert.strictEqual(provider1Called, false);244assert.strictEqual(provider2Called, false);245assert.strictEqual(provider3Called, false);246assert.strictEqual(providerDefaultCanceled, false);247assert.strictEqual(provider1Canceled, false);248assert.strictEqual(provider2Canceled, true);249assert.strictEqual(provider3Canceled, false);250assert.strictEqual(providerDefaultDisposed, false);251assert.strictEqual(provider1Disposed, false);252assert.strictEqual(provider2Disposed, true);253assert.strictEqual(provider3Disposed, false);254255await timeout(1);256257assert.strictEqual(providerDefaultCanceled, true);258assert.strictEqual(providerDefaultDisposed, true);259assert.strictEqual(provider3Called, true);260261await timeout(1);262263assert.strictEqual(provider3Canceled, true);264assert.strictEqual(provider3Disposed, true);265266disposables.dispose();267268restore();269});270271let fastProviderCalled = false;272let slowProviderCalled = false;273let fastAndSlowProviderCalled = false;274275let slowProviderCanceled = false;276let fastAndSlowProviderCanceled = false;277278class FastTestQuickPickProvider extends PickerQuickAccessProvider<IQuickPickItem> {279280constructor() {281super('fast');282}283284protected _getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array<IQuickPickItem> {285fastProviderCalled = true;286287return [{ label: 'Fast Pick' }];288}289}290291class SlowTestQuickPickProvider extends PickerQuickAccessProvider<IQuickPickItem> {292293constructor() {294super('slow');295}296297protected async _getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise<Array<IQuickPickItem>> {298slowProviderCalled = true;299300await timeout(1);301302if (token.isCancellationRequested) {303slowProviderCanceled = true;304}305306return [{ label: 'Slow Pick' }];307}308}309310class FastAndSlowTestQuickPickProvider extends PickerQuickAccessProvider<IQuickPickItem> {311312constructor() {313super('bothFastAndSlow');314}315316protected _getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): FastAndSlowPicks<IQuickPickItem> {317fastAndSlowProviderCalled = true;318319return {320picks: [{ label: 'Fast Pick' }],321additionalPicks: (async () => {322await timeout(1);323324if (token.isCancellationRequested) {325fastAndSlowProviderCanceled = true;326}327328return [{ label: 'Slow Pick' }];329})()330};331}332}333334const fastProviderDescriptor = { ctor: FastTestQuickPickProvider, prefix: 'fast', helpEntries: [] };335const slowProviderDescriptor = { ctor: SlowTestQuickPickProvider, prefix: 'slow', helpEntries: [] };336const fastAndSlowProviderDescriptor = { ctor: FastAndSlowTestQuickPickProvider, prefix: 'bothFastAndSlow', helpEntries: [] };337338test('quick pick access - show()', async () => {339const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));340const restore = (registry as QuickAccessRegistry).clear();341342const disposables = new DisposableStore();343344disposables.add(registry.registerQuickAccessProvider(fastProviderDescriptor));345disposables.add(registry.registerQuickAccessProvider(slowProviderDescriptor));346disposables.add(registry.registerQuickAccessProvider(fastAndSlowProviderDescriptor));347348accessor.quickInputService.quickAccess.show('fast');349assert.strictEqual(fastProviderCalled, true);350assert.strictEqual(slowProviderCalled, false);351assert.strictEqual(fastAndSlowProviderCalled, false);352fastProviderCalled = false;353354accessor.quickInputService.quickAccess.show('slow');355await timeout(2);356357assert.strictEqual(fastProviderCalled, false);358assert.strictEqual(slowProviderCalled, true);359assert.strictEqual(slowProviderCanceled, false);360assert.strictEqual(fastAndSlowProviderCalled, false);361slowProviderCalled = false;362363accessor.quickInputService.quickAccess.show('bothFastAndSlow');364await timeout(2);365366assert.strictEqual(fastProviderCalled, false);367assert.strictEqual(slowProviderCalled, false);368assert.strictEqual(fastAndSlowProviderCalled, true);369assert.strictEqual(fastAndSlowProviderCanceled, false);370fastAndSlowProviderCalled = false;371372accessor.quickInputService.quickAccess.show('slow');373accessor.quickInputService.quickAccess.show('bothFastAndSlow');374accessor.quickInputService.quickAccess.show('fast');375376assert.strictEqual(fastProviderCalled, true);377assert.strictEqual(slowProviderCalled, true);378assert.strictEqual(fastAndSlowProviderCalled, true);379380await timeout(2);381assert.strictEqual(slowProviderCanceled, true);382assert.strictEqual(fastAndSlowProviderCanceled, true);383384disposables.dispose();385386restore();387});388389test('quick pick access - pick()', async () => {390const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));391const restore = (registry as QuickAccessRegistry).clear();392393const disposables = new DisposableStore();394395disposables.add(registry.registerQuickAccessProvider(fastProviderDescriptor));396397const result = accessor.quickInputService.quickAccess.pick('fast');398assert.strictEqual(fastProviderCalled, true);399assert.ok(result instanceof Promise);400401disposables.dispose();402403restore();404});405406test('PickerEditorState can properly restore editors', async () => {407408const part = await createEditorPart(instantiationService, disposables.add(new DisposableStore()));409instantiationService.stub(IEditorGroupsService, part);410411const editorService = disposables.add(instantiationService.createInstance(EditorService, undefined));412instantiationService.stub(IEditorService, editorService);413414const editorViewState = disposables.add(instantiationService.createInstance(PickerEditorState));415disposables.add(part);416disposables.add(editorService);417418const input1 = {419resource: URI.parse('foo://bar1'),420options: {421pinned: true, preserveFocus: true, selection: new Range(1, 0, 1, 3)422}423};424const input2 = {425resource: URI.parse('foo://bar2'),426options: {427pinned: true, selection: new Range(1, 0, 1, 3)428}429};430const input3 = {431resource: URI.parse('foo://bar3')432};433const input4 = {434resource: URI.parse('foo://bar4')435};436437const editor = await editorService.openEditor(input1);438assert.strictEqual(editor, editorService.activeEditorPane);439editorViewState.set();440await editorService.openEditor(input2);441await editorViewState.openTransientEditor(input3);442await editorViewState.openTransientEditor(input4);443await editorViewState.restore();444445assert.strictEqual(part.activeGroup.activeEditor?.resource, input1.resource);446assert.deepStrictEqual(part.activeGroup.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(e => e.resource), [input1.resource, input2.resource]);447if (part.activeGroup.activeEditorPane?.getSelection) {448assert.deepStrictEqual(part.activeGroup.activeEditorPane?.getSelection(), input1.options.selection);449}450await part.activeGroup.closeAllEditors();451});452});453454455