Path: blob/main/src/vs/editor/test/browser/services/openerService.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 { Disposable } from '../../../../base/common/lifecycle.js';6import { URI } from '../../../../base/common/uri.js';7import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';8import { OpenerService } from '../../../browser/services/openerService.js';9import { TestCodeEditorService } from '../editorTestServices.js';10import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js';11import { NullCommandService } from '../../../../platform/commands/test/common/nullCommandService.js';12import { ITextEditorOptions } from '../../../../platform/editor/common/editor.js';13import { matchesScheme, matchesSomeScheme } from '../../../../base/common/network.js';14import { TestThemeService } from '../../../../platform/theme/test/common/testThemeService.js';1516suite('OpenerService', function () {17const themeService = new TestThemeService();18const editorService = new TestCodeEditorService(themeService);1920let lastCommand: { id: string; args: any[] } | undefined;2122const commandService = new (class implements ICommandService {23declare readonly _serviceBrand: undefined;24onWillExecuteCommand = () => Disposable.None;25onDidExecuteCommand = () => Disposable.None;26executeCommand(id: string, ...args: any[]): Promise<any> {27lastCommand = { id, args };28return Promise.resolve(undefined);29}30})();3132setup(function () {33lastCommand = undefined;34});3536const store = ensureNoDisposablesAreLeakedInTestSuite();3738test('delegate to editorService, scheme:///fff', async function () {39const openerService = new OpenerService(editorService, NullCommandService);40await openerService.open(URI.parse('another:///somepath'));41assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection, undefined);42});4344test('delegate to editorService, scheme:///fff#L123', async function () {45const openerService = new OpenerService(editorService, NullCommandService);4647await openerService.open(URI.parse('file:///somepath#L23'));48assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startLineNumber, 23);49assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startColumn, 1);50assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endLineNumber, undefined);51assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endColumn, undefined);52assert.strictEqual(editorService.lastInput!.resource.fragment, '');5354await openerService.open(URI.parse('another:///somepath#L23'));55assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startLineNumber, 23);56assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startColumn, 1);5758await openerService.open(URI.parse('another:///somepath#L23,45'));59assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startLineNumber, 23);60assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startColumn, 45);61assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endLineNumber, undefined);62assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endColumn, undefined);63assert.strictEqual(editorService.lastInput!.resource.fragment, '');64});6566test('delegate to editorService, scheme:///fff#123,123', async function () {67const openerService = new OpenerService(editorService, NullCommandService);6869await openerService.open(URI.parse('file:///somepath#23'));70assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startLineNumber, 23);71assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startColumn, 1);72assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endLineNumber, undefined);73assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endColumn, undefined);74assert.strictEqual(editorService.lastInput!.resource.fragment, '');7576await openerService.open(URI.parse('file:///somepath#23,45'));77assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startLineNumber, 23);78assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startColumn, 45);79assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endLineNumber, undefined);80assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endColumn, undefined);81assert.strictEqual(editorService.lastInput!.resource.fragment, '');82});8384test('delegate to commandsService, command:someid', async function () {85const openerService = new OpenerService(editorService, commandService);8687const id = `aCommand${Math.random()}`;88store.add(CommandsRegistry.registerCommand(id, function () { }));8990assert.strictEqual(lastCommand, undefined);91await openerService.open(URI.parse('command:' + id));92assert.strictEqual(lastCommand, undefined);93});949596test('delegate to commandsService, command:someid, 2', async function () {97const openerService = new OpenerService(editorService, commandService);9899const id = `aCommand${Math.random()}`;100store.add(CommandsRegistry.registerCommand(id, function () { }));101102await openerService.open(URI.parse('command:' + id).with({ query: '\"123\"' }), { allowCommands: true });103assert.strictEqual(lastCommand!.id, id);104assert.strictEqual(lastCommand!.args.length, 1);105assert.strictEqual(lastCommand!.args[0], '123');106107await openerService.open(URI.parse('command:' + id), { allowCommands: true });108assert.strictEqual(lastCommand!.id, id);109assert.strictEqual(lastCommand!.args.length, 0);110111await openerService.open(URI.parse('command:' + id).with({ query: '123' }), { allowCommands: true });112assert.strictEqual(lastCommand!.id, id);113assert.strictEqual(lastCommand!.args.length, 1);114assert.strictEqual(lastCommand!.args[0], 123);115116await openerService.open(URI.parse('command:' + id).with({ query: JSON.stringify([12, true]) }), { allowCommands: true });117assert.strictEqual(lastCommand!.id, id);118assert.strictEqual(lastCommand!.args.length, 2);119assert.strictEqual(lastCommand!.args[0], 12);120assert.strictEqual(lastCommand!.args[1], true);121});122123test('links are protected by validators', async function () {124const openerService = new OpenerService(editorService, commandService);125126store.add(openerService.registerValidator({ shouldOpen: () => Promise.resolve(false) }));127128const httpResult = await openerService.open(URI.parse('https://www.microsoft.com'));129const httpsResult = await openerService.open(URI.parse('https://www.microsoft.com'));130assert.strictEqual(httpResult, false);131assert.strictEqual(httpsResult, false);132});133134test('links validated by validators go to openers', async function () {135const openerService = new OpenerService(editorService, commandService);136137store.add(openerService.registerValidator({ shouldOpen: () => Promise.resolve(true) }));138139let openCount = 0;140store.add(openerService.registerOpener({141open: (resource: URI) => {142openCount++;143return Promise.resolve(true);144}145}));146147await openerService.open(URI.parse('http://microsoft.com'));148assert.strictEqual(openCount, 1);149await openerService.open(URI.parse('https://microsoft.com'));150assert.strictEqual(openCount, 2);151});152153test('links aren\'t manipulated before being passed to validator: PR #118226', async function () {154const openerService = new OpenerService(editorService, commandService);155156store.add(openerService.registerValidator({157shouldOpen: (resource) => {158// We don't want it to convert strings into URIs159assert.strictEqual(resource instanceof URI, false);160return Promise.resolve(false);161}162}));163await openerService.open('https://wwww.microsoft.com');164await openerService.open('https://www.microsoft.com??params=CountryCode%3DUSA%26Name%3Dvscode"');165});166167test('links validated by multiple validators', async function () {168const openerService = new OpenerService(editorService, commandService);169170let v1 = 0;171openerService.registerValidator({172shouldOpen: () => {173v1++;174return Promise.resolve(true);175}176});177178let v2 = 0;179openerService.registerValidator({180shouldOpen: () => {181v2++;182return Promise.resolve(true);183}184});185186let openCount = 0;187openerService.registerOpener({188open: (resource: URI) => {189openCount++;190return Promise.resolve(true);191}192});193194await openerService.open(URI.parse('http://microsoft.com'));195assert.strictEqual(openCount, 1);196assert.strictEqual(v1, 1);197assert.strictEqual(v2, 1);198await openerService.open(URI.parse('https://microsoft.com'));199assert.strictEqual(openCount, 2);200assert.strictEqual(v1, 2);201assert.strictEqual(v2, 2);202});203204test('links invalidated by first validator do not continue validating', async function () {205const openerService = new OpenerService(editorService, commandService);206207let v1 = 0;208openerService.registerValidator({209shouldOpen: () => {210v1++;211return Promise.resolve(false);212}213});214215let v2 = 0;216openerService.registerValidator({217shouldOpen: () => {218v2++;219return Promise.resolve(true);220}221});222223let openCount = 0;224openerService.registerOpener({225open: (resource: URI) => {226openCount++;227return Promise.resolve(true);228}229});230231await openerService.open(URI.parse('http://microsoft.com'));232assert.strictEqual(openCount, 0);233assert.strictEqual(v1, 1);234assert.strictEqual(v2, 0);235await openerService.open(URI.parse('https://microsoft.com'));236assert.strictEqual(openCount, 0);237assert.strictEqual(v1, 2);238assert.strictEqual(v2, 0);239});240241test('matchesScheme', function () {242assert.ok(matchesScheme('https://microsoft.com', 'https'));243assert.ok(matchesScheme('http://microsoft.com', 'http'));244assert.ok(matchesScheme('hTTPs://microsoft.com', 'https'));245assert.ok(matchesScheme('httP://microsoft.com', 'http'));246assert.ok(matchesScheme(URI.parse('https://microsoft.com'), 'https'));247assert.ok(matchesScheme(URI.parse('http://microsoft.com'), 'http'));248assert.ok(matchesScheme(URI.parse('hTTPs://microsoft.com'), 'https'));249assert.ok(matchesScheme(URI.parse('httP://microsoft.com'), 'http'));250assert.ok(!matchesScheme(URI.parse('https://microsoft.com'), 'http'));251assert.ok(!matchesScheme(URI.parse('htt://microsoft.com'), 'http'));252assert.ok(!matchesScheme(URI.parse('z://microsoft.com'), 'http'));253});254255test('matchesSomeScheme', function () {256assert.ok(matchesSomeScheme('https://microsoft.com', 'http', 'https'));257assert.ok(matchesSomeScheme('http://microsoft.com', 'http', 'https'));258assert.ok(!matchesSomeScheme('x://microsoft.com', 'http', 'https'));259});260261test('resolveExternalUri', async function () {262const openerService = new OpenerService(editorService, NullCommandService);263264try {265await openerService.resolveExternalUri(URI.parse('file:///Users/user/folder'));266assert.fail('Should not reach here');267} catch {268// OK269}270271const disposable = openerService.registerExternalUriResolver({272async resolveExternalUri(uri) {273return { resolved: uri, dispose() { } };274}275});276277const result = await openerService.resolveExternalUri(URI.parse('file:///Users/user/folder'));278assert.deepStrictEqual(result.resolved.toString(), 'file:///Users/user/folder');279disposable.dispose();280});281282test('vscode.open command can\'t open HTTP URL with hash (#) in it [extension development] #140907', async function () {283const openerService = new OpenerService(editorService, NullCommandService);284285const actual: string[] = [];286287openerService.setDefaultExternalOpener({288async openExternal(href) {289actual.push(href);290return true;291}292});293294const href = 'https://gitlab.com/viktomas/test-project/merge_requests/new?merge_request%5Bsource_branch%5D=test-%23-hash';295const uri = URI.parse(href);296297assert.ok(await openerService.open(uri));298assert.ok(await openerService.open(href));299300assert.deepStrictEqual(actual, [301encodeURI(uri.toString(true)), // BAD, the encoded # (%23) is double encoded to %2523 (% is double encoded)302href // good303]);304});305});306307308