@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
172 lines (148 loc) • 7.41 kB
text/typescript
// *****************************************************************************
// Copyright (C) 2019 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { enableJSDOM } from '../test/jsdom';
let disableJSDOM = enableJSDOM();
import { Container, injectable } from 'inversify';
import type { IMacKeyboardLayoutInfo } from 'native-keymap';
import * as chai from 'chai';
import * as sinon from 'sinon';
import * as os from '../../common/os';
import { ILogger, Loggable } from '../../common/logger';
import { LocalStorageService } from '../storage-service';
import { MessageService } from '../../common/message-service';
import { WindowService } from '../window/window-service';
import { BrowserKeyboardLayoutProvider } from './browser-keyboard-layout-provider';
import { Key } from './keys';
disableJSDOM();
describe('browser keyboard layout provider', function (): void {
let stubOSX: sinon.SinonStub;
let stubWindows: sinon.SinonStub;
before(() => disableJSDOM = enableJSDOM());
after(() => disableJSDOM());
const setup = (system: 'mac' | 'win' | 'linux') => {
switch (system) {
case 'mac':
stubOSX = sinon.stub(os, 'isOSX').value(true);
stubWindows = sinon.stub(os, 'isWindows').value(false);
break;
case 'win':
stubOSX = sinon.stub(os, 'isOSX').value(false);
stubWindows = sinon.stub(os, 'isWindows').value(true);
break;
default:
stubOSX = sinon.stub(os, 'isOSX').value(false);
stubWindows = sinon.stub(os, 'isWindows').value(false);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const container = new Container();
container.bind(BrowserKeyboardLayoutProvider).toSelf();
container.bind(ILogger).to(MockLogger);
container.bind(LocalStorageService).toSelf().inSingletonScope();
container.bind(MessageService).toConstantValue({} as MessageService);
container.bind(WindowService).toConstantValue({} as WindowService);
const service = container.get(BrowserKeyboardLayoutProvider);
return { service, container };
};
afterEach(() => {
stubOSX.restore();
stubWindows.restore();
});
it('detects German Mac layout', async () => {
const { service } = setup('mac');
let currentLayout = await service.getNativeLayout();
service.onDidChangeNativeLayout(l => {
currentLayout = l;
});
chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.US');
service.validateKey({ code: Key.SEMICOLON.code, character: 'ö' });
chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.German');
});
it('detects French Mac layout', async () => {
const { service } = setup('mac');
let currentLayout = await service.getNativeLayout();
service.onDidChangeNativeLayout(l => {
currentLayout = l;
});
chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.German');
service.validateKey({ code: Key.SEMICOLON.code, character: 'm' });
chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.French');
});
it('detects keyboard layout change', async () => {
const { service } = setup('mac');
let currentLayout = await service.getNativeLayout();
service.onDidChangeNativeLayout(l => {
currentLayout = l;
});
service.validateKey({ code: Key.QUOTE.code, character: 'ä' });
service.validateKey({ code: Key.SEMICOLON.code, character: 'ö' });
service.validateKey({ code: Key.BRACKET_LEFT.code, character: 'ü' });
chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.German');
service.validateKey({ code: Key.SEMICOLON.code, character: 'm' });
chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.French');
});
it('applies layout chosen by the user', async () => {
const { service } = setup('mac');
let currentLayout = await service.getNativeLayout();
service.onDidChangeNativeLayout(l => {
currentLayout = l;
});
service.validateKey({ code: Key.SEMICOLON.code, character: 'm' });
const spanishLayout = service.allLayoutData.find(data => data.name === 'Spanish' && data.hardware === 'mac')!;
await service.setLayoutData(spanishLayout);
chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.Spanish');
await service.setLayoutData('autodetect');
chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.French');
});
it('restores pressed keys from last session', async () => {
const { service, container } = setup('mac');
service.validateKey({ code: Key.SEMICOLON.code, character: 'm' });
const service2 = container.get(BrowserKeyboardLayoutProvider);
chai.expect(service2).to.not.equal(service);
const currentLayout = await service2.getNativeLayout();
chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.French');
});
it('restores user selection from last session', async () => {
const { service, container } = setup('mac');
const spanishLayout = service.allLayoutData.find(data => data.name === 'Spanish' && data.hardware === 'mac')!;
await service.setLayoutData(spanishLayout);
const service2 = container.get(BrowserKeyboardLayoutProvider);
chai.expect(service2).to.not.equal(service);
service2.validateKey({ code: Key.SEMICOLON.code, character: 'm' });
const currentLayout = await service2.getNativeLayout();
chai.expect((currentLayout.info as IMacKeyboardLayoutInfo).id).to.equal('com.apple.keylayout.Spanish');
});
});
()
class MockLogger implements Partial<ILogger> {
trace(loggable: Loggable): Promise<void> {
return Promise.resolve();
}
debug(loggable: Loggable): Promise<void> {
return Promise.resolve();
}
info(loggable: Loggable): Promise<void> {
return Promise.resolve();
}
warn(loggable: Loggable): Promise<void> {
return Promise.resolve();
}
error(loggable: Loggable): Promise<void> {
return Promise.resolve();
}
fatal(loggable: Loggable): Promise<void> {
return Promise.resolve();
}
}