import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom'; const disableJSDOM = enableJSDOM(); import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; FrontendApplicationConfigProvider.set({}); import { ApplicationShell, FrontendApplication, LabelProvider, OpenerService, } from '@theia/core/lib/browser'; import { ClipboardService } from '@theia/core/lib/browser/clipboard-service'; import { ApplicationServer } from '@theia/core/lib/common/application-protocol'; import { CommandService } from '@theia/core/lib/common/command'; import { MessageService } from '@theia/core/lib/common/message-service'; import { nls } from '@theia/core/lib/common/nls'; import { OS } from '@theia/core/lib/common/os'; import { SelectionService } from '@theia/core/lib/common/selection-service'; import URI from '@theia/core/lib/common/uri'; import { Container } from '@theia/core/shared/inversify'; import { FileDialogService } from '@theia/filesystem/lib/browser'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { FileStat } from '@theia/filesystem/lib/common/files'; import { WorkspaceCompareHandler } from '@theia/workspace/lib/browser/workspace-compare-handler'; import { WorkspaceDeleteHandler } from '@theia/workspace/lib/browser/workspace-delete-handler'; import { WorkspaceDuplicateHandler } from '@theia/workspace/lib/browser/workspace-duplicate-handler'; import { WorkspacePreferences } from '@theia/workspace/lib/browser/workspace-preferences'; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import { expect } from 'chai'; import { ConfigServiceClient } from '../../browser/config/config-service-client'; import { CreateFeatures } from '../../browser/create/create-features'; import { SketchesServiceClientImpl } from '../../browser/sketches-service-client-impl'; import { fileAlreadyExists, invalidExtension as invalidExtensionMessage, parseFileInput, WorkspaceCommandContribution, } from '../../browser/theia/workspace/workspace-commands'; import { Sketch, SketchesService } from '../../common/protocol'; disableJSDOM(); describe('workspace-commands', () => { describe('parseFileInput', () => { it("should parse input without extension as '.ino'", () => { const actual = parseFileInput('foo'); expect(actual).to.be.deep.equal({ raw: 'foo', name: 'foo', extension: '.ino', }); }); it("should parse input with a trailing dot as '.ino'", () => { const actual = parseFileInput('foo.'); expect(actual).to.be.deep.equal({ raw: 'foo.', name: 'foo', extension: '.ino', }); }); it('should parse input with a valid extension', () => { const actual = parseFileInput('lib.cpp'); expect(actual).to.be.deep.equal({ raw: 'lib.cpp', name: 'lib', extension: '.cpp', }); }); it('should calculate the file extension based on the last dot index', () => { const actual = parseFileInput('lib.ino.x'); expect(actual).to.be.deep.equal({ raw: 'lib.ino.x', name: 'lib.ino', extension: '.x', }); }); it('should ignore trailing spaces after the last dot', () => { const actual = parseFileInput(' foo. '); expect(actual).to.be.deep.equal({ raw: ' foo. ', name: ' foo', extension: '.ino', }); }); }); describe('validateFileName', () => { const child: FileStat = { isFile: true, isDirectory: false, isSymbolicLink: false, resource: new URI('sketch/sketch.ino'), name: 'sketch.ino', }; const parent: FileStat = { isFile: false, isDirectory: true, isSymbolicLink: false, resource: new URI('sketch'), name: 'sketch', children: [child], }; let workspaceCommands: WorkspaceCommandContribution; async function testMe(userInput: string): Promise<string> { return workspaceCommands['validateFileName'](userInput, parent); } function createContainer(): Container { const container = new Container(); container.bind(FileDialogService).toConstantValue(<FileDialogService>{}); container.bind(FileService).toConstantValue(<FileService>{ async exists(resource: URI): Promise<boolean> { return ( resource.path.base.includes('_sketch') || resource.path.base.includes('sketch') ); }, }); container .bind(FrontendApplication) .toConstantValue(<FrontendApplication>{}); container.bind(LabelProvider).toConstantValue(<LabelProvider>{}); container.bind(MessageService).toConstantValue(<MessageService>{}); container.bind(OpenerService).toConstantValue(<OpenerService>{}); container.bind(SelectionService).toConstantValue(<SelectionService>{}); container.bind(WorkspaceCommandContribution).toSelf().inSingletonScope(); container .bind(WorkspaceCompareHandler) .toConstantValue(<WorkspaceCompareHandler>{}); container .bind(WorkspaceDeleteHandler) .toConstantValue(<WorkspaceDeleteHandler>{}); container .bind(WorkspaceDuplicateHandler) .toConstantValue(<WorkspaceDuplicateHandler>{}); container .bind(WorkspacePreferences) .toConstantValue(<WorkspacePreferences>{}); container.bind(WorkspaceService).toConstantValue(<WorkspaceService>{}); container.bind(ClipboardService).toConstantValue(<ClipboardService>{}); container.bind(ApplicationServer).toConstantValue(<ApplicationServer>{ async getBackendOS(): Promise<OS.Type> { return OS.type(); }, }); container.bind(CommandService).toConstantValue(<CommandService>{}); container.bind(SketchesService).toConstantValue(<SketchesService>{}); container .bind(SketchesServiceClientImpl) .toConstantValue(<SketchesServiceClientImpl>{}); container.bind(CreateFeatures).toConstantValue(<CreateFeatures>{}); container.bind(ApplicationShell).toConstantValue(<ApplicationShell>{}); container .bind(ConfigServiceClient) .toConstantValue(<ConfigServiceClient>{}); return container; } beforeEach(() => { workspaceCommands = createContainer().get<WorkspaceCommandContribution>( WorkspaceCommandContribution ); }); it("should validate input string without an extension as an '.ino' file", async () => { const actual = await testMe('valid'); expect(actual).to.be.empty; }); it('code files cannot start with number (no extension)', async () => { const actual = await testMe('_invalid'); expect(actual).to.be.equal(Sketch.invalidSketchFolderNameMessage); }); it('code files cannot start with number (trailing dot)', async () => { const actual = await testMe('_invalid.'); expect(actual).to.be.equal(Sketch.invalidSketchFolderNameMessage); }); it('code files cannot start with number (trailing dot)', async () => { const actual = await testMe('_invalid.cpp'); expect(actual).to.be.equal(Sketch.invalidSketchFolderNameMessage); }); it('should warn about invalid extension first', async () => { const actual = await testMe('_invalid.xxx'); expect(actual).to.be.equal(invalidExtensionMessage('.xxx')); }); it('should not warn about invalid file extension for empty input', async () => { const actual = await testMe(''); expect(actual).to.be.equal(Sketch.invalidSketchFolderNameMessage); }); it('should ignore non-code filename validation from the spec', async () => { const actual = await testMe('_invalid.json'); expect(actual).to.be.empty; }); it('non-code files should be validated against default new file validation rules', async () => { const name = ' invalid.json'; const actual = await testMe(name); const expected = nls.localizeByDefault( 'Leading or trailing whitespace detected in file or folder name.' ); expect(actual).to.be.equal(expected); }); it('should warn about existing resource', async () => { const name = 'sketch.ino'; const actual = await testMe(name); const expected = fileAlreadyExists(name); expect(actual).to.be.equal(expected); }); }); });