mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-27 13:16:43 +00:00
fix: library search boosting
Closes #1106 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
5d264ef5b6
commit
79b6b7ecc0
@ -17,7 +17,7 @@
|
|||||||
"build": "tsc && ncp ./src/node/cli-protocol/ ./lib/node/cli-protocol/ && yarn lint",
|
"build": "tsc && ncp ./src/node/cli-protocol/ ./lib/node/cli-protocol/ && yarn lint",
|
||||||
"watch": "tsc -w",
|
"watch": "tsc -w",
|
||||||
"test": "mocha \"./lib/test/**/*.test.js\"",
|
"test": "mocha \"./lib/test/**/*.test.js\"",
|
||||||
"test:slow": "mocha \"./lib/test/**/*.slow-test.js\"",
|
"test:slow": "mocha \"./lib/test/**/*.slow-test.js\" --slow 5000",
|
||||||
"test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\""
|
"test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -30,7 +30,6 @@ export class BoardsListWidget extends ListWidget<BoardsPackage, BoardSearch> {
|
|||||||
searchable: service,
|
searchable: service,
|
||||||
installable: service,
|
installable: service,
|
||||||
itemLabel: (item: BoardsPackage) => item.name,
|
itemLabel: (item: BoardsPackage) => item.name,
|
||||||
itemDeprecated: (item: BoardsPackage) => item.deprecated,
|
|
||||||
itemRenderer,
|
itemRenderer,
|
||||||
filterRenderer,
|
filterRenderer,
|
||||||
defaultSearchOptions: { query: '', type: 'All' },
|
defaultSearchOptions: { query: '', type: 'All' },
|
||||||
|
@ -41,7 +41,6 @@ export class LibraryListWidget extends ListWidget<
|
|||||||
searchable: service,
|
searchable: service,
|
||||||
installable: service,
|
installable: service,
|
||||||
itemLabel: (item: LibraryPackage) => item.name,
|
itemLabel: (item: LibraryPackage) => item.name,
|
||||||
itemDeprecated: (item: LibraryPackage) => item.deprecated,
|
|
||||||
itemRenderer,
|
itemRenderer,
|
||||||
filterRenderer,
|
filterRenderer,
|
||||||
defaultSearchOptions: { query: '', type: 'All', topic: 'All' },
|
defaultSearchOptions: { query: '', type: 'All', topic: 'All' },
|
||||||
|
@ -147,7 +147,6 @@ export namespace ComponentList {
|
|||||||
export interface Props<T extends ArduinoComponent> {
|
export interface Props<T extends ArduinoComponent> {
|
||||||
readonly items: T[];
|
readonly items: T[];
|
||||||
readonly itemLabel: (item: T) => string;
|
readonly itemLabel: (item: T) => string;
|
||||||
readonly itemDeprecated: (item: T) => boolean;
|
|
||||||
readonly itemRenderer: ListItemRenderer<T>;
|
readonly itemRenderer: ListItemRenderer<T>;
|
||||||
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
|
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
|
||||||
readonly uninstall: (item: T) => Promise<void>;
|
readonly uninstall: (item: T) => Promise<void>;
|
||||||
|
@ -82,12 +82,11 @@ export class FilterableListContainer<
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected renderComponentList(): React.ReactNode {
|
protected renderComponentList(): React.ReactNode {
|
||||||
const { itemLabel, itemDeprecated, itemRenderer } = this.props;
|
const { itemLabel, itemRenderer } = this.props;
|
||||||
return (
|
return (
|
||||||
<ComponentList<T>
|
<ComponentList<T>
|
||||||
items={this.state.items}
|
items={this.state.items}
|
||||||
itemLabel={itemLabel}
|
itemLabel={itemLabel}
|
||||||
itemDeprecated={itemDeprecated}
|
|
||||||
itemRenderer={itemRenderer}
|
itemRenderer={itemRenderer}
|
||||||
install={this.install.bind(this)}
|
install={this.install.bind(this)}
|
||||||
uninstall={this.uninstall.bind(this)}
|
uninstall={this.uninstall.bind(this)}
|
||||||
@ -109,9 +108,7 @@ export class FilterableListContainer<
|
|||||||
|
|
||||||
protected search(searchOptions: S): void {
|
protected search(searchOptions: S): void {
|
||||||
const { searchable } = this.props;
|
const { searchable } = this.props;
|
||||||
searchable
|
searchable.search(searchOptions).then((items) => this.setState({ items }));
|
||||||
.search(searchOptions)
|
|
||||||
.then((items) => this.setState({ items: this.props.sort(items) }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async install(
|
protected async install(
|
||||||
@ -127,7 +124,7 @@ export class FilterableListContainer<
|
|||||||
run: ({ progressId }) => install({ item, progressId, version }),
|
run: ({ progressId }) => install({ item, progressId, version }),
|
||||||
});
|
});
|
||||||
const items = await searchable.search(this.state.searchOptions);
|
const items = await searchable.search(this.state.searchOptions);
|
||||||
this.setState({ items: this.props.sort(items) });
|
this.setState({ items });
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async uninstall(item: T): Promise<void> {
|
protected async uninstall(item: T): Promise<void> {
|
||||||
@ -155,7 +152,7 @@ export class FilterableListContainer<
|
|||||||
run: ({ progressId }) => uninstall({ item, progressId }),
|
run: ({ progressId }) => uninstall({ item, progressId }),
|
||||||
});
|
});
|
||||||
const items = await searchable.search(this.state.searchOptions);
|
const items = await searchable.search(this.state.searchOptions);
|
||||||
this.setState({ items: this.props.sort(items) });
|
this.setState({ items });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +165,6 @@ export namespace FilterableListContainer {
|
|||||||
readonly container: ListWidget<T, S>;
|
readonly container: ListWidget<T, S>;
|
||||||
readonly searchable: Searchable<T, S>;
|
readonly searchable: Searchable<T, S>;
|
||||||
readonly itemLabel: (item: T) => string;
|
readonly itemLabel: (item: T) => string;
|
||||||
readonly itemDeprecated: (item: T) => boolean;
|
|
||||||
readonly itemRenderer: ListItemRenderer<T>;
|
readonly itemRenderer: ListItemRenderer<T>;
|
||||||
readonly filterRenderer: FilterRenderer<S>;
|
readonly filterRenderer: FilterRenderer<S>;
|
||||||
readonly resolveFocus: (element: HTMLElement | undefined) => void;
|
readonly resolveFocus: (element: HTMLElement | undefined) => void;
|
||||||
@ -192,7 +188,6 @@ export namespace FilterableListContainer {
|
|||||||
progressId: string;
|
progressId: string;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
readonly commandService: CommandService;
|
readonly commandService: CommandService;
|
||||||
readonly sort: (items: T[]) => T[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State<T, S extends Searchable.Options> {
|
export interface State<T, S extends Searchable.Options> {
|
||||||
|
@ -53,11 +53,9 @@ export abstract class ListWidget<
|
|||||||
*/
|
*/
|
||||||
protected firstActivate = true;
|
protected firstActivate = true;
|
||||||
|
|
||||||
protected readonly defaultSortComparator: (left: T, right: T) => number;
|
|
||||||
|
|
||||||
constructor(protected options: ListWidget.Options<T, S>) {
|
constructor(protected options: ListWidget.Options<T, S>) {
|
||||||
super();
|
super();
|
||||||
const { id, label, iconClass, itemDeprecated, itemLabel } = options;
|
const { id, label, iconClass } = options;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.title.label = label;
|
this.title.label = label;
|
||||||
this.title.caption = label;
|
this.title.caption = label;
|
||||||
@ -67,15 +65,6 @@ export abstract class ListWidget<
|
|||||||
this.node.tabIndex = 0; // To be able to set the focus on the widget.
|
this.node.tabIndex = 0; // To be able to set the focus on the widget.
|
||||||
this.scrollOptions = undefined;
|
this.scrollOptions = undefined;
|
||||||
this.toDispose.push(this.searchOptionsChangeEmitter);
|
this.toDispose.push(this.searchOptionsChangeEmitter);
|
||||||
|
|
||||||
this.defaultSortComparator = (left, right): number => {
|
|
||||||
// always put deprecated items at the bottom of the list
|
|
||||||
if (itemDeprecated(left)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return itemLabel(left).localeCompare(itemLabel(right));
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
@ -144,30 +133,6 @@ export abstract class ListWidget<
|
|||||||
return this.options.installable.uninstall({ item, progressId });
|
return this.options.installable.uninstall({ item, progressId });
|
||||||
}
|
}
|
||||||
|
|
||||||
protected filterableListSort = (items: T[]): T[] => {
|
|
||||||
const isArduinoTypeComparator = (left: T, right: T) => {
|
|
||||||
const aIsArduinoType = left.types.includes('Arduino');
|
|
||||||
const bIsArduinoType = right.types.includes('Arduino');
|
|
||||||
|
|
||||||
if (aIsArduinoType && !bIsArduinoType && !left.deprecated) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!aIsArduinoType && bIsArduinoType && !right.deprecated) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
return items.sort((left, right) => {
|
|
||||||
return (
|
|
||||||
isArduinoTypeComparator(left, right) ||
|
|
||||||
this.defaultSortComparator(left, right)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<FilterableListContainer<T, S>
|
<FilterableListContainer<T, S>
|
||||||
@ -178,14 +143,12 @@ export abstract class ListWidget<
|
|||||||
install={this.install.bind(this)}
|
install={this.install.bind(this)}
|
||||||
uninstall={this.uninstall.bind(this)}
|
uninstall={this.uninstall.bind(this)}
|
||||||
itemLabel={this.options.itemLabel}
|
itemLabel={this.options.itemLabel}
|
||||||
itemDeprecated={this.options.itemDeprecated}
|
|
||||||
itemRenderer={this.options.itemRenderer}
|
itemRenderer={this.options.itemRenderer}
|
||||||
filterRenderer={this.options.filterRenderer}
|
filterRenderer={this.options.filterRenderer}
|
||||||
searchOptionsDidChange={this.searchOptionsChangeEmitter.event}
|
searchOptionsDidChange={this.searchOptionsChangeEmitter.event}
|
||||||
messageService={this.messageService}
|
messageService={this.messageService}
|
||||||
commandService={this.commandService}
|
commandService={this.commandService}
|
||||||
responseService={this.responseService}
|
responseService={this.responseService}
|
||||||
sort={this.filterableListSort}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -218,7 +181,6 @@ export namespace ListWidget {
|
|||||||
readonly installable: Installable<T>;
|
readonly installable: Installable<T>;
|
||||||
readonly searchable: Searchable<T, S>;
|
readonly searchable: Searchable<T, S>;
|
||||||
readonly itemLabel: (item: T) => string;
|
readonly itemLabel: (item: T) => string;
|
||||||
readonly itemDeprecated: (item: T) => boolean;
|
|
||||||
readonly itemRenderer: ListItemRenderer<T>;
|
readonly itemRenderer: ListItemRenderer<T>;
|
||||||
readonly filterRenderer: FilterRenderer<S>;
|
readonly filterRenderer: FilterRenderer<S>;
|
||||||
readonly defaultSearchOptions: S;
|
readonly defaultSearchOptions: S;
|
||||||
|
@ -2,7 +2,7 @@ import { Installable } from './installable';
|
|||||||
|
|
||||||
export interface ArduinoComponent {
|
export interface ArduinoComponent {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly deprecated: boolean;
|
readonly deprecated?: boolean;
|
||||||
readonly author: string;
|
readonly author: string;
|
||||||
readonly summary: string;
|
readonly summary: string;
|
||||||
readonly description: string;
|
readonly description: string;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
|
import type { ArduinoComponent } from './arduino-component';
|
||||||
|
|
||||||
export interface Searchable<T, O extends Searchable.Options> {
|
export interface Searchable<T, O extends Searchable.Options> {
|
||||||
search(options: O): Promise<T[]>;
|
search(options: O): Promise<T[]>;
|
||||||
@ -31,3 +32,31 @@ export namespace Searchable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IDE2 must keep the library search order from the CLI but do additional boosting
|
||||||
|
// https://github.com/arduino/arduino-ide/issues/1106
|
||||||
|
// This additional search result boosting considers the following groups: 'Arduino', '', 'Arduino-Retired', and 'Retired'.
|
||||||
|
// If two libraries fall into the same group, the original index is the tiebreaker.
|
||||||
|
export type SortGroup = 'Arduino' | '' | 'Arduino-Retired' | 'Retired';
|
||||||
|
const sortGroupOrder: Record<SortGroup, number> = {
|
||||||
|
Arduino: 0,
|
||||||
|
'': 1,
|
||||||
|
'Arduino-Retired': 2,
|
||||||
|
Retired: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function sortComponents<T extends ArduinoComponent>(
|
||||||
|
components: T[],
|
||||||
|
group: (component: T) => SortGroup
|
||||||
|
): T[] {
|
||||||
|
return components
|
||||||
|
.map((component, index) => ({ ...component, index }))
|
||||||
|
.sort((left, right) => {
|
||||||
|
const leftGroup = group(left);
|
||||||
|
const rightGroup = group(right);
|
||||||
|
if (leftGroup === rightGroup) {
|
||||||
|
return left.index - right.index;
|
||||||
|
}
|
||||||
|
return sortGroupOrder[leftGroup] - sortGroupOrder[rightGroup];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -17,6 +17,8 @@ import {
|
|||||||
BoardWithPackage,
|
BoardWithPackage,
|
||||||
BoardUserField,
|
BoardUserField,
|
||||||
BoardSearch,
|
BoardSearch,
|
||||||
|
sortComponents,
|
||||||
|
SortGroup,
|
||||||
} from '../common/protocol';
|
} from '../common/protocol';
|
||||||
import {
|
import {
|
||||||
PlatformInstallRequest,
|
PlatformInstallRequest,
|
||||||
@ -405,7 +407,8 @@ export class BoardsServiceImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filter = this.typePredicate(options);
|
const filter = this.typePredicate(options);
|
||||||
return [...packages.values()].filter(filter);
|
const boardsPackages = [...packages.values()].filter(filter);
|
||||||
|
return sortComponents(boardsPackages, boardsPackageSortGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
private typePredicate(
|
private typePredicate(
|
||||||
@ -559,3 +562,14 @@ function isMissingPlatformError(error: unknown): boolean {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function boardsPackageSortGroup(boardsPackage: BoardsPackage): SortGroup {
|
||||||
|
const types: string[] = [];
|
||||||
|
if (boardsPackage.types.includes('Arduino')) {
|
||||||
|
types.push('Arduino');
|
||||||
|
}
|
||||||
|
if (boardsPackage.deprecated) {
|
||||||
|
types.push('Retired');
|
||||||
|
}
|
||||||
|
return types.join('-') as SortGroup;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,14 @@
|
|||||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
import { ILogger, notEmpty } from '@theia/core';
|
||||||
|
import { FileUri } from '@theia/core/lib/node';
|
||||||
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { duration } from '../common/decorators';
|
||||||
|
import {
|
||||||
|
NotificationServiceServer,
|
||||||
|
ResponseService,
|
||||||
|
sortComponents,
|
||||||
|
SortGroup,
|
||||||
|
} from '../common/protocol';
|
||||||
|
import { Installable } from '../common/protocol/installable';
|
||||||
import {
|
import {
|
||||||
LibraryDependency,
|
LibraryDependency,
|
||||||
LibraryLocation,
|
LibraryLocation,
|
||||||
@ -6,29 +16,24 @@ import {
|
|||||||
LibrarySearch,
|
LibrarySearch,
|
||||||
LibraryService,
|
LibraryService,
|
||||||
} from '../common/protocol/library-service';
|
} from '../common/protocol/library-service';
|
||||||
import { CoreClientAware } from './core-client-provider';
|
|
||||||
import { BoardDiscovery } from './board-discovery';
|
import { BoardDiscovery } from './board-discovery';
|
||||||
import {
|
import {
|
||||||
InstalledLibrary,
|
InstalledLibrary,
|
||||||
Library,
|
Library,
|
||||||
|
LibraryInstallLocation,
|
||||||
LibraryInstallRequest,
|
LibraryInstallRequest,
|
||||||
LibraryListRequest,
|
LibraryListRequest,
|
||||||
LibraryListResponse,
|
LibraryListResponse,
|
||||||
LibraryLocation as GrpcLibraryLocation,
|
LibraryLocation as GrpcLibraryLocation,
|
||||||
LibraryRelease,
|
LibraryRelease,
|
||||||
LibraryResolveDependenciesRequest,
|
LibraryResolveDependenciesRequest,
|
||||||
LibraryUninstallRequest,
|
|
||||||
ZipLibraryInstallRequest,
|
|
||||||
LibrarySearchRequest,
|
LibrarySearchRequest,
|
||||||
LibrarySearchResponse,
|
LibrarySearchResponse,
|
||||||
LibraryInstallLocation,
|
LibraryUninstallRequest,
|
||||||
|
ZipLibraryInstallRequest,
|
||||||
} from './cli-protocol/cc/arduino/cli/commands/v1/lib_pb';
|
} from './cli-protocol/cc/arduino/cli/commands/v1/lib_pb';
|
||||||
import { Installable } from '../common/protocol/installable';
|
import { CoreClientAware } from './core-client-provider';
|
||||||
import { ILogger, notEmpty } from '@theia/core';
|
|
||||||
import { FileUri } from '@theia/core/lib/node';
|
|
||||||
import { ResponseService, NotificationServiceServer } from '../common/protocol';
|
|
||||||
import { ExecuteWithProgress } from './grpc-progressible';
|
import { ExecuteWithProgress } from './grpc-progressible';
|
||||||
import { duration } from '../common/decorators';
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class LibraryServiceImpl
|
export class LibraryServiceImpl
|
||||||
@ -108,7 +113,10 @@ export class LibraryServiceImpl
|
|||||||
|
|
||||||
const typePredicate = this.typePredicate(options);
|
const typePredicate = this.typePredicate(options);
|
||||||
const topicPredicate = this.topicPredicate(options);
|
const topicPredicate = this.topicPredicate(options);
|
||||||
return items.filter((item) => typePredicate(item) && topicPredicate(item));
|
const libraries = items.filter(
|
||||||
|
(item) => typePredicate(item) && topicPredicate(item)
|
||||||
|
);
|
||||||
|
return sortComponents(libraries, librarySortGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
private typePredicate(
|
private typePredicate(
|
||||||
@ -448,7 +456,6 @@ function toLibrary(
|
|||||||
name: '',
|
name: '',
|
||||||
exampleUris: [],
|
exampleUris: [],
|
||||||
installable: false,
|
installable: false,
|
||||||
deprecated: false,
|
|
||||||
location: 0,
|
location: 0,
|
||||||
...pkg,
|
...pkg,
|
||||||
|
|
||||||
@ -462,3 +469,14 @@ function toLibrary(
|
|||||||
types: lib.getTypesList(),
|
types: lib.getTypesList(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Libraries do not have a deprecated property. The deprecated information is inferred if 'Retired' is in 'types'
|
||||||
|
function librarySortGroup(library: LibraryPackage): SortGroup {
|
||||||
|
const types: string[] = [];
|
||||||
|
for (const type of ['Arduino', 'Retired']) {
|
||||||
|
if (library.types.includes(type)) {
|
||||||
|
types.push(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return types.join('-') as SortGroup;
|
||||||
|
}
|
||||||
|
@ -0,0 +1,114 @@
|
|||||||
|
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||||
|
import { Container } from '@theia/core/shared/inversify';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { BoardSearch, BoardsService } from '../../common/protocol';
|
||||||
|
import {
|
||||||
|
configureBackendApplicationConfigProvider,
|
||||||
|
createBaseContainer,
|
||||||
|
startDaemon,
|
||||||
|
} from './test-bindings';
|
||||||
|
|
||||||
|
describe('boards-service-impl', () => {
|
||||||
|
let boardService: BoardsService;
|
||||||
|
let toDispose: Disposable[] = [];
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
configureBackendApplicationConfigProvider();
|
||||||
|
this.timeout(20_000);
|
||||||
|
toDispose = [];
|
||||||
|
const container = createContainer();
|
||||||
|
await start(container, toDispose);
|
||||||
|
boardService = container.get<BoardsService>(BoardsService);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
let disposable = toDispose.pop();
|
||||||
|
while (disposable) {
|
||||||
|
try {
|
||||||
|
disposable?.dispose();
|
||||||
|
} catch {}
|
||||||
|
disposable = toDispose.pop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search', () => {
|
||||||
|
it('should run search', async function () {
|
||||||
|
const result = await boardService.search({});
|
||||||
|
expect(result).is.not.empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should boost a result when 'types' includes 'arduino', and lower the score if deprecated", async function () {
|
||||||
|
const result = await boardService.search({});
|
||||||
|
const arduinoIndexes: number[] = [];
|
||||||
|
const otherIndexes: number[] = [];
|
||||||
|
const deprecatedArduinoIndexes: number[] = [];
|
||||||
|
const deprecatedOtherIndexes: number[] = [];
|
||||||
|
const arduino: BoardSearch.Type = 'Arduino';
|
||||||
|
result.forEach((platform, index) => {
|
||||||
|
if (platform.types.includes(arduino)) {
|
||||||
|
if (platform.deprecated) {
|
||||||
|
deprecatedArduinoIndexes.push(index);
|
||||||
|
} else {
|
||||||
|
arduinoIndexes.push(index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (platform.deprecated) {
|
||||||
|
deprecatedOtherIndexes.push(index);
|
||||||
|
} else {
|
||||||
|
otherIndexes.push(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
arduinoIndexes.forEach(
|
||||||
|
(index) =>
|
||||||
|
expect(otherIndexes.every((otherIndex) => otherIndex > index)).to.be
|
||||||
|
.true
|
||||||
|
);
|
||||||
|
otherIndexes.forEach(
|
||||||
|
(index) =>
|
||||||
|
expect(
|
||||||
|
deprecatedArduinoIndexes.every(
|
||||||
|
(deprecatedArduinoIndex) => deprecatedArduinoIndex > index
|
||||||
|
)
|
||||||
|
).to.be.true
|
||||||
|
);
|
||||||
|
deprecatedArduinoIndexes.forEach(
|
||||||
|
(index) =>
|
||||||
|
expect(
|
||||||
|
deprecatedOtherIndexes.every(
|
||||||
|
(deprecatedOtherIndex) => deprecatedOtherIndex > index
|
||||||
|
)
|
||||||
|
).to.be.true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should boost 'arduino' and deprecated to the end of the results", async function () {
|
||||||
|
const query = 'OS';
|
||||||
|
const result = await boardService.search({ query });
|
||||||
|
expect(result.length).greaterThan(1);
|
||||||
|
const lastIndex = result.length - 1;
|
||||||
|
const last = result[lastIndex];
|
||||||
|
expect(last.id).to.be.equal('arduino:mbed');
|
||||||
|
expect(last.deprecated).to.be.true;
|
||||||
|
const windowsIoTCoreIndex = result.findIndex(
|
||||||
|
(platform) => platform.id === 'Microsoft:win10'
|
||||||
|
);
|
||||||
|
expect(windowsIoTCoreIndex).to.be.greaterThanOrEqual(0);
|
||||||
|
expect(windowsIoTCoreIndex).to.be.lessThan(lastIndex);
|
||||||
|
const first = result[0];
|
||||||
|
expect(typeof first.deprecated).to.be.equal('boolean');
|
||||||
|
expect(first.deprecated).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createContainer(): Container {
|
||||||
|
return createBaseContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start(
|
||||||
|
container: Container,
|
||||||
|
toDispose: Disposable[]
|
||||||
|
): Promise<void> {
|
||||||
|
return startDaemon(container, toDispose);
|
||||||
|
}
|
@ -1,61 +1,20 @@
|
|||||||
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';
|
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';
|
||||||
import {
|
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||||
CommandContribution,
|
|
||||||
CommandRegistry,
|
|
||||||
CommandService,
|
|
||||||
} from '@theia/core/lib/common/command';
|
|
||||||
import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider';
|
|
||||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||||
import { EnvVariablesServer as TheiaEnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
||||||
import { ILogger, Loggable } from '@theia/core/lib/common/logger';
|
|
||||||
import { LogLevel } from '@theia/core/lib/common/logger-protocol';
|
|
||||||
import { isWindows } from '@theia/core/lib/common/os';
|
import { isWindows } from '@theia/core/lib/common/os';
|
||||||
import { waitForEvent } from '@theia/core/lib/common/promise-util';
|
|
||||||
import { MockLogger } from '@theia/core/lib/common/test/mock-logger';
|
|
||||||
import { BackendApplicationConfigProvider } from '@theia/core/lib/node/backend-application-config-provider';
|
|
||||||
import { FileUri } from '@theia/core/lib/node/file-uri';
|
import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||||
import {
|
import { Container, injectable } from '@theia/core/shared/inversify';
|
||||||
Container,
|
|
||||||
ContainerModule,
|
|
||||||
injectable,
|
|
||||||
} from '@theia/core/shared/inversify';
|
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import {
|
import {
|
||||||
ArduinoDaemon,
|
|
||||||
AttachedBoardsChangeEvent,
|
|
||||||
AvailablePorts,
|
|
||||||
BoardsPackage,
|
|
||||||
BoardsService,
|
BoardsService,
|
||||||
ConfigService,
|
|
||||||
ConfigState,
|
|
||||||
CoreService,
|
CoreService,
|
||||||
IndexUpdateDidCompleteParams,
|
|
||||||
IndexUpdateDidFailParams,
|
|
||||||
IndexUpdateParams,
|
|
||||||
LibraryPackage,
|
|
||||||
NotificationServiceClient,
|
|
||||||
NotificationServiceServer,
|
|
||||||
OutputMessage,
|
|
||||||
ProgressMessage,
|
|
||||||
ResponseService,
|
|
||||||
Sketch,
|
|
||||||
SketchesService,
|
SketchesService,
|
||||||
} from '../../common/protocol';
|
} from '../../common/protocol';
|
||||||
import { ArduinoDaemonImpl } from '../../node/arduino-daemon-impl';
|
|
||||||
import { BoardDiscovery } from '../../node/board-discovery';
|
|
||||||
import { BoardsServiceImpl } from '../../node/boards-service-impl';
|
|
||||||
import { ConfigServiceImpl } from '../../node/config-service-impl';
|
|
||||||
import { CoreClientProvider } from '../../node/core-client-provider';
|
|
||||||
import { CoreServiceImpl } from '../../node/core-service-impl';
|
|
||||||
import { IsTempSketch } from '../../node/is-temp-sketch';
|
|
||||||
import { MonitorManager } from '../../node/monitor-manager';
|
|
||||||
import { MonitorService } from '../../node/monitor-service';
|
|
||||||
import {
|
import {
|
||||||
MonitorServiceFactory,
|
configureBackendApplicationConfigProvider,
|
||||||
MonitorServiceFactoryOptions,
|
createBaseContainer,
|
||||||
} from '../../node/monitor-service-factory';
|
startDaemon,
|
||||||
import { SketchesServiceImpl } from '../../node/sketches-service-impl';
|
} from './test-bindings';
|
||||||
import { EnvVariablesServer } from '../../node/theia/env-variables/env-variables-server';
|
|
||||||
|
|
||||||
const testTimeout = 30_000;
|
const testTimeout = 30_000;
|
||||||
const setupTimeout = 5 * 60 * 1_000; // five minutes
|
const setupTimeout = 5 * 60 * 1_000; // five minutes
|
||||||
@ -67,7 +26,7 @@ describe('core-service-impl', () => {
|
|||||||
let toDispose: Disposable[];
|
let toDispose: Disposable[];
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
BackendApplicationConfigProvider.set({ configDirName: '.testArduinoIDE' });
|
configureBackendApplicationConfigProvider();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
@ -135,12 +94,7 @@ async function start(
|
|||||||
container: Container,
|
container: Container,
|
||||||
toDispose: Disposable[]
|
toDispose: Disposable[]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const daemon = container.get<ArduinoDaemonImpl>(ArduinoDaemonImpl);
|
await startDaemon(container, toDispose, async (container) => {
|
||||||
const configService = container.get<ConfigServiceImpl>(ConfigServiceImpl);
|
|
||||||
toDispose.push(Disposable.create(() => daemon.stop()));
|
|
||||||
configService.onStart();
|
|
||||||
daemon.onStart();
|
|
||||||
await waitForEvent(daemon.onDaemonStarted, 10_000);
|
|
||||||
const boardService = container.get<BoardsService>(BoardsService);
|
const boardService = container.get<BoardsService>(BoardsService);
|
||||||
const searchResults = await boardService.search({ query: avr });
|
const searchResults = await boardService.search({ query: avr });
|
||||||
const platform = searchResults.find(({ id }) => id === avr);
|
const platform = searchResults.find(({ id }) => id === avr);
|
||||||
@ -148,146 +102,14 @@ async function start(
|
|||||||
throw new Error(`Could not find platform: ${avr}`);
|
throw new Error(`Could not find platform: ${avr}`);
|
||||||
}
|
}
|
||||||
await boardService.install({ item: platform, skipPostInstall: true });
|
await boardService.install({ item: platform, skipPostInstall: true });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createContainer(): Container {
|
function createContainer(): Container {
|
||||||
const container = new Container({ defaultScope: 'Singleton' });
|
return createBaseContainer((bind) => {
|
||||||
const module = new ContainerModule((bind) => {
|
|
||||||
bind(CoreClientProvider).toSelf().inSingletonScope();
|
|
||||||
bind(CoreServiceImpl).toSelf().inSingletonScope();
|
|
||||||
bind(CoreService).toService(CoreServiceImpl);
|
|
||||||
bind(BoardsServiceImpl).toSelf().inSingletonScope();
|
|
||||||
bind(BoardsService).toService(BoardsServiceImpl);
|
|
||||||
bind(TestResponseService).toSelf().inSingletonScope();
|
|
||||||
bind(ResponseService).toService(TestResponseService);
|
|
||||||
bind(MonitorManager).toSelf().inSingletonScope();
|
|
||||||
bind(MonitorServiceFactory).toFactory(
|
|
||||||
({ container }) =>
|
|
||||||
(options: MonitorServiceFactoryOptions) => {
|
|
||||||
const child = container.createChild();
|
|
||||||
child
|
|
||||||
.bind<MonitorServiceFactoryOptions>(MonitorServiceFactoryOptions)
|
|
||||||
.toConstantValue({
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
child.bind(MonitorService).toSelf();
|
|
||||||
return child.get<MonitorService>(MonitorService);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
bind(EnvVariablesServer).toSelf().inSingletonScope();
|
|
||||||
bind(TheiaEnvVariablesServer).toService(EnvVariablesServer);
|
|
||||||
bind(SilentArduinoDaemon).toSelf().inSingletonScope();
|
|
||||||
bind(ArduinoDaemon).toService(SilentArduinoDaemon);
|
|
||||||
bind(ArduinoDaemonImpl).toService(SilentArduinoDaemon);
|
|
||||||
bind(ConsoleLogger).toSelf().inSingletonScope();
|
|
||||||
bind(ILogger).toService(ConsoleLogger);
|
|
||||||
bind(TestNotificationServiceServer).toSelf().inSingletonScope();
|
|
||||||
bind(NotificationServiceServer).toService(TestNotificationServiceServer);
|
|
||||||
bind(ConfigServiceImpl).toSelf().inSingletonScope();
|
|
||||||
bind(ConfigService).toService(ConfigServiceImpl);
|
|
||||||
bind(TestCommandRegistry).toSelf().inSingletonScope();
|
bind(TestCommandRegistry).toSelf().inSingletonScope();
|
||||||
bind(CommandRegistry).toService(TestCommandRegistry);
|
bind(CommandRegistry).toService(TestCommandRegistry);
|
||||||
bind(CommandService).toService(CommandRegistry);
|
|
||||||
bindContributionProvider(bind, CommandContribution);
|
|
||||||
bind(TestBoardDiscovery).toSelf().inSingletonScope();
|
|
||||||
bind(BoardDiscovery).toService(TestBoardDiscovery);
|
|
||||||
bind(IsTempSketch).toSelf().inSingletonScope();
|
|
||||||
bind(SketchesServiceImpl).toSelf().inSingletonScope();
|
|
||||||
bind(SketchesService).toService(SketchesServiceImpl);
|
|
||||||
});
|
});
|
||||||
container.load(module);
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
class TestResponseService implements ResponseService {
|
|
||||||
readonly outputMessages: OutputMessage[] = [];
|
|
||||||
readonly progressMessages: ProgressMessage[] = [];
|
|
||||||
|
|
||||||
appendToOutput(message: OutputMessage): void {
|
|
||||||
this.outputMessages.push(message);
|
|
||||||
}
|
|
||||||
reportProgress(message: ProgressMessage): void {
|
|
||||||
this.progressMessages.push(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
class TestNotificationServiceServer implements NotificationServiceServer {
|
|
||||||
readonly events: string[] = [];
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
|
|
||||||
disposeClient(client: NotificationServiceClient): void {
|
|
||||||
this.events.push('disposeClient:');
|
|
||||||
}
|
|
||||||
notifyDidReinitialize(): void {
|
|
||||||
this.events.push('notifyDidReinitialize:');
|
|
||||||
}
|
|
||||||
notifyIndexUpdateWillStart(params: IndexUpdateParams): void {
|
|
||||||
this.events.push(`notifyIndexUpdateWillStart:${JSON.stringify(params)}`);
|
|
||||||
}
|
|
||||||
notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void {
|
|
||||||
this.events.push(
|
|
||||||
`notifyIndexUpdateDidProgress:${JSON.stringify(progressMessage)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
notifyIndexUpdateDidComplete(params: IndexUpdateDidCompleteParams): void {
|
|
||||||
this.events.push(`notifyIndexUpdateDidComplete:${JSON.stringify(params)}`);
|
|
||||||
}
|
|
||||||
notifyIndexUpdateDidFail(params: IndexUpdateDidFailParams): void {
|
|
||||||
this.events.push(`notifyIndexUpdateDidFail:${JSON.stringify(params)}`);
|
|
||||||
}
|
|
||||||
notifyDaemonDidStart(port: string): void {
|
|
||||||
this.events.push(`notifyDaemonDidStart:${port}`);
|
|
||||||
}
|
|
||||||
notifyDaemonDidStop(): void {
|
|
||||||
this.events.push('notifyDaemonDidStop:');
|
|
||||||
}
|
|
||||||
notifyConfigDidChange(event: ConfigState): void {
|
|
||||||
this.events.push(`notifyConfigDidChange:${JSON.stringify(event)}`);
|
|
||||||
}
|
|
||||||
notifyPlatformDidInstall(event: { item: BoardsPackage }): void {
|
|
||||||
this.events.push(`notifyPlatformDidInstall:${JSON.stringify(event)}`);
|
|
||||||
}
|
|
||||||
notifyPlatformDidUninstall(event: { item: BoardsPackage }): void {
|
|
||||||
this.events.push(`notifyPlatformDidUninstall:${JSON.stringify(event)}`);
|
|
||||||
}
|
|
||||||
notifyLibraryDidInstall(event: {
|
|
||||||
item: LibraryPackage | 'zip-install';
|
|
||||||
}): void {
|
|
||||||
this.events.push(`notifyLibraryDidInstall:${JSON.stringify(event)}`);
|
|
||||||
}
|
|
||||||
notifyLibraryDidUninstall(event: { item: LibraryPackage }): void {
|
|
||||||
this.events.push(`notifyLibraryDidUninstall:${JSON.stringify(event)}`);
|
|
||||||
}
|
|
||||||
notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void {
|
|
||||||
this.events.push(`notifyAttachedBoardsDidChange:${JSON.stringify(event)}`);
|
|
||||||
}
|
|
||||||
notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void {
|
|
||||||
this.events.push(`notifyRecentSketchesDidChange:${JSON.stringify(event)}`);
|
|
||||||
}
|
|
||||||
dispose(): void {
|
|
||||||
this.events.push('dispose:');
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
|
|
||||||
setClient(client: NotificationServiceClient | undefined): void {
|
|
||||||
this.events.push('setClient:');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
class TestBoardDiscovery extends BoardDiscovery {
|
|
||||||
mutableAvailablePorts: AvailablePorts = {};
|
|
||||||
|
|
||||||
override async start(): Promise<void> {
|
|
||||||
// NOOP
|
|
||||||
}
|
|
||||||
override async stop(): Promise<void> {
|
|
||||||
// NOOP
|
|
||||||
}
|
|
||||||
override get availablePorts(): AvailablePorts {
|
|
||||||
return this.mutableAvailablePorts;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
@ -314,88 +136,3 @@ class TestCommandRegistry extends CommandRegistry {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@injectable()
|
|
||||||
class ConsoleLogger extends MockLogger {
|
|
||||||
override log(
|
|
||||||
logLevel: number,
|
|
||||||
arg2: string | Loggable | Error,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
...params: any[]
|
|
||||||
): Promise<void> {
|
|
||||||
if (arg2 instanceof Error) {
|
|
||||||
return this.error(String(arg2), params);
|
|
||||||
}
|
|
||||||
switch (logLevel) {
|
|
||||||
case LogLevel.INFO:
|
|
||||||
return this.info(arg2, params);
|
|
||||||
case LogLevel.WARN:
|
|
||||||
return this.warn(arg2, params);
|
|
||||||
case LogLevel.TRACE:
|
|
||||||
return this.trace(arg2, params);
|
|
||||||
case LogLevel.ERROR:
|
|
||||||
return this.error(arg2, params);
|
|
||||||
case LogLevel.FATAL:
|
|
||||||
return this.fatal(arg2, params);
|
|
||||||
default:
|
|
||||||
return this.info(arg2, params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
override async info(arg: string | Loggable, ...params: any[]): Promise<void> {
|
|
||||||
if (params.length) {
|
|
||||||
console.info(arg, ...params);
|
|
||||||
} else {
|
|
||||||
console.info(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override async trace(
|
|
||||||
arg: string | Loggable,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
...params: any[]
|
|
||||||
): Promise<void> {
|
|
||||||
if (params.length) {
|
|
||||||
console.trace(arg, ...params);
|
|
||||||
} else {
|
|
||||||
console.trace(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
override async warn(arg: string | Loggable, ...params: any[]): Promise<void> {
|
|
||||||
if (params.length) {
|
|
||||||
console.warn(arg, ...params);
|
|
||||||
} else {
|
|
||||||
console.warn(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override async error(
|
|
||||||
arg: string | Loggable,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
...params: any[]
|
|
||||||
): Promise<void> {
|
|
||||||
if (params.length) {
|
|
||||||
console.error(arg, ...params);
|
|
||||||
} else {
|
|
||||||
console.error(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override async fatal(
|
|
||||||
arg: string | Loggable,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
...params: any[]
|
|
||||||
): Promise<void> {
|
|
||||||
return this.error(arg, params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
class SilentArduinoDaemon extends ArduinoDaemonImpl {
|
|
||||||
protected override onData(): void {
|
|
||||||
// NOOP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||||
|
import { Container } from '@theia/core/shared/inversify';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { LibrarySearch, LibraryService } from '../../common/protocol';
|
||||||
|
import { LibraryServiceImpl } from '../../node/library-service-impl';
|
||||||
|
import {
|
||||||
|
configureBackendApplicationConfigProvider,
|
||||||
|
createBaseContainer,
|
||||||
|
startDaemon,
|
||||||
|
} from './test-bindings';
|
||||||
|
|
||||||
|
describe('library-service-impl', () => {
|
||||||
|
let libraryService: LibraryService;
|
||||||
|
let toDispose: Disposable[] = [];
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
configureBackendApplicationConfigProvider();
|
||||||
|
this.timeout(20_000);
|
||||||
|
toDispose = [];
|
||||||
|
const container = createContainer();
|
||||||
|
await start(container, toDispose);
|
||||||
|
libraryService = container.get<LibraryService>(LibraryService);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
let disposable = toDispose.pop();
|
||||||
|
while (disposable) {
|
||||||
|
try {
|
||||||
|
disposable?.dispose();
|
||||||
|
} catch {}
|
||||||
|
disposable = toDispose.pop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search', () => {
|
||||||
|
it('should run search', async function () {
|
||||||
|
const result = await libraryService.search({});
|
||||||
|
expect(result).is.not.empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should boost a result when 'types' includes 'arduino'", async function () {
|
||||||
|
const result = await libraryService.search({});
|
||||||
|
const arduinoIndexes: number[] = [];
|
||||||
|
const otherIndexes: number[] = [];
|
||||||
|
// Special `"types": ["Arduino", "Retired"]` case handling: https://github.com/arduino/arduino-ide/issues/1106#issuecomment-1419392742
|
||||||
|
const retiredIndexes: number[] = [];
|
||||||
|
const arduino: LibrarySearch.Type = 'Arduino';
|
||||||
|
const retired: LibrarySearch.Type = 'Retired';
|
||||||
|
result
|
||||||
|
.filter((library) => library.types.length === 1)
|
||||||
|
.forEach((library, index) => {
|
||||||
|
if (library.types.includes(arduino)) {
|
||||||
|
if (library.types.includes(retired)) {
|
||||||
|
retiredIndexes.push(index);
|
||||||
|
} else {
|
||||||
|
arduinoIndexes.push(index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
otherIndexes.push(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
arduinoIndexes.forEach(
|
||||||
|
(index) =>
|
||||||
|
expect(otherIndexes.every((otherIndex) => otherIndex > index)).to.be
|
||||||
|
.true
|
||||||
|
);
|
||||||
|
otherIndexes.forEach(
|
||||||
|
(index) =>
|
||||||
|
expect(retiredIndexes.every((retiredIndex) => retiredIndex > index))
|
||||||
|
.to.be.true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should boost library 'SD' to the top if the query term is 'SD'", async function () {
|
||||||
|
const query = 'SD';
|
||||||
|
const result = await libraryService.search({ query });
|
||||||
|
expect(result.length).greaterThan(1);
|
||||||
|
expect(result[0].name).to.be.equal(query);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createContainer(): Container {
|
||||||
|
return createBaseContainer((bind) => {
|
||||||
|
bind(LibraryServiceImpl).toSelf().inSingletonScope();
|
||||||
|
bind(LibraryService).toService(LibraryServiceImpl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start(
|
||||||
|
container: Container,
|
||||||
|
toDispose: Disposable[]
|
||||||
|
): Promise<void> {
|
||||||
|
return startDaemon(container, toDispose);
|
||||||
|
}
|
320
arduino-ide-extension/src/test/node/test-bindings.ts
Normal file
320
arduino-ide-extension/src/test/node/test-bindings.ts
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
import {
|
||||||
|
CommandContribution,
|
||||||
|
CommandRegistry,
|
||||||
|
CommandService,
|
||||||
|
} from '@theia/core/lib/common/command';
|
||||||
|
import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider';
|
||||||
|
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||||
|
import { EnvVariablesServer as TheiaEnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||||
|
import { ILogger, Loggable } from '@theia/core/lib/common/logger';
|
||||||
|
import { LogLevel } from '@theia/core/lib/common/logger-protocol';
|
||||||
|
import { waitForEvent } from '@theia/core/lib/common/promise-util';
|
||||||
|
import { MockLogger } from '@theia/core/lib/common/test/mock-logger';
|
||||||
|
import { BackendApplicationConfigProvider } from '@theia/core/lib/node/backend-application-config-provider';
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
ContainerModule,
|
||||||
|
injectable,
|
||||||
|
interfaces,
|
||||||
|
} from '@theia/core/shared/inversify';
|
||||||
|
import {
|
||||||
|
ArduinoDaemon,
|
||||||
|
AttachedBoardsChangeEvent,
|
||||||
|
AvailablePorts,
|
||||||
|
BoardsPackage,
|
||||||
|
BoardsService,
|
||||||
|
ConfigService,
|
||||||
|
ConfigState,
|
||||||
|
CoreService,
|
||||||
|
IndexUpdateDidCompleteParams,
|
||||||
|
IndexUpdateDidFailParams,
|
||||||
|
IndexUpdateParams,
|
||||||
|
LibraryPackage,
|
||||||
|
NotificationServiceClient,
|
||||||
|
NotificationServiceServer,
|
||||||
|
OutputMessage,
|
||||||
|
ProgressMessage,
|
||||||
|
ResponseService,
|
||||||
|
Sketch,
|
||||||
|
SketchesService,
|
||||||
|
} from '../../common/protocol';
|
||||||
|
import { ArduinoDaemonImpl } from '../../node/arduino-daemon-impl';
|
||||||
|
import { BoardDiscovery } from '../../node/board-discovery';
|
||||||
|
import { BoardsServiceImpl } from '../../node/boards-service-impl';
|
||||||
|
import { ConfigServiceImpl } from '../../node/config-service-impl';
|
||||||
|
import { CoreClientProvider } from '../../node/core-client-provider';
|
||||||
|
import { CoreServiceImpl } from '../../node/core-service-impl';
|
||||||
|
import { IsTempSketch } from '../../node/is-temp-sketch';
|
||||||
|
import { MonitorManager } from '../../node/monitor-manager';
|
||||||
|
import { MonitorService } from '../../node/monitor-service';
|
||||||
|
import {
|
||||||
|
MonitorServiceFactory,
|
||||||
|
MonitorServiceFactoryOptions,
|
||||||
|
} from '../../node/monitor-service-factory';
|
||||||
|
import { SketchesServiceImpl } from '../../node/sketches-service-impl';
|
||||||
|
import { EnvVariablesServer } from '../../node/theia/env-variables/env-variables-server';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class ConsoleLogger extends MockLogger {
|
||||||
|
override log(
|
||||||
|
logLevel: number,
|
||||||
|
arg2: string | Loggable | Error,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
...params: any[]
|
||||||
|
): Promise<void> {
|
||||||
|
if (arg2 instanceof Error) {
|
||||||
|
return this.error(String(arg2), params);
|
||||||
|
}
|
||||||
|
switch (logLevel) {
|
||||||
|
case LogLevel.INFO:
|
||||||
|
return this.info(arg2, params);
|
||||||
|
case LogLevel.WARN:
|
||||||
|
return this.warn(arg2, params);
|
||||||
|
case LogLevel.TRACE:
|
||||||
|
return this.trace(arg2, params);
|
||||||
|
case LogLevel.ERROR:
|
||||||
|
return this.error(arg2, params);
|
||||||
|
case LogLevel.FATAL:
|
||||||
|
return this.fatal(arg2, params);
|
||||||
|
default:
|
||||||
|
return this.info(arg2, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
override async info(arg: string | Loggable, ...params: any[]): Promise<void> {
|
||||||
|
if (params.length) {
|
||||||
|
console.info(arg, ...params);
|
||||||
|
} else {
|
||||||
|
console.info(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override async trace(
|
||||||
|
arg: string | Loggable,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
...params: any[]
|
||||||
|
): Promise<void> {
|
||||||
|
if (params.length) {
|
||||||
|
console.trace(arg, ...params);
|
||||||
|
} else {
|
||||||
|
console.trace(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
override async warn(arg: string | Loggable, ...params: any[]): Promise<void> {
|
||||||
|
if (params.length) {
|
||||||
|
console.warn(arg, ...params);
|
||||||
|
} else {
|
||||||
|
console.warn(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override async error(
|
||||||
|
arg: string | Loggable,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
...params: any[]
|
||||||
|
): Promise<void> {
|
||||||
|
if (params.length) {
|
||||||
|
console.error(arg, ...params);
|
||||||
|
} else {
|
||||||
|
console.error(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override async fatal(
|
||||||
|
arg: string | Loggable,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
...params: any[]
|
||||||
|
): Promise<void> {
|
||||||
|
return this.error(arg, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class SilentArduinoDaemon extends ArduinoDaemonImpl {
|
||||||
|
protected override onData(): void {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestBoardDiscovery extends BoardDiscovery {
|
||||||
|
mutableAvailablePorts: AvailablePorts = {};
|
||||||
|
|
||||||
|
override async start(): Promise<void> {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
override async stop(): Promise<void> {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
override get availablePorts(): AvailablePorts {
|
||||||
|
return this.mutableAvailablePorts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestNotificationServiceServer implements NotificationServiceServer {
|
||||||
|
readonly events: string[] = [];
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
|
||||||
|
disposeClient(client: NotificationServiceClient): void {
|
||||||
|
this.events.push('disposeClient:');
|
||||||
|
}
|
||||||
|
notifyDidReinitialize(): void {
|
||||||
|
this.events.push('notifyDidReinitialize:');
|
||||||
|
}
|
||||||
|
notifyIndexUpdateWillStart(params: IndexUpdateParams): void {
|
||||||
|
this.events.push(`notifyIndexUpdateWillStart:${JSON.stringify(params)}`);
|
||||||
|
}
|
||||||
|
notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void {
|
||||||
|
this.events.push(
|
||||||
|
`notifyIndexUpdateDidProgress:${JSON.stringify(progressMessage)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
notifyIndexUpdateDidComplete(params: IndexUpdateDidCompleteParams): void {
|
||||||
|
this.events.push(`notifyIndexUpdateDidComplete:${JSON.stringify(params)}`);
|
||||||
|
}
|
||||||
|
notifyIndexUpdateDidFail(params: IndexUpdateDidFailParams): void {
|
||||||
|
this.events.push(`notifyIndexUpdateDidFail:${JSON.stringify(params)}`);
|
||||||
|
}
|
||||||
|
notifyDaemonDidStart(port: string): void {
|
||||||
|
this.events.push(`notifyDaemonDidStart:${port}`);
|
||||||
|
}
|
||||||
|
notifyDaemonDidStop(): void {
|
||||||
|
this.events.push('notifyDaemonDidStop:');
|
||||||
|
}
|
||||||
|
notifyConfigDidChange(event: ConfigState): void {
|
||||||
|
this.events.push(`notifyConfigDidChange:${JSON.stringify(event)}`);
|
||||||
|
}
|
||||||
|
notifyPlatformDidInstall(event: { item: BoardsPackage }): void {
|
||||||
|
this.events.push(`notifyPlatformDidInstall:${JSON.stringify(event)}`);
|
||||||
|
}
|
||||||
|
notifyPlatformDidUninstall(event: { item: BoardsPackage }): void {
|
||||||
|
this.events.push(`notifyPlatformDidUninstall:${JSON.stringify(event)}`);
|
||||||
|
}
|
||||||
|
notifyLibraryDidInstall(event: {
|
||||||
|
item: LibraryPackage | 'zip-install';
|
||||||
|
}): void {
|
||||||
|
this.events.push(`notifyLibraryDidInstall:${JSON.stringify(event)}`);
|
||||||
|
}
|
||||||
|
notifyLibraryDidUninstall(event: { item: LibraryPackage }): void {
|
||||||
|
this.events.push(`notifyLibraryDidUninstall:${JSON.stringify(event)}`);
|
||||||
|
}
|
||||||
|
notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void {
|
||||||
|
this.events.push(`notifyAttachedBoardsDidChange:${JSON.stringify(event)}`);
|
||||||
|
}
|
||||||
|
notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void {
|
||||||
|
this.events.push(`notifyRecentSketchesDidChange:${JSON.stringify(event)}`);
|
||||||
|
}
|
||||||
|
dispose(): void {
|
||||||
|
this.events.push('dispose:');
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
|
||||||
|
setClient(client: NotificationServiceClient | undefined): void {
|
||||||
|
this.events.push('setClient:');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class TestResponseService implements ResponseService {
|
||||||
|
readonly outputMessages: OutputMessage[] = [];
|
||||||
|
readonly progressMessages: ProgressMessage[] = [];
|
||||||
|
|
||||||
|
appendToOutput(message: OutputMessage): void {
|
||||||
|
this.outputMessages.push(message);
|
||||||
|
}
|
||||||
|
reportProgress(message: ProgressMessage): void {
|
||||||
|
this.progressMessages.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createBaseContainer(
|
||||||
|
containerCustomizations?: (
|
||||||
|
bind: interfaces.Bind,
|
||||||
|
rebind: interfaces.Rebind
|
||||||
|
) => void
|
||||||
|
): Container {
|
||||||
|
const container = new Container({ defaultScope: 'Singleton' });
|
||||||
|
const module = new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||||
|
bind(CoreClientProvider).toSelf().inSingletonScope();
|
||||||
|
bind(CoreServiceImpl).toSelf().inSingletonScope();
|
||||||
|
bind(CoreService).toService(CoreServiceImpl);
|
||||||
|
bind(BoardsServiceImpl).toSelf().inSingletonScope();
|
||||||
|
bind(BoardsService).toService(BoardsServiceImpl);
|
||||||
|
bind(TestResponseService).toSelf().inSingletonScope();
|
||||||
|
bind(ResponseService).toService(TestResponseService);
|
||||||
|
bind(MonitorManager).toSelf().inSingletonScope();
|
||||||
|
bind(MonitorServiceFactory).toFactory(
|
||||||
|
({ container }) =>
|
||||||
|
(options: MonitorServiceFactoryOptions) => {
|
||||||
|
const child = container.createChild();
|
||||||
|
child
|
||||||
|
.bind<MonitorServiceFactoryOptions>(MonitorServiceFactoryOptions)
|
||||||
|
.toConstantValue({
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
child.bind(MonitorService).toSelf();
|
||||||
|
return child.get<MonitorService>(MonitorService);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
bind(EnvVariablesServer).toSelf().inSingletonScope();
|
||||||
|
bind(TheiaEnvVariablesServer).toService(EnvVariablesServer);
|
||||||
|
bind(SilentArduinoDaemon).toSelf().inSingletonScope();
|
||||||
|
bind(ArduinoDaemon).toService(SilentArduinoDaemon);
|
||||||
|
bind(ArduinoDaemonImpl).toService(SilentArduinoDaemon);
|
||||||
|
bind(ConsoleLogger).toSelf().inSingletonScope();
|
||||||
|
bind(ILogger).toService(ConsoleLogger);
|
||||||
|
bind(TestNotificationServiceServer).toSelf().inSingletonScope();
|
||||||
|
bind(NotificationServiceServer).toService(TestNotificationServiceServer);
|
||||||
|
bind(ConfigServiceImpl).toSelf().inSingletonScope();
|
||||||
|
bind(ConfigService).toService(ConfigServiceImpl);
|
||||||
|
bind(CommandService).toService(CommandRegistry);
|
||||||
|
bindContributionProvider(bind, CommandContribution);
|
||||||
|
bind(TestBoardDiscovery).toSelf().inSingletonScope();
|
||||||
|
bind(BoardDiscovery).toService(TestBoardDiscovery);
|
||||||
|
bind(IsTempSketch).toSelf().inSingletonScope();
|
||||||
|
bind(SketchesServiceImpl).toSelf().inSingletonScope();
|
||||||
|
bind(SketchesService).toService(SketchesServiceImpl);
|
||||||
|
if (containerCustomizations) {
|
||||||
|
containerCustomizations(bind, rebind);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
container.load(module);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startDaemon(
|
||||||
|
container: Container,
|
||||||
|
toDispose: Disposable[],
|
||||||
|
startCustomizations?: (
|
||||||
|
container: Container,
|
||||||
|
toDispose: Disposable[]
|
||||||
|
) => Promise<void>
|
||||||
|
): Promise<void> {
|
||||||
|
const daemon = container.get<ArduinoDaemonImpl>(ArduinoDaemonImpl);
|
||||||
|
const configService = container.get<ConfigServiceImpl>(ConfigServiceImpl);
|
||||||
|
toDispose.push(Disposable.create(() => daemon.stop()));
|
||||||
|
configService.onStart();
|
||||||
|
daemon.onStart();
|
||||||
|
await waitForEvent(daemon.onDaemonStarted, 10_000);
|
||||||
|
if (startCustomizations) {
|
||||||
|
await startCustomizations(container, toDispose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function configureBackendApplicationConfigProvider(): void {
|
||||||
|
try {
|
||||||
|
BackendApplicationConfigProvider.get();
|
||||||
|
} catch (err) {
|
||||||
|
if (
|
||||||
|
err instanceof Error &&
|
||||||
|
err.message.includes('BackendApplicationConfigProvider#set')
|
||||||
|
) {
|
||||||
|
BackendApplicationConfigProvider.set({
|
||||||
|
configDirName: '.testArduinoIDE',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user