Skip to content

Commit

Permalink
fix: clean and add tests
Browse files Browse the repository at this point in the history
Signed-off-by: lstocchi <lstocchi@redhat.com>
  • Loading branch information
lstocchi committed Jul 11, 2023
1 parent fb48a34 commit f8b8726
Show file tree
Hide file tree
Showing 18 changed files with 217 additions and 127 deletions.
1 change: 0 additions & 1 deletion packages/main/src/plugin/api/container-info.ts
Expand Up @@ -29,7 +29,6 @@ export interface ContainerInfo extends Dockerode.ContainerInfo {
status: string;
engineId: string;
};
icon?: string;
}

export interface SimpleContainerInfo extends Dockerode.ContainerInfo {
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/plugin/api/context-into.ts
Expand Up @@ -24,6 +24,6 @@ export interface IContext {

export interface ContextInfo {
readonly id: number;
readonly parent?: Context | null;
readonly parent: Context | null;
readonly extension: string | null;
}
2 changes: 2 additions & 0 deletions packages/main/src/plugin/authentication.spec.ts
Expand Up @@ -48,6 +48,7 @@ import type { IconRegistry } from './icon-registry.js';
import type { Directories } from './directories.js';
import type { CustomPickRegistry } from './custompick/custompick-registry.js';
import type { ViewRegistry } from './view-registry.js';
import type { ContextRegistry } from './context-registry.js';

function randomNumber(n = 5) {
return Math.round(Math.random() * 10 * n);
Expand Down Expand Up @@ -256,6 +257,7 @@ suite('Authentication', () => {
authentication,
vi.fn() as unknown as IconRegistry,
vi.fn() as unknown as Telemetry,
vi.fn() as unknown as ContextRegistry,
vi.fn() as unknown as ViewRegistry,
directories,
);
Expand Down
4 changes: 1 addition & 3 deletions packages/main/src/plugin/container-registry.spec.ts
Expand Up @@ -24,7 +24,6 @@ import type { Proxy } from '/@/plugin/proxy.js';
import { ImageRegistry } from '/@/plugin/image-registry.js';
import type { ApiSenderType } from '/@/plugin/api.js';
import type Dockerode from 'dockerode';
import { ViewRegistry } from './view-registry.js';

/* eslint-disable @typescript-eslint/no-empty-function */

Expand Down Expand Up @@ -125,8 +124,7 @@ beforeEach(() => {
} as unknown as Proxy;

const imageRegistry = new ImageRegistry({} as ApiSenderType, telemetry, certificates, proxy);
const viewRegistry = new ViewRegistry();
containerRegistry = new TestContainerProviderRegistry({} as ApiSenderType, imageRegistry, telemetry, viewRegistry);
containerRegistry = new TestContainerProviderRegistry({} as ApiSenderType, imageRegistry, telemetry);
});

test('tag should reject if no provider', async () => {
Expand Down
84 changes: 84 additions & 0 deletions packages/main/src/plugin/context-registry.spec.ts
@@ -0,0 +1,84 @@
/**********************************************************************
* Copyright (C) 2023 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

import { beforeAll, beforeEach, expect, expectTypeOf, test, vi } from 'vitest';
import { ContextRegistry } from './context-registry.js';
import type { ApiSenderType } from './api.js';

let contextRegistry: ContextRegistry;

beforeAll(() => {
contextRegistry = new ContextRegistry(vi.fn() as unknown as ApiSenderType);
});

beforeEach(() => {
vi.clearAllMocks();
});

test('Should register extension context', async () => {
contextRegistry.registerContext('extension');
const contexts = contextRegistry.listContextInfos();
expectTypeOf(contexts).toBeArray();
expect(contexts.length).toBe(1);
});

test('Should not register new context for an already registered extension', async () => {
contextRegistry.registerContext('extension');
contextRegistry.registerContext('extension');
const contexts = contextRegistry.listContextInfos();
expect(contexts.length).toBe(1);
});

test('Should unregister extension context', async () => {
contextRegistry.registerContext('extension');
let contexts = contextRegistry.listContextInfos();
expectTypeOf(contexts).toBeArray();
expect(contexts.length).toBe(1);
contextRegistry.unregisterContext('extension');
contexts = contextRegistry.listContextInfos();
expectTypeOf(contexts).toBeArray();
expect(contexts.length).toBe(0);
});

test('Should return empty array if no extension has been registered', async () => {
const contexts = contextRegistry.listContextInfos();
expectTypeOf(contexts).toBeArray();
expect(contexts.length).toBe(0);
});

test('Should return array with contexts of registered extensions', async () => {
contextRegistry.registerContext('extension');
const contexts = contextRegistry.listContextInfos();
expectTypeOf(contexts).toBeArray();
expect(contexts.length).toBe(1);
expect(contexts[0].id).toEqual(0);
expect(contexts[0].parent).toBeNull();
expect(contexts[0].extension).toEqual('extension');
});

test('Should throw an error if trying to get context not registered extension', async () => {
expect(() => contextRegistry.getContextInfo('unknown')).toThrowError('no context found for extension unknown');
});

test('Should return the contextInfo of the registered extension', async () => {
contextRegistry.registerContext('extension');
const context = contextRegistry.getContextInfo('extension');
expect(context.id).toEqual(0);
expect(context.parent).toBeNull();
expect(context.extension).toEqual('extension');
});
26 changes: 13 additions & 13 deletions packages/main/src/plugin/context-registry.ts
Expand Up @@ -20,43 +20,43 @@ import type { ContextInfo } from './api/context-into.js';
import { Context } from './context/context.js';

export class ContextRegistry {
private contexts: Map<string, Context>;
private contexts: Context[];

constructor(private apiSender: ApiSenderType) {
this.contexts = new Map();
this.contexts = [];
}

registerContext(extension: string): void {
if (!this.contexts.has(extension)) {
const ctx = new Context(this.contexts.size, null, extension, this.apiSender);
this.contexts.set(extension, ctx);
if (!this.contexts.find(context => context.extension === extension)) {
const ctx = new Context(this.contexts.length, null, extension, this.apiSender);
this.contexts.push(ctx);
}
}

unregisterContext(extension: string): void {
const ctx = this.contexts.get(extension);
const ctx = this.contexts.find(context => context.extension === extension);
ctx?.dispose();
this.contexts.delete(extension);
this.contexts = this.contexts.filter(context => context.extension !== extension);
}

getContext(extension: string): Context | undefined {
return this.contexts.get(extension);
return this.contexts.find(context => context.extension === extension);
}

listContextInfos(): ContextInfo[] {
const contexts: ContextInfo[] = [];
Array.from(this.contexts.values()).forEach(value => {
contexts.push({
const contextsInfos: ContextInfo[] = [];
this.contexts.forEach(value => {
contextsInfos.push({
extension: value.extension,
id: value.id,
parent: value.parent,
});
});
return contexts;
return contextsInfos;
}

getContextInfo(extension: string): ContextInfo {
const context = this.contexts.get(extension);
const context = this.getContext(extension);
if (!context) {
throw new Error(`no context found for extension ${extension}`);
}
Expand Down
4 changes: 4 additions & 0 deletions packages/main/src/plugin/extension-loader.spec.ts
Expand Up @@ -46,6 +46,7 @@ import type { IconRegistry } from './icon-registry.js';
import type { Directories } from './directories.js';
import type { CustomPickRegistry } from './custompick/custompick-registry.js';
import type { ViewRegistry } from './view-registry.js';
import type { ContextRegistry } from './context-registry.js';

class TestExtensionLoader extends ExtensionLoader {
public async setupScanningDirectory(): Promise<void> {
Expand Down Expand Up @@ -112,6 +113,8 @@ const iconRegistry: IconRegistry = {} as unknown as IconRegistry;
const telemetryTrackMock = vi.fn();
const telemetry: Telemetry = { track: telemetryTrackMock } as unknown as Telemetry;

const contextRegistry: ContextRegistry = {} as unknown as ContextRegistry;

const viewRegistry: ViewRegistry = {} as unknown as ViewRegistry;

const directories = {
Expand Down Expand Up @@ -143,6 +146,7 @@ beforeAll(() => {
authenticationProviderRegistry,
iconRegistry,
telemetry,
contextRegistry,
viewRegistry,
directories,
);
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/plugin/index.ts
Expand Up @@ -368,7 +368,7 @@ export class PluginSystem {
await certificates.init();
const imageRegistry = new ImageRegistry(apiSender, telemetry, certificates, proxy);
const contextRegistry = new ContextRegistry(apiSender);
const viewRegistry = new ViewRegistry(apiSender);
const viewRegistry = new ViewRegistry();
const containerProviderRegistry = new ContainerProviderRegistry(apiSender, imageRegistry, telemetry);
const cancellationTokenRegistry = new CancellationTokenRegistry();
const providerRegistry = new ProviderRegistry(apiSender, containerProviderRegistry, telemetry);
Expand Down
76 changes: 0 additions & 76 deletions packages/main/src/plugin/types/disposable.ts
Expand Up @@ -53,79 +53,3 @@ export class Disposable implements IDisposable {
return new Disposable(func);
}
}

/**
* Manages a collection of disposable values.
*
* This is the preferred way to manage multiple disposables. A `DisposableStore` is safer to work with than an
* `IDisposable[]` as it considers edge cases, such as registering the same value multiple times or adding an item to a
* store that has already been disposed of.
*/
export class DisposableStore implements IDisposable {
static DISABLE_DISPOSED_WARNING = false;

private readonly _toDispose = new Set<IDisposable>();
private _isDisposed = false;

/**
* Dispose of all registered disposables and mark this object as disposed.
*
* Any future disposables added to this object will be disposed of on `add`.
*/
public dispose(): void {
if (this._isDisposed) {
return;
}

this._isDisposed = true;
this.clear();
}

/**
* @return `true` if this object has been disposed of.
*/
public get isDisposed(): boolean {
return this._isDisposed;
}

/**
* Dispose of all registered disposables but do not mark this object as disposed.
*/
public clear(): void {
if (this._toDispose.size === 0) {
return;
}

try {
this._toDispose.forEach(d => d.dispose());
} finally {
this._toDispose.clear();
}
}

/**
* Add a new {@link IDisposable disposable} to the collection.
*/
public add<T extends IDisposable>(o: T): T {
if (!o) {
return o;
}
if ((o as unknown as DisposableStore) === this) {
throw new Error('Cannot register a disposable on itself!');
}

if (this._isDisposed) {
if (!DisposableStore.DISABLE_DISPOSED_WARNING) {
console.warn(
new Error(
'Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!',
).stack,
);
}
} else {
this._toDispose.add(o);
}

return o;
}
}
8 changes: 3 additions & 5 deletions packages/main/src/plugin/view-registry.spec.ts
Expand Up @@ -29,34 +29,32 @@ beforeAll(() => {
views: {
'icons/containersList': [
{
id: 'kind_icon',
when: 'io.x-k8s.kind.cluster in containerLabelKeys',
icon: '${kind-icon}',
},
],
},
},
};
viewRegistry.registerViewContribution('extension', manifest.contributes.views);
viewRegistry.registerViews('extension', manifest.contributes.views);
});

beforeEach(() => {
vi.clearAllMocks();
});

test('Should return empty array for unknown view', async () => {
const views = viewRegistry.getViewContribution('unknownView');
const views = viewRegistry.fetchViewsContributions('unknown');
expect(views).toBeDefined();
expectTypeOf(views).toBeArray();
expect(views.length).toBe(0);
});

test('View context should have a single entry', async () => {
const views = viewRegistry.getViewContribution('icons/containersList');
const views = viewRegistry.fetchViewsContributions('extension');
expect(views).toBeDefined();
expectTypeOf(views).toBeArray();
expect(views.length).toBe(1);
expect(views[0].id).toBe('kind_icon');
expect(views[0].when).toBe('io.x-k8s.kind.cluster in containerLabelKeys');
expect(views[0].icon).toBe('${kind-icon}');
});
6 changes: 2 additions & 4 deletions packages/main/src/plugin/view-registry.ts
Expand Up @@ -15,13 +15,12 @@
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/
import type { ApiSenderType } from './api.js';
import type { ViewContribution, ViewInfoUI } from './api/view-info.js';

export class ViewRegistry {
private extViewContribution: Map<string, Map<string, ViewContribution[]>>;

constructor(private apiSender: ApiSenderType) {
constructor() {
this.extViewContribution = new Map();
}

Expand All @@ -44,7 +43,6 @@ export class ViewRegistry {
this.registerView(extensionId, viewId, viewContribution);
});
});
this.apiSender.send('extension-views-contribs-added', extensionId);
}

unregisterViews(extensionId: string): void {
Expand All @@ -67,7 +65,7 @@ export class ViewRegistry {
return listViewInfoUI;
}

fetchViewsContributions(extensionId: string) {
fetchViewsContributions(extensionId: string): ViewInfoUI[] {
const listViewInfoUI: ViewInfoUI[] = [];
const viewContributions = this.extViewContribution.get(extensionId);
if (!viewContributions) {
Expand Down

0 comments on commit f8b8726

Please sign in to comment.