mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-09 02:18:32 +00:00
implement unit tests for boards-auto-installer (#513)
Co-authored-by: Francesco Stasi <f.stasi@me.com>
This commit is contained in:
committed by
GitHub
parent
79b075c961
commit
e9db1c0482
@@ -4,7 +4,7 @@
|
||||
"description": "An extension for Theia building the Arduino IDE",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"prepare": "yarn download-cli && yarn download-fwuploader && yarn download-ls && yarn clean && yarn download-examples && yarn build",
|
||||
"prepare": "yarn download-cli && yarn download-fwuploader && yarn download-ls && yarn clean && yarn download-examples && yarn build && yarn test",
|
||||
"clean": "rimraf lib",
|
||||
"download-cli": "node ./scripts/download-cli.js",
|
||||
"download-fwuploader": "node ./scripts/download-fwuploader.js",
|
||||
@@ -101,6 +101,7 @@
|
||||
"protoc": "^1.0.4",
|
||||
"shelljs": "^0.8.3",
|
||||
"sinon": "^9.0.1",
|
||||
"typemoq": "^2.1.0",
|
||||
"uuid": "^3.2.1",
|
||||
"yargs": "^11.1.0"
|
||||
},
|
||||
@@ -109,7 +110,8 @@
|
||||
},
|
||||
"mocha": {
|
||||
"require": [
|
||||
"reflect-metadata/Reflect"
|
||||
"reflect-metadata/Reflect",
|
||||
"ignore-styles"
|
||||
],
|
||||
"reporter": "spec",
|
||||
"colors": true,
|
||||
|
||||
@@ -165,8 +165,9 @@ import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/mo
|
||||
import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service';
|
||||
import { ResponseServiceImpl } from './response-service-impl';
|
||||
import {
|
||||
ResponseServicePath,
|
||||
ResponseService,
|
||||
ResponseServiceArduino,
|
||||
ResponseServicePath,
|
||||
} from '../common/protocol/response-service';
|
||||
import { NotificationCenter } from './notification-center';
|
||||
import {
|
||||
@@ -617,7 +618,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
);
|
||||
return responseService;
|
||||
});
|
||||
|
||||
bind(ResponseService).toService(ResponseServiceImpl);
|
||||
bind(ResponseServiceArduino).toService(ResponseServiceImpl);
|
||||
|
||||
bind(NotificationCenter).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(NotificationCenter);
|
||||
|
||||
@@ -7,10 +7,9 @@ import {
|
||||
Board,
|
||||
} from '../../common/protocol/boards-service';
|
||||
import { BoardsServiceProvider } from './boards-service-provider';
|
||||
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
import { Installable } from '../../common/protocol';
|
||||
import { ResponseServiceImpl } from '../response-service-impl';
|
||||
import { Installable, ResponseServiceArduino } from '../../common/protocol';
|
||||
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
|
||||
|
||||
/**
|
||||
* Listens on `BoardsConfig.Config` changes, if a board is selected which does not
|
||||
@@ -27,8 +26,8 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
||||
@inject(BoardsServiceProvider)
|
||||
protected readonly boardsServiceClient: BoardsServiceProvider;
|
||||
|
||||
@inject(ResponseServiceImpl)
|
||||
protected readonly responseService: ResponseServiceImpl;
|
||||
@inject(ResponseServiceArduino)
|
||||
protected readonly responseService: ResponseServiceArduino;
|
||||
|
||||
@inject(BoardsListWidgetFrontendContribution)
|
||||
protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution;
|
||||
@@ -106,7 +105,7 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (answer) {
|
||||
if (answer === 'Install Manually') {
|
||||
this.boardsManagerFrontendContribution
|
||||
.openView({ reveal: true })
|
||||
.then((widget) =>
|
||||
|
||||
@@ -4,8 +4,11 @@ import URI from '@theia/core/lib/common/uri';
|
||||
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
|
||||
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ResponseServiceImpl } from '../response-service-impl';
|
||||
import { Installable, LibraryService } from '../../common/protocol';
|
||||
import {
|
||||
Installable,
|
||||
LibraryService,
|
||||
ResponseServiceArduino,
|
||||
} from '../../common/protocol';
|
||||
import {
|
||||
SketchContribution,
|
||||
Command,
|
||||
@@ -18,8 +21,8 @@ export class AddZipLibrary extends SketchContribution {
|
||||
@inject(EnvVariablesServer)
|
||||
protected readonly envVariableServer: EnvVariablesServer;
|
||||
|
||||
@inject(ResponseServiceImpl)
|
||||
protected readonly responseService: ResponseServiceImpl;
|
||||
@inject(ResponseServiceArduino)
|
||||
protected readonly responseService: ResponseServiceArduino;
|
||||
|
||||
@inject(LibraryService)
|
||||
protected readonly libraryService: LibraryService;
|
||||
|
||||
@@ -3,13 +3,13 @@ import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { OutputContribution } from '@theia/output/lib/browser/output-contribution';
|
||||
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
|
||||
import {
|
||||
ResponseService,
|
||||
OutputMessage,
|
||||
ProgressMessage,
|
||||
ResponseServiceArduino,
|
||||
} from '../common/protocol/response-service';
|
||||
|
||||
@injectable()
|
||||
export class ResponseServiceImpl implements ResponseService {
|
||||
export class ResponseServiceImpl implements ResponseServiceArduino {
|
||||
@inject(OutputContribution)
|
||||
protected outputContribution: OutputContribution;
|
||||
|
||||
@@ -17,8 +17,13 @@ export class ResponseServiceImpl implements ResponseService {
|
||||
protected outputChannelManager: OutputChannelManager;
|
||||
|
||||
protected readonly progressDidChangeEmitter = new Emitter<ProgressMessage>();
|
||||
|
||||
readonly onProgressDidChange = this.progressDidChangeEmitter.event;
|
||||
|
||||
clearArduinoChannel(): void {
|
||||
this.outputChannelManager.getChannel('Arduino').clear();
|
||||
}
|
||||
|
||||
appendToOutput(message: OutputMessage): void {
|
||||
const { chunk } = message;
|
||||
const channel = this.outputChannelManager.getChannel('Arduino');
|
||||
@@ -26,10 +31,6 @@ export class ResponseServiceImpl implements ResponseService {
|
||||
channel.append(chunk);
|
||||
}
|
||||
|
||||
clearArduinoChannel(): void {
|
||||
this.outputChannelManager.getChannel('Arduino').clear();
|
||||
}
|
||||
|
||||
reportProgress(progress: ProgressMessage): void {
|
||||
this.progressDidChangeEmitter.fire(progress);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { SearchBar } from './search-bar';
|
||||
import { ListWidget } from './list-widget';
|
||||
import { ComponentList } from './component-list';
|
||||
import { ListItemRenderer } from './list-item-renderer';
|
||||
import { ResponseServiceImpl } from '../../response-service-impl';
|
||||
import { ResponseServiceArduino } from '../../../common/protocol';
|
||||
|
||||
export class FilterableListContainer<
|
||||
T extends ArduinoComponent
|
||||
@@ -153,7 +153,7 @@ export namespace FilterableListContainer {
|
||||
readonly resolveFocus: (element: HTMLElement | undefined) => void;
|
||||
readonly filterTextChangeEvent: Event<string | undefined>;
|
||||
readonly messageService: MessageService;
|
||||
readonly responseService: ResponseServiceImpl;
|
||||
readonly responseService: ResponseServiceArduino;
|
||||
readonly install: ({
|
||||
item,
|
||||
progressId,
|
||||
|
||||
@@ -12,11 +12,11 @@ import {
|
||||
Installable,
|
||||
Searchable,
|
||||
ArduinoComponent,
|
||||
ResponseServiceArduino,
|
||||
} from '../../../common/protocol';
|
||||
import { FilterableListContainer } from './filterable-list-container';
|
||||
import { ListItemRenderer } from './list-item-renderer';
|
||||
import { NotificationCenter } from '../../notification-center';
|
||||
import { ResponseServiceImpl } from '../../response-service-impl';
|
||||
|
||||
@injectable()
|
||||
export abstract class ListWidget<
|
||||
@@ -28,8 +28,8 @@ export abstract class ListWidget<
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
@inject(ResponseServiceImpl)
|
||||
protected readonly responseService: ResponseServiceImpl;
|
||||
@inject(ResponseServiceArduino)
|
||||
protected readonly responseService: ResponseServiceArduino;
|
||||
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { naturalCompare } from './../utils';
|
||||
import { ArduinoComponent } from './arduino-component';
|
||||
import { MessageService } from '@theia/core';
|
||||
import { ResponseServiceImpl } from '../../browser/response-service-impl';
|
||||
import { ResponseServiceArduino } from './response-service';
|
||||
|
||||
export interface Installable<T extends ArduinoComponent> {
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ export namespace Installable {
|
||||
>(options: {
|
||||
installable: Installable<T>;
|
||||
messageService: MessageService;
|
||||
responseService: ResponseServiceImpl;
|
||||
responseService: ResponseServiceArduino;
|
||||
item: T;
|
||||
version: Installable.Version;
|
||||
}): Promise<void> {
|
||||
@@ -66,7 +66,7 @@ export namespace Installable {
|
||||
>(options: {
|
||||
installable: Installable<T>;
|
||||
messageService: MessageService;
|
||||
responseService: ResponseServiceImpl;
|
||||
responseService: ResponseServiceArduino;
|
||||
item: T;
|
||||
}): Promise<void> {
|
||||
const { item } = options;
|
||||
@@ -86,7 +86,7 @@ export namespace Installable {
|
||||
export async function doWithProgress(options: {
|
||||
run: ({ progressId }: { progressId: string }) => Promise<void>;
|
||||
messageService: MessageService;
|
||||
responseService: ResponseServiceImpl;
|
||||
responseService: ResponseServiceArduino;
|
||||
progressText: string;
|
||||
}): Promise<void> {
|
||||
return withProgress(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Event } from '@theia/core/lib/common/event';
|
||||
|
||||
export interface OutputMessage {
|
||||
readonly chunk: string;
|
||||
readonly severity?: 'error' | 'warning' | 'info'; // Currently not used!
|
||||
@@ -21,3 +23,9 @@ export interface ResponseService {
|
||||
appendToOutput(message: OutputMessage): void;
|
||||
reportProgress(message: ProgressMessage): void;
|
||||
}
|
||||
|
||||
export const ResponseServiceArduino = Symbol('ResponseServiceArduino');
|
||||
export interface ResponseServiceArduino extends ResponseService {
|
||||
onProgressDidChange: Event<ProgressMessage>;
|
||||
clearArduinoChannel: () => void;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
|
||||
const disableJSDOM = enableJSDOM();
|
||||
|
||||
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
|
||||
import { ApplicationProps } from '@theia/application-package/lib/application-props';
|
||||
FrontendApplicationConfigProvider.set({
|
||||
...ApplicationProps.DEFAULT.frontend.config,
|
||||
});
|
||||
|
||||
import { MessageService } from '@theia/core';
|
||||
import { BoardsServiceProvider } from '../../browser/boards/boards-service-provider';
|
||||
import { BoardsListWidgetFrontendContribution } from '../../browser/boards/boards-widget-frontend-contribution';
|
||||
import {
|
||||
Board,
|
||||
BoardsPackage,
|
||||
BoardsService,
|
||||
Port,
|
||||
ResponseServiceArduino,
|
||||
} from '../../common/protocol';
|
||||
import { IMock, It, Mock, Times } from 'typemoq';
|
||||
import { Container, ContainerModule } from 'inversify';
|
||||
import { BoardsAutoInstaller } from '../../browser/boards/boards-auto-installer';
|
||||
import { BoardsConfig } from '../../browser/boards/boards-config';
|
||||
import { tick } from '../utils';
|
||||
import { ListWidget } from '../../browser/widgets/component-list/list-widget';
|
||||
|
||||
disableJSDOM();
|
||||
|
||||
const aBoard: Board = {
|
||||
fqbn: 'some:board:fqbn',
|
||||
name: 'Some Arduino Board',
|
||||
port: { address: '/lol/port1234', protocol: 'serial' },
|
||||
};
|
||||
const aPort: Port = {
|
||||
address: aBoard.port!.address,
|
||||
protocol: aBoard.port!.protocol,
|
||||
};
|
||||
const aBoardConfig: BoardsConfig.Config = {
|
||||
selectedBoard: aBoard,
|
||||
selectedPort: aPort,
|
||||
};
|
||||
const aPackage: BoardsPackage = {
|
||||
author: 'someAuthor',
|
||||
availableVersions: ['some.ver.sion', 'some.other.version'],
|
||||
boards: [aBoard],
|
||||
deprecated: false,
|
||||
description: 'Some Arduino Board, Some Other Arduino Board',
|
||||
id: 'some:arduinoCoreId',
|
||||
installable: true,
|
||||
moreInfoLink: 'http://www.some-url.lol/',
|
||||
name: 'Some Arduino Package',
|
||||
summary: 'Boards included in this package:',
|
||||
};
|
||||
|
||||
const anInstalledPackage: BoardsPackage = {
|
||||
...aPackage,
|
||||
installedVersion: 'some.ver.sion',
|
||||
};
|
||||
|
||||
describe('BoardsAutoInstaller', () => {
|
||||
let subject: BoardsAutoInstaller;
|
||||
let messageService: IMock<MessageService>;
|
||||
let boardsService: IMock<BoardsService>;
|
||||
let boardsServiceClient: IMock<BoardsServiceProvider>;
|
||||
let responseService: IMock<ResponseServiceArduino>;
|
||||
let boardsManagerFrontendContribution: IMock<BoardsListWidgetFrontendContribution>;
|
||||
let boardsManagerWidget: IMock<ListWidget<BoardsPackage>>;
|
||||
|
||||
let testContainer: Container;
|
||||
|
||||
beforeEach(() => {
|
||||
testContainer = new Container();
|
||||
messageService = Mock.ofType<MessageService>();
|
||||
boardsService = Mock.ofType<BoardsService>();
|
||||
boardsServiceClient = Mock.ofType<BoardsServiceProvider>();
|
||||
responseService = Mock.ofType<ResponseServiceArduino>();
|
||||
boardsManagerFrontendContribution =
|
||||
Mock.ofType<BoardsListWidgetFrontendContribution>();
|
||||
boardsManagerWidget = Mock.ofType<ListWidget<BoardsPackage>>();
|
||||
|
||||
boardsManagerWidget.setup((b) =>
|
||||
b.refresh(aPackage.name.toLocaleLowerCase())
|
||||
);
|
||||
|
||||
boardsManagerFrontendContribution
|
||||
.setup((b) => b.openView({ reveal: true }))
|
||||
.returns(async () => boardsManagerWidget.object);
|
||||
|
||||
messageService
|
||||
.setup((m) => m.showProgress(It.isAny(), It.isAny()))
|
||||
.returns(async () => ({
|
||||
cancel: () => null,
|
||||
id: '',
|
||||
report: () => null,
|
||||
result: Promise.resolve(''),
|
||||
}));
|
||||
|
||||
responseService
|
||||
.setup((r) => r.onProgressDidChange(It.isAny()))
|
||||
.returns(() => ({ dispose: () => null }));
|
||||
|
||||
const module = new ContainerModule((bind) => {
|
||||
bind(BoardsAutoInstaller).toSelf();
|
||||
bind(MessageService).toConstantValue(messageService.object);
|
||||
bind(BoardsService).toConstantValue(boardsService.object);
|
||||
bind(BoardsServiceProvider).toConstantValue(boardsServiceClient.object);
|
||||
bind(ResponseServiceArduino).toConstantValue(responseService.object);
|
||||
bind(BoardsListWidgetFrontendContribution).toConstantValue(
|
||||
boardsManagerFrontendContribution.object
|
||||
);
|
||||
});
|
||||
|
||||
testContainer.load(module);
|
||||
subject = testContainer.get(BoardsAutoInstaller);
|
||||
});
|
||||
|
||||
context('when it starts', () => {
|
||||
it('should register to the BoardsServiceClient in order to check the packages every a new board is plugged in', () => {
|
||||
subject.onStart();
|
||||
boardsServiceClient.verify(
|
||||
(b) => b.onBoardsConfigChanged(It.isAny()),
|
||||
Times.once()
|
||||
);
|
||||
});
|
||||
|
||||
context('and it checks the installable packages', () => {
|
||||
context(`and a port and a board a selected`, () => {
|
||||
beforeEach(() => {
|
||||
boardsServiceClient
|
||||
.setup((b) => b.boardsConfig)
|
||||
.returns(() => aBoardConfig);
|
||||
});
|
||||
context('if no package for the board is already installed', () => {
|
||||
context('if a candidate package for the board is found', () => {
|
||||
beforeEach(() => {
|
||||
boardsService
|
||||
.setup((b) => b.search(It.isValue({})))
|
||||
.returns(async () => [aPackage]);
|
||||
});
|
||||
it('should show a notification suggesting to install that package', async () => {
|
||||
messageService
|
||||
.setup((m) =>
|
||||
m.info(It.isAnyString(), It.isAnyString(), It.isAnyString())
|
||||
)
|
||||
.returns(() => Promise.resolve('Install Manually'));
|
||||
subject.onStart();
|
||||
await tick();
|
||||
messageService.verify(
|
||||
(m) =>
|
||||
m.info(It.isAnyString(), It.isAnyString(), It.isAnyString()),
|
||||
Times.once()
|
||||
);
|
||||
});
|
||||
context(`if the answer to the message is 'Yes'`, () => {
|
||||
beforeEach(() => {
|
||||
messageService
|
||||
.setup((m) =>
|
||||
m.info(It.isAnyString(), It.isAnyString(), It.isAnyString())
|
||||
)
|
||||
.returns(() => Promise.resolve('Yes'));
|
||||
});
|
||||
it('should install the package', async () => {
|
||||
subject.onStart();
|
||||
|
||||
await tick();
|
||||
|
||||
messageService.verify(
|
||||
(m) => m.showProgress(It.isAny(), It.isAny()),
|
||||
Times.once()
|
||||
);
|
||||
});
|
||||
});
|
||||
context(
|
||||
`if the answer to the message is 'Install Manually'`,
|
||||
() => {
|
||||
beforeEach(() => {
|
||||
messageService
|
||||
.setup((m) =>
|
||||
m.info(
|
||||
It.isAnyString(),
|
||||
It.isAnyString(),
|
||||
It.isAnyString()
|
||||
)
|
||||
)
|
||||
.returns(() => Promise.resolve('Install Manually'));
|
||||
});
|
||||
it('should open the boards manager widget', () => {
|
||||
subject.onStart();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
context('if a candidate package for the board is not found', () => {
|
||||
beforeEach(() => {
|
||||
boardsService
|
||||
.setup((b) => b.search(It.isValue({})))
|
||||
.returns(async () => []);
|
||||
});
|
||||
it('should do nothing', async () => {
|
||||
subject.onStart();
|
||||
await tick();
|
||||
messageService.verify(
|
||||
(m) =>
|
||||
m.info(It.isAnyString(), It.isAnyString(), It.isAnyString()),
|
||||
Times.never()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
context(
|
||||
'if one of the packages for the board is already installed',
|
||||
() => {
|
||||
beforeEach(() => {
|
||||
boardsService
|
||||
.setup((b) => b.search(It.isValue({})))
|
||||
.returns(async () => [aPackage, anInstalledPackage]);
|
||||
messageService
|
||||
.setup((m) =>
|
||||
m.info(It.isAnyString(), It.isAnyString(), It.isAnyString())
|
||||
)
|
||||
.returns(() => Promise.resolve('Yes'));
|
||||
});
|
||||
it('should do nothing', async () => {
|
||||
subject.onStart();
|
||||
await tick();
|
||||
messageService.verify(
|
||||
(m) =>
|
||||
m.info(It.isAnyString(), It.isAnyString(), It.isAnyString()),
|
||||
Times.never()
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
context('and there is no selected board or port', () => {
|
||||
it('should do nothing', async () => {
|
||||
subject.onStart();
|
||||
await tick();
|
||||
messageService.verify(
|
||||
(m) => m.info(It.isAnyString(), It.isAnyString(), It.isAnyString()),
|
||||
Times.never()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
3
arduino-ide-extension/src/test/utils.ts
Normal file
3
arduino-ide-extension/src/test/utils.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function tick(): Promise<void> {
|
||||
return new Promise((res) => setTimeout(res, 1));
|
||||
}
|
||||
Reference in New Issue
Block a user