1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

fixed hotbar-store.test.ts

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-04-08 15:07:27 -04:00
parent 6d720db036
commit 9add1d15e4
47 changed files with 505 additions and 463 deletions

View File

@ -5,69 +5,21 @@
import { anyObject } from "jest-mock-extended";
import mockFs from "mock-fs";
import logger from "../../main/logger";
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import hotbarStoreInjectable from "../hotbar-store.injectable";
import { HotbarStore } from "../hotbar-store";
import hotbarStoreInjectable from "../hotbars/store.injectable";
import type { HotbarStore } from "../hotbars/store";
import catalogEntityRegistryInjectable from "../../main/catalog/entity-registry.injectable";
import { computed } from "mobx";
import hasCategoryForEntityInjectable from "../catalog/has-category-for-entity.injectable";
import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import loggerInjectable from "../logger.injectable";
import type { Logger } from "../logger";
jest.mock("../../main/catalog/catalog-entity-registry", () => ({
catalogEntityRegistry: {
items: [
getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
name: "mycluster",
source: "local",
labels: {},
},
}),
getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "55b42c3c7ba3b04193416cda405269a5",
name: "my_shiny_cluster",
source: "remote",
labels: {},
},
}),
getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "catalog-entity",
name: "Catalog",
source: "app",
labels: {},
},
}),
],
},
}));
console.log("I am here as reminder against mockfs (and to fix console logging)");
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
return {
@ -84,69 +36,81 @@ function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKi
} as CatalogEntity;
}
const testCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "test",
name: "test",
labels: {},
},
});
const minikubeCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "minikube",
name: "minikube",
labels: {},
},
});
const awsCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "aws",
name: "aws",
labels: {},
},
});
describe("HotbarStore", () => {
let di: DiContainer;
let hotbarStore: HotbarStore;
let testCluster: CatalogEntity;
let minikubeCluster: CatalogEntity;
let awsCluster: CatalogEntity;
let logger: jest.Mocked<Logger>;
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting({ doGeneralOverrides: true, overrideHotbarStore: false });
testCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "some-test-id",
name: "my-test-cluster",
source: "local",
labels: {},
},
});
minikubeCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "some-minikube-id",
name: "my-minikube-cluster",
source: "local",
labels: {},
},
});
awsCluster = getMockCatalogEntity({
apiVersion: "v1",
kind: "Cluster",
status: {
phase: "Running",
},
metadata: {
uid: "some-aws-id",
name: "my-aws-cluster",
source: "local",
labels: {},
},
});
di.override(hasCategoryForEntityInjectable, () => () => true);
logger = di.inject(loggerInjectable) as jest.Mocked<Logger>;
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
catalogEntityRegistry.addComputedSource("some-id", computed(() => [
testCluster,
minikubeCluster,
awsCluster,
catalogCatalogEntity,
]));
di.permitSideEffects(getConfigurationFileModelInjectable);
di.permitSideEffects(appVersionInjectable);
di.override(hotbarStoreInjectable, () => {
HotbarStore.resetInstance();
return HotbarStore.createInstance({
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
});
});
di.permitSideEffects(hotbarStoreInjectable);
});
afterEach(() => {
mockFs.restore();
});
describe("given no migrations", () => {
describe("given no previous data in store, running all migrations", () => {
beforeEach(async () => {
mockFs();
@ -186,7 +150,7 @@ describe("HotbarStore", () => {
it("removes items", () => {
hotbarStore.addToHotbar(testCluster);
hotbarStore.removeFromHotbar("test");
hotbarStore.removeFromHotbar("some-test-id");
hotbarStore.removeFromHotbar("catalog-entity");
const items = hotbarStore.getActive().items.filter(Boolean);
@ -211,7 +175,7 @@ describe("HotbarStore", () => {
hotbarStore.restackItems(1, 5);
expect(hotbarStore.getActive().items[5]).toBeTruthy();
expect(hotbarStore.getActive().items[5]?.entity.uid).toEqual("test");
expect(hotbarStore.getActive().items[5]?.entity.uid).toEqual("some-test-id");
});
it("moves items down", () => {
@ -224,7 +188,7 @@ describe("HotbarStore", () => {
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
expect(items.slice(0, 4)).toEqual(["aws", "catalog-entity", "test", "minikube"]);
expect(items.slice(0, 4)).toEqual(["some-aws-id", "catalog-entity", "some-test-id", "some-minikube-id"]);
});
it("moves items up", () => {
@ -237,28 +201,21 @@ describe("HotbarStore", () => {
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
expect(items.slice(0, 4)).toEqual(["catalog-entity", "minikube", "aws", "test"]);
expect(items.slice(0, 4)).toEqual(["catalog-entity", "some-minikube-id", "some-aws-id", "some-test-id"]);
});
it("logs an error if cellIndex is out of bounds", () => {
hotbarStore.add({ name: "hottest", id: "hottest" });
hotbarStore.setActiveHotbar("hottest");
const { error } = logger;
const mocked = jest.fn();
logger.error = mocked;
hotbarStore.addToHotbar(testCluster, -1);
expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
expect(logger.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
hotbarStore.addToHotbar(testCluster, 12);
expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
expect(logger.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
hotbarStore.addToHotbar(testCluster, 13);
expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
logger.error = error;
expect(logger.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
});
it("throws an error if getId is invalid or returns not a string", () => {
@ -275,7 +232,7 @@ describe("HotbarStore", () => {
hotbarStore.addToHotbar(testCluster);
hotbarStore.restackItems(1, 1);
expect(hotbarStore.getActive().items[1]?.entity.uid).toEqual("test");
expect(hotbarStore.getActive().items[1]?.entity.uid).toEqual("some-test-id");
});
it("new items takes first empty cell", () => {
@ -284,7 +241,7 @@ describe("HotbarStore", () => {
hotbarStore.restackItems(0, 3);
hotbarStore.addToHotbar(minikubeCluster);
expect(hotbarStore.getActive().items[0]?.entity.uid).toEqual("minikube");
expect(hotbarStore.getActive().items[0]?.entity.uid).toEqual("some-minikube-id");
});
it("throws if invalid arguments provided", () => {
@ -315,7 +272,7 @@ describe("HotbarStore", () => {
});
});
describe("given pre beta-5 configurations", () => {
describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => {
beforeEach(async () => {
const configurationToBeMigrated = {
"some-electron-app-path-for-user-data": {
@ -332,7 +289,7 @@ describe("HotbarStore", () => {
items: [
{
entity: {
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
uid: "some-aws-id",
},
},
{
@ -381,13 +338,15 @@ describe("HotbarStore", () => {
mockFs(configurationToBeMigrated);
di.override(appVersionInjectable, () => "5.0.0-beta.10");
await di.runSetups();
hotbarStore = di.inject(hotbarStoreInjectable);
});
it("allows to retrieve a hotbar", () => {
const hotbar = hotbarStore.getById("3caac17f-aec2-4723-9694-ad204465d935");
const hotbar = hotbarStore.findById("3caac17f-aec2-4723-9694-ad204465d935");
expect(hotbar?.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
});
@ -403,17 +362,9 @@ describe("HotbarStore", () => {
expect(items[0]).toEqual({
entity: {
name: "mycluster",
name: "my-aws-cluster",
source: "local",
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
},
});
expect(items[1]).toEqual({
entity: {
name: "my_shiny_cluster",
source: "remote",
uid: "55b42c3c7ba3b04193416cda405269a5",
uid: "some-aws-id",
},
});
});

View File

@ -10,7 +10,7 @@ import { ipcMain, ipcRenderer } from "electron";
import type { IEqualsComparer } from "mobx";
import { makeObservable, reaction, runInAction } from "mobx";
import type { Disposer } from "./utils";
import { getAppVersion, Singleton, toJS } from "./utils";
import { Singleton, toJS } from "./utils";
import logger from "../main/logger";
import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc";
import isEqual from "lodash/isEqual";
@ -59,8 +59,6 @@ export abstract class BaseStore<T> extends Singleton {
this.storeConfig = getConfigurationFileModel({
...this.params,
projectName: "lens",
projectVersion: getAppVersion(),
cwd: this.cwd(),
});

View File

@ -6,7 +6,6 @@
import { navigate } from "../../renderer/navigation";
import type { CatalogEntityMetadata, CatalogEntitySpec, CatalogEntityStatus } from "../catalog";
import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog";
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
interface GeneralEntitySpec extends CatalogEntitySpec {
path: string;
@ -54,5 +53,3 @@ export class GeneralCategory extends CatalogCategory {
},
};
}
catalogCategoryRegistry.add(new GeneralCategory());

View File

@ -158,5 +158,3 @@ class KubernetesClusterCategory extends CatalogCategory {
}
export const kubernetesClusterCategory = new KubernetesClusterCategory();
catalogCategoryRegistry.add(kubernetesClusterCategory);

View File

@ -69,5 +69,3 @@ export class WebLinkCategory extends CatalogCategory {
},
};
}
catalogCategoryRegistry.add(new WebLinkCategory());

View File

@ -3,96 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { action, computed, observable, makeObservable } from "mobx";
import { once } from "lodash";
import { iter, getOrInsertMap, strictSet } from "../utils";
import type { Disposer } from "../utils";
import type { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
import catalogCategoryRegistryInjectable from "./category-registry.injectable";
export type CategoryFilter = (category: CatalogCategory) => any;
export class CatalogCategoryRegistry {
protected categories = observable.set<CatalogCategory>();
protected groupKinds = new Map<string, Map<string, CatalogCategory>>();
protected filters = observable.set<CategoryFilter>([], {
deep: false,
});
constructor() {
makeObservable(this);
}
@action add(category: CatalogCategory): Disposer {
const byGroup = getOrInsertMap(this.groupKinds, category.spec.group);
this.categories.add(category);
strictSet(byGroup, category.spec.names.kind, category);
return () => {
this.categories.delete(category);
byGroup.delete(category.spec.names.kind);
};
}
@computed get items() {
return Array.from(this.categories);
}
@computed get filteredItems() {
return Array.from(
iter.reduce(
this.filters,
iter.filter,
this.items.values(),
),
);
}
getForGroupKind<T extends CatalogCategory>(group: string, kind: string): T | undefined {
return this.groupKinds.get(group)?.get(kind) as T;
}
getEntityForData(data: CatalogEntityData & CatalogEntityKindData) {
const category = this.getCategoryForEntity(data);
if (!category) {
return null;
}
const splitApiVersion = data.apiVersion.split("/");
const version = splitApiVersion[1];
const specVersion = category.spec.versions.find((v) => v.name === version);
if (!specVersion) {
return null;
}
return new specVersion.entityClass(data);
}
getCategoryForEntity<T extends CatalogCategory>(data: CatalogEntityData & CatalogEntityKindData): T | undefined {
const splitApiVersion = data.apiVersion.split("/");
const group = splitApiVersion[0];
return this.getForGroupKind(group, data.kind);
}
getByName(name: string) {
return this.items.find(category => category.metadata?.name == name);
}
/**
* Add a new filter to the set of category filters
* @param fn The function that should return a truthy value if that category should be displayed
* @returns A function to remove that filter
*/
addCatalogCategoryFilter(fn: CategoryFilter): Disposer {
this.filters.add(fn);
return once(() => void this.filters.delete(fn));
}
}
export const catalogCategoryRegistry = new CatalogCategoryRegistry();
/**
* @deprecated use `di.inject(catalogCategoryRegistryInjectable)` instead
*/
export const catalogCategoryRegistry = asLegacyGlobalForExtensionApi(catalogCategoryRegistryInjectable);

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { GeneralCategory, kubernetesClusterCategory, WebLinkCategory } from "../catalog-entities";
import { CatalogCategoryRegistry } from "./category-registry";
const catalogCategoryRegistryInjectable = getInjectable({
id: "catalog-category-registry",
instantiate: () => {
const registry = new CatalogCategoryRegistry();
// TODO: move to different place
registry.add(new GeneralCategory());
registry.add(kubernetesClusterCategory);
registry.add(new WebLinkCategory());
return registry;
},
});
export default catalogCategoryRegistryInjectable;

View File

@ -0,0 +1,103 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { action, computed, observable, makeObservable } from "mobx";
import { once } from "lodash";
import { iter, getOrInsertMap, strictSet } from "../utils";
import type { Disposer } from "../utils";
import type { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
export type CategoryFilter = (category: CatalogCategory) => any;
export class CatalogCategoryRegistry {
protected readonly categories = observable.set<CatalogCategory>();
protected readonly groupKinds = new Map<string, Map<string, CatalogCategory>>();
protected readonly filters = observable.set<CategoryFilter>([], {
deep: false,
});
constructor() {
makeObservable(this);
}
@action add(category: CatalogCategory): Disposer {
const byGroup = getOrInsertMap(this.groupKinds, category.spec.group);
this.categories.add(category);
strictSet(byGroup, category.spec.names.kind, category);
return () => {
this.categories.delete(category);
byGroup.delete(category.spec.names.kind);
};
}
@computed get items() {
return Array.from(this.categories);
}
@computed get filteredItems() {
return Array.from(
iter.reduce(
this.filters,
iter.filter,
this.items.values(),
),
);
}
getForGroupKind<T extends CatalogCategory>(group: string, kind: string): T | undefined {
return this.groupKinds.get(group)?.get(kind) as T;
}
getEntityForData(data: CatalogEntityData & CatalogEntityKindData) {
const category = this.getCategoryForEntity(data);
if (!category) {
return null;
}
const splitApiVersion = data.apiVersion.split("/");
const version = splitApiVersion[1];
const specVersion = category.spec.versions.find((v) => v.name === version);
if (!specVersion) {
return null;
}
return new specVersion.entityClass(data);
}
hasCategoryForEntity({ kind, apiVersion }: CatalogEntityData & CatalogEntityKindData): boolean {
const splitApiVersion = apiVersion.split("/");
const group = splitApiVersion[0];
return this.groupKinds.get(group)?.has(kind) ?? false;
}
getCategoryForEntity<T extends CatalogCategory>(data: CatalogEntityData & CatalogEntityKindData): T | undefined {
const splitApiVersion = data.apiVersion.split("/");
const group = splitApiVersion[0];
return this.getForGroupKind(group, data.kind);
}
getByName(name: string) {
return this.items.find(category => category.metadata?.name == name);
}
/**
* Add a new filter to the set of category filters
* @param fn The function that should return a truthy value if that category should be displayed
* @returns A function to remove that filter
*/
addCatalogCategoryFilter(fn: CategoryFilter): Disposer {
this.filters.add(fn);
return once(() => void this.filters.delete(fn));
}
}

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
import catalogCategoryRegistryInjectable from "./category-registry.injectable";
export type HasCategoryForEntity = (data: CatalogEntityData & CatalogEntityKindData) => boolean;
const hasCategoryForEntityInjectable = getInjectable({
id: "has-category-for-entity",
instantiate: (di): HasCategoryForEntity => {
const registry = di.inject(catalogCategoryRegistryInjectable);
return (data) => registry.hasCategoryForEntity(data);
},
});
export default hasCategoryForEntityInjectable;

View File

@ -4,4 +4,5 @@
*/
export * from "./catalog-category-registry";
export * from "./category-registry";
export * from "./catalog-entity";

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import hotbarStoreInjectable from "./store.injectable";
import type { CreateHotbarData, CreateHotbarOptions } from "./types";
export type AddHotbar = (data: CreateHotbarData, opts?: CreateHotbarOptions) => void;
const addHotbarInjectable = getInjectable({
id: "add-hotbar",
instantiate: (di): AddHotbar => {
const store = di.inject(hotbarStoreInjectable);
return (data, opts) => store.add(data, opts);
},
});
export default addHotbarInjectable;

View File

@ -3,8 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import catalogCatalogEntityInjectable from "./catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { HotbarStore } from "./hotbar-store";
import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { HotbarStore } from "./store";
import loggerInjectable from "../logger.injectable";
const hotbarStoreInjectable = getInjectable({
id: "hotbar-store",
@ -14,6 +15,7 @@ const hotbarStoreInjectable = getInjectable({
return HotbarStore.createInstance({
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
logger: di.inject(loggerInjectable),
});
},

View File

@ -3,23 +3,18 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { action, comparer, observable, makeObservable, computed, runInAction } from "mobx";
import { BaseStore } from "./base-store";
import migrations from "../migrations/hotbar-store";
import { toJS } from "./utils";
import type { CatalogEntity } from "./catalog";
import logger from "../main/logger";
import { broadcastMessage } from "./ipc";
import type {
Hotbar,
CreateHotbarData,
CreateHotbarOptions } from "./hotbar-types";
import {
defaultHotbarCells,
getEmptyHotbar,
} from "./hotbar-types";
import { hotbarTooManyItemsChannel } from "./ipc/hotbar";
import type { GeneralEntity } from "./catalog-entities";
import { action, comparer, observable, makeObservable, computed } from "mobx";
import { BaseStore } from "../base-store";
import migrations from "../../migrations/hotbar-store";
import { toJS } from "../utils";
import type { CatalogEntity } from "../catalog";
import { broadcastMessage } from "../ipc";
import type { Hotbar, CreateHotbarData, CreateHotbarOptions } from "./types";
import { defaultHotbarCells, getEmptyHotbar } from "./types";
import { hotbarTooManyItemsChannel } from "../ipc/hotbar";
import type { GeneralEntity } from "../catalog-entities";
import type { Logger } from "../logger";
import assert from "assert";
export interface HotbarStoreModel {
hotbars: Hotbar[];
@ -27,7 +22,8 @@ export interface HotbarStoreModel {
}
interface Dependencies {
catalogCatalogEntity: GeneralEntity;
readonly catalogCatalogEntity: GeneralEntity;
readonly logger: Logger;
}
export class HotbarStore extends BaseStore<HotbarStoreModel> {
@ -35,7 +31,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
@observable hotbars: Hotbar[] = [];
@observable private _activeHotbarId!: string;
constructor(private dependencies: Dependencies) {
constructor(private readonly dependencies: Dependencies) {
super({
configName: "lens-hotbar-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
@ -62,7 +58,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
this._activeHotbarId = this.hotbars[hotbar].id;
}
} else if (typeof hotbar === "string") {
if (this.getById(hotbar)) {
if (this.findById(hotbar)) {
this._activeHotbarId = hotbar;
}
} else {
@ -121,47 +117,34 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
}
getActive(): Hotbar {
const hotbar = this.getById(this.activeHotbarId);
const hotbar = this.findById(this.activeHotbarId);
if (hotbar) {
return hotbar;
}
assert(hotbar, "There MUST always be an active hotbar");
runInAction(() => {
if (this.hotbars.length === 0) {
this.hotbars.push(getEmptyHotbar("Default"));
}
this._activeHotbarId = this.hotbars[0].id;
});
return this.hotbars[0];
return hotbar;
}
getByName(name: string) {
findByName(name: string) {
return this.hotbars.find((hotbar) => hotbar.name === name);
}
getById(id: string) {
findById(id: string) {
return this.hotbars.find((hotbar) => hotbar.id === id);
}
add = action(
(
data: CreateHotbarData,
{ setActive = false }: CreateHotbarOptions = {},
) => {
const hotbar = getEmptyHotbar(data.name, data.id);
@action
add(data: CreateHotbarData, { setActive = false }: CreateHotbarOptions = {}) {
const hotbar = getEmptyHotbar(data.name, data.id);
this.hotbars.push(hotbar);
this.hotbars.push(hotbar);
if (setActive) {
this._activeHotbarId = hotbar.id;
}
},
);
if (setActive) {
this._activeHotbarId = hotbar.id;
}
}
setHotbarName = action((id: string, name: string): void => {
@action
setHotbarName(id: string, name: string): void {
const index = this.hotbars.findIndex((hotbar) => hotbar.id === id);
if (index < 0) {
@ -172,19 +155,18 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
}
this.hotbars[index].name = name;
});
}
remove = action((hotbar: Hotbar) => {
if (this.hotbars.length <= 1) {
throw new Error("Cannot remove the last hotbar");
}
@action
remove(hotbar: Hotbar) {
assert(this.hotbars.length >= 2, "Cannot remove the last hotbar");
this.hotbars = this.hotbars.filter((h) => h !== hotbar);
if (this.activeHotbarId === hotbar.id) {
this.setActiveHotbar(0);
}
});
}
@action
addToHotbar(item: CatalogEntity, cellIndex?: number) {
@ -223,7 +205,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
} else if (0 <= cellIndex && cellIndex < hotbar.items.length) {
hotbar.items[cellIndex] = newItem;
} else {
logger.error(
this.dependencies.logger.error(
`[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range`,
{ entityId: uid, hotbarId: hotbar.id, cellIndex },
);
@ -260,8 +242,9 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
findClosestEmptyIndex(from: number, direction = 1) {
let index = from;
const hotbar = this.getActive();
while (this.getActive().items[index] != null) {
while (hotbar.items[index] != null) {
index += direction;
}
@ -328,11 +311,9 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
return false;
}
return (
this.getActive().items.findIndex(
(item) => item?.entity.uid === entity.getId(),
) >= 0
);
const indexInActiveHotbar = this.getActive().items.findIndex(item => item?.entity.uid === entity.getId());
return indexInActiveHotbar >= 0;
}
getDisplayLabel(hotbar: Hotbar): string {

View File

@ -4,8 +4,8 @@
*/
import * as uuid from "uuid";
import type { Tuple } from "./utils";
import { tuple } from "./utils";
import type { Tuple } from "../utils";
import { tuple } from "../utils";
export interface HotbarItem {
entity: {

View File

@ -4,6 +4,7 @@
*/
import type { DiContainer } from "@ogre-tools/injectable";
import assert from "assert";
import { iter } from "../../common/utils";
const legacyGlobalDis = new Map<Environments, DiContainer>();
@ -20,13 +21,17 @@ export const setLegacyGlobalDiForExtensionApi = (
};
export const getLegacyGlobalDiForExtensionApi = () => {
const globalDis = [...legacyGlobalDis.values()];
if (globalDis.length > 1) {
if (legacyGlobalDis.size > 1) {
throw new Error("Tried to get DI container using legacy globals where there is multiple containers available.");
}
return globalDis[0];
const di = iter.first(legacyGlobalDis.values());
if (!di) {
throw new Error("Tried to get DI container using legacy globals where there is no containers available.");
}
return di;
};
export function getEnvironmentSpecificLegacyGlobalDiForExtensionApi(environment: Environments) {

View File

@ -3,10 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/**
* @jest-environment jsdom
*/
import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars";
describe("clearKubeconfigEnvVars tests", () => {

View File

@ -227,7 +227,6 @@ export const computeDiff = ({ directoryForKubeConfigs, createCluster }: Dependen
}
}
} catch (error) {
console.log(error);
logger.warn(`${logPrefix} Failed to compute diff: ${error}`, { filePath });
source.clear(); // clear source if we have failed so as to not show outdated information
}

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import catalogEntityRegistryInjectable from "../catalog/catalog-entity-registry.injectable";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
import { generalCatalogEntityInjectionToken } from "../../common/catalog-entities/general-catalog-entities/general-catalog-entity-injection-token";
import { computed } from "mobx";

View File

@ -3,50 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { action, computed, type IComputedValue, type IObservableArray, makeObservable, observable } from "mobx";
import type { CatalogCategoryRegistry, CatalogEntity } from "../../common/catalog";
import { catalogCategoryRegistry } from "../../common/catalog";
import { iter } from "../../common/utils";
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
import catalogEntityRegistryInjectable from "./entity-registry.injectable";
export class CatalogEntityRegistry {
protected sources = observable.map<string, IComputedValue<CatalogEntity[]>>();
constructor(private categoryRegistry: CatalogCategoryRegistry) {
makeObservable(this);
}
@action addObservableSource(id: string, source: IObservableArray<CatalogEntity>) {
this.sources.set(id, computed(() => source));
}
@action addComputedSource(id: string, source: IComputedValue<CatalogEntity[]>) {
this.sources.set(id, source);
}
@action removeSource(id: string) {
this.sources.delete(id);
}
@computed get items(): CatalogEntity[] {
return Array.from(
iter.filter(
iter.flatMap(this.sources.values(), source => source.get()),
entity => this.categoryRegistry.getCategoryForEntity(entity),
),
);
}
findById(id: string): CatalogEntity | undefined {
return this.items.find(entity => entity.getId() === id);
}
filterItemsForApiKind(apiVersion: string, kind: string): CatalogEntity[] {
return this.items.filter((item) => item.apiVersion === apiVersion && item.kind === kind);
}
filterItemsByPredicate<E extends CatalogEntity>(filter: (item: CatalogEntity) => item is E): E[] {
return this.items.filter(filter);
}
}
export const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
/**
* @deprecated use `di.inject(catalogEntityRegistryInjectable)` instead
*/
export const catalogEntityRegistry = asLegacyGlobalForExtensionApi(catalogEntityRegistryInjectable);

View File

@ -3,12 +3,14 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { catalogEntityRegistry } from "./catalog-entity-registry";
import hasCategoryForEntityInjectable from "../../common/catalog/has-category-for-entity.injectable";
import { CatalogEntityRegistry } from "./entity-registry";
const catalogEntityRegistryInjectable = getInjectable({
id: "catalog-entity-registry",
instantiate: () => catalogEntityRegistry,
causesSideEffects: true,
instantiate: (di) => new CatalogEntityRegistry({
hasCategoryForEntity: di.inject(hasCategoryForEntityInjectable),
}),
});
export default catalogEntityRegistryInjectable;

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { action, computed, type IComputedValue, type IObservableArray, makeObservable, observable } from "mobx";
import type { CatalogEntity } from "../../common/catalog";
import type { HasCategoryForEntity } from "../../common/catalog/has-category-for-entity.injectable";
import { iter } from "../../common/utils";
interface Dependencies {
readonly hasCategoryForEntity: HasCategoryForEntity;
}
export class CatalogEntityRegistry {
protected sources = observable.map<string, IComputedValue<CatalogEntity[]>>();
constructor(protected readonly dependencies: Dependencies) {
makeObservable(this);
}
@action addObservableSource(id: string, source: IObservableArray<CatalogEntity>) {
this.sources.set(id, computed(() => source));
}
@action addComputedSource(id: string, source: IComputedValue<CatalogEntity[]>) {
this.sources.set(id, source);
}
@action removeSource(id: string) {
this.sources.delete(id);
}
@computed get items(): CatalogEntity[] {
return Array.from(
iter.filter(
iter.flatMap(this.sources.values(), source => source.get()),
entity => this.dependencies.hasCategoryForEntity(entity),
),
);
}
findById(id: string): CatalogEntity | undefined {
return this.items.find(entity => entity.getId() === id);
}
filterItemsForApiKind(apiVersion: string, kind: string): CatalogEntity[] {
return this.items.filter((item) => item.apiVersion === apiVersion && item.kind === kind);
}
filterItemsByPredicate<E extends CatalogEntity>(filter: (item: CatalogEntity) => item is E): E[] {
return this.items.filter(filter);
}
}

View File

@ -4,7 +4,7 @@
*/
import glob from "glob";
import { kebabCase, memoize, noop } from "lodash/fp";
import { kebabCase, memoize } from "lodash/fp";
import { createContainer } from "@ogre-tools/injectable";
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
@ -26,7 +26,6 @@ import clusterStoreInjectable from "../common/cluster-store/cluster-store.inject
import type { ClusterStore } from "../common/cluster-store/cluster-store";
import type { Cluster } from "../common/cluster/cluster";
import userStoreInjectable from "../common/user-store/user-store.injectable";
import type { UserStore } from "../common/user-store";
import isMacInjectable from "../common/vars/is-mac.injectable";
import isWindowsInjectable from "../common/vars/is-windows.injectable";
import isLinuxInjectable from "../common/vars/is-linux.injectable";
@ -34,11 +33,29 @@ import getAbsolutePathInjectable from "../common/path/get-absolute-path.injectab
import { getAbsolutePathFake } from "../common/test-utils/get-absolute-path-fake";
import joinPathsInjectable from "../common/path/join-paths.injectable";
import { joinPathsFake } from "../common/test-utils/join-paths-fake";
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../common/hotbars/store.injectable";
export interface GetDiOptions {
doGeneralOverrides?: boolean;
overrideHotbarStore?: boolean;
overrideUserStore?: boolean;
overrideExtensionsStore?: boolean;
overrideClusterStore?: boolean;
overrideFileSystemProvisionerStore?: boolean;
}
export function getDiForUnitTesting(opts: GetDiOptions = {}) {
const {
doGeneralOverrides = false,
} = opts;
const {
overrideHotbarStore = doGeneralOverrides,
overrideUserStore = doGeneralOverrides,
overrideExtensionsStore = doGeneralOverrides,
overrideClusterStore = doGeneralOverrides,
overrideFileSystemProvisionerStore = doGeneralOverrides,
} = opts;
export const getDiForUnitTesting = (
{ doGeneralOverrides } = { doGeneralOverrides: false },
) => {
const di = createContainer();
setLegacyGlobalDiForExtensionApi(di, Environments.main);
@ -55,6 +72,26 @@ export const getDiForUnitTesting = (
di.preventSideEffects();
if (overrideHotbarStore) {
di.override(hotbarStoreInjectable, () => ({}));
}
if (overrideUserStore) {
di.override(userStoreInjectable, () => ({}));
}
if (overrideExtensionsStore) {
di.override(extensionsStoreInjectable, () => ({ isEnabled: (opts) => (void opts, false) }) as ExtensionsStore);
}
if (overrideClusterStore) {
di.override(clusterStoreInjectable, () => ({ getById: (id) => (void id, {}) as Cluster }) as ClusterStore);
}
if (overrideFileSystemProvisionerStore) {
di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore);
}
if (doGeneralOverrides) {
di.override(isMacInjectable, () => true);
di.override(isWindowsInjectable, () => false);
@ -63,17 +100,6 @@ export const getDiForUnitTesting = (
di.override(getAbsolutePathInjectable, () => getAbsolutePathFake);
di.override(joinPathsInjectable, () => joinPathsFake);
// eslint-disable-next-line unused-imports/no-unused-vars-ts
di.override(extensionsStoreInjectable, () => ({ isEnabled: ({ id, isBundled }) => false }) as ExtensionsStore);
di.override(hotbarStoreInjectable, () => ({}));
di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore);
// eslint-disable-next-line unused-imports/no-unused-vars-ts
di.override(clusterStoreInjectable, () => ({ getById: (id): Cluster => ({}) as Cluster }) as ClusterStore);
di.override(userStoreInjectable, () => ({}) as UserStore);
di.override(
getElectronAppPathInjectable,
() => (name: string) => `some-electron-app-path-for-${kebabCase(name)}`,
@ -104,16 +130,16 @@ export const getDiForUnitTesting = (
});
di.override(loggerInjectable, () => ({
warn: noop,
debug: noop,
error: (message: string, ...args: any) => console.error(message, ...args),
info: noop,
silly: noop,
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
info: jest.fn(),
silly: jest.fn(),
}));
}
return di;
};
}
const getInjectableFilePaths = memoize(() => [
...glob.sync("./**/*.injectable.{ts,tsx}", { cwd: __dirname }),

View File

@ -59,7 +59,7 @@ import assert from "assert";
import windowManagerInjectable from "./window-manager.injectable";
import navigateToPreferencesInjectable from "../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable";
import syncGeneralCatalogEntitiesInjectable from "./catalog-sources/sync-general-catalog-entities.injectable";
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../common/hotbars/store.injectable";
import applicationMenuItemsInjectable from "./menu/application-menu-items.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import { init } from "@sentry/electron/main";

View File

@ -5,7 +5,7 @@
// Cleans up a store that had the state related data stored
import type { MigrationDeclaration } from "../helpers";
import { getEmptyHotbar } from "../../common/hotbar-types";
import { getEmptyHotbar } from "../../common/hotbars/types";
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import catalogCatalogEntityInjectable from "../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";

View File

@ -4,7 +4,7 @@
*/
// Cleans up a store that had the state related data stored
import type { Hotbar } from "../../common/hotbar-types";
import type { Hotbar } from "../../common/hotbars/types";
import * as uuid from "uuid";
import type { MigrationDeclaration } from "../helpers";

View File

@ -8,8 +8,8 @@ import { isNull } from "lodash";
import path from "path";
import * as uuid from "uuid";
import type { ClusterStoreModel } from "../../common/cluster-store/cluster-store";
import type { Hotbar, HotbarItem } from "../../common/hotbar-types";
import { defaultHotbarCells, getEmptyHotbar } from "../../common/hotbar-types";
import type { Hotbar, HotbarItem } from "../../common/hotbars/types";
import { defaultHotbarCells, getEmptyHotbar } from "../../common/hotbars/types";
import type { MigrationDeclaration } from "../helpers";
import { migrationLog } from "../helpers";
import { generateNewIdFor } from "../utils";

View File

@ -3,8 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Hotbar } from "../../common/hotbar-types";
import { catalogEntityRegistry } from "../../main/catalog";
import type { Hotbar } from "../../common/hotbars/types";
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import catalogEntityRegistryInjectable from "../../main/catalog/entity-registry.injectable";
import type { MigrationDeclaration } from "../helpers";
export default {
@ -12,6 +13,8 @@ export default {
run(store) {
const rawHotbars = store.get("hotbars");
const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars : [];
const di = getLegacyGlobalDiForExtensionApi();
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
for (const hotbar of hotbars) {
for (let i = 0; i < hotbar.items.length; i += 1) {

View File

@ -40,7 +40,7 @@ import historyInjectable from "./navigation/history.injectable";
import themeStoreInjectable from "./theme-store.injectable";
import navigateToAddClusterInjectable from "../common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable";
import addSyncEntriesInjectable from "./initializers/add-sync-entries.injectable";
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../common/hotbars/store.injectable";
import { bindEvents } from "./navigation/events";
import assert from "assert";
import openDeleteClusterDialogInjectable from "./components/delete-cluster-dialog/open.injectable";

View File

@ -13,7 +13,7 @@ import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns";
import type { CategoryColumns, GetCategoryColumnsParams } from "../get-category-columns.injectable";
import getCategoryColumnsInjectable from "../get-category-columns.injectable";
import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
class TestCategory extends CatalogCategory {
apiVersion = "catalog.k8slens.dev/v1alpha1";

View File

@ -41,8 +41,8 @@ import catalogRouteParametersInjectable from "./catalog-route-parameters.injecta
import { browseCatalogTab } from "./catalog-browse-tab";
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import type { HotbarStore } from "../../../common/hotbar-store";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { HotbarStore } from "../../../common/hotbars/store";
interface Dependencies {
catalogPreviousActiveTabStorage: { set: (value: string ) => void; get: () => string };

View File

@ -9,17 +9,17 @@ import { MenuItem } from "../menu";
import type { CatalogEntity } from "../../api/catalog-entity";
import { withInjectables } from "@ogre-tools/injectable-react";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import type { HotbarStore } from "../../../common/hotbar-store";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { HotbarStore } from "../../../common/hotbars/store";
interface Dependencies {
hotbarStore: HotbarStore;
}
interface HotbarToggleMenuItemProps {
entity: CatalogEntity;
addContent: ReactNode;
removeContent: ReactNode;
entity: CatalogEntity;
addContent: ReactNode;
removeContent: ReactNode;
}
function NonInjectedHotbarToggleMenuItem({

View File

@ -10,8 +10,8 @@ import { Avatar } from "../avatar";
import { Icon } from "../icon";
import React from "react";
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import type { HotbarStore } from "../../../common/hotbar-store";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { HotbarStore } from "../../../common/hotbars/store";
const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) => {
const isItemInHotbar = hotbarStore.isAddedToActive(entity);

View File

@ -16,7 +16,7 @@ import { createClusterInjectionToken } from "../../../../common/cluster/create-c
import createContextHandlerInjectable from "../../../../main/context-handler/create-context-handler.injectable";
import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor";
import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import type { OpenDeleteClusterDialog } from "../open.injectable";
import openDeleteClusterDialogInjectable from "../open.injectable";

View File

@ -17,9 +17,9 @@ import { Icon } from "../icon";
import { Select } from "../select";
import { Checkbox } from "../checkbox";
import { requestClearClusterAsDeleting, requestDeleteCluster, requestSetClusterAsDeleting } from "../../ipc";
import type { HotbarStore } from "../../../common/hotbar-store";
import type { HotbarStore } from "../../../common/hotbars/store";
import { withInjectables } from "@ogre-tools/injectable-react";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { DeleteClusterDialogState } from "./state.injectable";
import deleteClusterDialogStateInjectable from "./state.injectable";

View File

@ -11,7 +11,7 @@ import type { DiContainer } from "@ogre-tools/injectable";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor";
import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import { ThemeStore } from "../../../theme.store";
import { ConfirmDialog } from "../../confirm-dialog";
import { UserStore } from "../../../../common/user-store";
@ -19,7 +19,7 @@ import mockFs from "mock-fs";
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import getConfigurationFileModelInjectable from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable from "../../../../common/get-configuration-file-model/app-version/app-version.injectable";
import type { HotbarStore } from "../../../../common/hotbar-store";
import type { HotbarStore } from "../../../../common/hotbars/store";
const mockHotbars: Partial<Record<string, any>> = {
"1": {

View File

@ -7,11 +7,11 @@ import React from "react";
import { observer } from "mobx-react";
import type { InputValidator } from "../input";
import { Input } from "../input";
import type { CreateHotbarData, CreateHotbarOptions } from "../../../common/hotbar-types";
import type { CreateHotbarData, CreateHotbarOptions } from "../../../common/hotbars/types";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable";
import addHotbarInjectable from "../../../common/hotbars/add-hotbar.injectable";
interface Dependencies {
closeCommandOverlay: () => void;
@ -51,7 +51,7 @@ const NonInjectedHotbarAddCommand = observer(({ closeCommandOverlay, addHotbar,
export const HotbarAddCommand = withInjectables<Dependencies>(NonInjectedHotbarAddCommand, {
getProps: (di, props) => ({
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
addHotbar: di.inject(hotbarStoreInjectable).add,
addHotbar: di.inject(addHotbarInjectable),
uniqueHotbarName: di.inject(uniqueHotbarNameInjectable),
...props,
}),

View File

@ -16,12 +16,12 @@ import { DragDropContext, Draggable, Droppable, type DropResult } from "react-be
import { HotbarSelector } from "./hotbar-selector";
import { HotbarCell } from "./hotbar-cell";
import { HotbarIcon } from "./hotbar-icon";
import type { HotbarItem } from "../../../common/hotbar-types";
import { defaultHotbarCells } from "../../../common/hotbar-types";
import type { HotbarItem } from "../../../common/hotbars/types";
import { defaultHotbarCells } from "../../../common/hotbars/types";
import { action, makeObservable, observable } from "mobx";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { HotbarStore } from "../../../common/hotbar-store";
import type { HotbarStore } from "../../../common/hotbars/store";
export interface HotbarMenuProps {
className?: IClassName;
@ -118,7 +118,7 @@ class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuPro
<Draggable
draggableId={item.entity.uid}
key={item.entity.uid}
index={0}
index={0}
>
{(provided, snapshot) => {
const style = {

View File

@ -6,19 +6,15 @@
import React from "react";
import { observer } from "mobx-react";
import { Select } from "../select";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import { ConfirmDialog } from "../confirm-dialog";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import type { Hotbar } from "../../../common/hotbar-types";
import type { HotbarStore } from "../../../common/hotbars/store";
interface Dependencies {
closeCommandOverlay: () => void;
hotbarStore: {
hotbars: Hotbar[];
remove: (hotbar: Hotbar) => void;
getDisplayLabel: (hotbar: Hotbar) => string;
};
hotbarStore: HotbarStore;
}
const NonInjectedHotbarRemoveCommand = observer(({

View File

@ -6,22 +6,18 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
import { Select } from "../select";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { InputValidator } from "../input";
import { Input } from "../input";
import type { Hotbar } from "../../../common/hotbar-types";
import type { Hotbar } from "../../../common/hotbars/types";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable";
import type { HotbarStore } from "../../../common/hotbars/store";
interface Dependencies {
closeCommandOverlay: () => void;
hotbarStore: {
hotbars: Hotbar[];
getById: (id: string) => Hotbar | undefined;
setHotbarName: (id: string, name: string) => void;
getDisplayLabel: (hotbar: Hotbar) => string;
};
hotbarStore: HotbarStore;
uniqueHotbarName: InputValidator<false>;
}

View File

@ -7,22 +7,18 @@ import styles from "./hotbar-selector.module.scss";
import React, { useRef, useState } from "react";
import { Icon } from "../icon";
import { Badge } from "../badge";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import { HotbarSwitchCommand } from "./hotbar-switch-command";
import { Tooltip, TooltipPosition } from "../tooltip";
import { observer } from "mobx-react";
import type { Hotbar } from "../../../common/hotbar-types";
import type { Hotbar } from "../../../common/hotbars/types";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import { cssNames } from "../../utils";
import type { HotbarStore } from "../../../common/hotbars/store";
interface Dependencies {
hotbarStore: {
switchToPrevious: () => void;
switchToNext: () => void;
getActive: () => Hotbar;
getDisplayIndex: (hotbar: Hotbar) => string;
};
hotbarStore: HotbarStore;
openCommandOverlay: (component: React.ReactElement) => void;
}

View File

@ -6,15 +6,15 @@
import React from "react";
import { observer } from "mobx-react";
import { Select } from "../select";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { CommandOverlay } from "../command-palette";
import { HotbarAddCommand } from "./hotbar-add-command";
import { HotbarRemoveCommand } from "./hotbar-remove-command";
import { HotbarRenameCommand } from "./hotbar-rename-command";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import type { HotbarStore } from "../../../common/hotbar-store";
import type { Hotbar } from "../../../common/hotbar-types";
import type { HotbarStore } from "../../../common/hotbars/store";
import type { Hotbar } from "../../../common/hotbars/types";
const hotbarAddAction = Symbol("hotbar-add");
const hotbarRemoveAction = Symbol("hotbar-remove");

View File

@ -4,17 +4,21 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import { inputValidator } from "../input_validators";
const uniqueHotbarNameInjectable = getInjectable({
id: "unique-hotbar-name",
instantiate: di => inputValidator({
condition: ({ required }) => required,
message: () => "Hotbar with this name already exists",
validate: value => !di.inject(hotbarStoreInjectable).getByName(value),
}),
instantiate: di => {
const store = di.inject(hotbarStoreInjectable);
return inputValidator({
condition: ({ required }) => required,
message: () => "Hotbar with this name already exists",
validate: value => !store.findByName(value),
});
},
});
export default uniqueHotbarNameInjectable;

View File

@ -11,8 +11,8 @@ import { KubernetesCluster } from "../../../../common/catalog-entities";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor";
import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable";
import type { HotbarStore } from "../../../../common/hotbar-store";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import type { HotbarStore } from "../../../../common/hotbars/store";
const clusterEntity = new KubernetesCluster({
metadata: {

View File

@ -16,8 +16,8 @@ import { Menu, MenuItem } from "../menu";
import { ConfirmDialog } from "../confirm-dialog";
import { Tooltip } from "../tooltip";
import { withInjectables } from "@ogre-tools/injectable-react";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import type { HotbarStore } from "../../../common/hotbar-store";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { HotbarStore } from "../../../common/hotbars/store";
import { observer } from "mobx-react";
const contextMenu: CatalogEntityContextMenuContext = observable({

View File

@ -195,8 +195,6 @@ describe("<TreeView/> dataTree inside <ScrollSpy/>", () => {
expect(queryByTitle("Application")).toHaveAttribute("aria-expanded");
expect(queryByTitle("Kubernetes")).toHaveAttribute("aria-expanded");
});
// console.log(prettyDOM());
});
it("skips sections without headings", async () => {

View File

@ -4,7 +4,7 @@
*/
import glob from "glob";
import { isEqual, isPlainObject, memoize, noop } from "lodash/fp";
import { isEqual, isPlainObject, memoize } from "lodash/fp";
import { createContainer } from "@ogre-tools/injectable";
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import getValueFromRegisteredChannelInjectable from "./app-paths/get-value-from-registered-channel/get-value-from-registered-channel.injectable";
@ -32,7 +32,7 @@ import getAbsolutePathInjectable from "../common/path/get-absolute-path.injectab
import { getAbsolutePathFake } from "../common/test-utils/get-absolute-path-fake";
import joinPathsInjectable from "../common/path/join-paths.injectable";
import { joinPathsFake } from "../common/test-utils/join-paths-fake";
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
import hotbarStoreInjectable from "../common/hotbars/store.injectable";
import terminalSpawningPoolInjectable from "./components/dock/terminal/terminal-spawning-pool.injectable";
import hostedClusterIdInjectable from "../common/cluster-store/hosted-cluster-id.injectable";
import createStorageInjectable from "./utils/create-storage/create-storage.injectable";
@ -130,10 +130,11 @@ export const getDiForUnitTesting = (
di.override(focusWindowInjectable, () => () => {});
di.override(loggerInjectable, () => ({
warn: noop,
debug: noop,
error: (message: string, ...args: any) => console.error(message, ...args),
info: noop,
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
info: jest.fn(),
silly: jest.fn(),
}));
}

View File

@ -11,7 +11,7 @@ import { areArgsUpdateAvailableFromMain, UpdateAvailableChannel, onCorrect, ipcR
import { Notifications, notificationsStore } from "../components/notifications";
import { Button } from "../components/button";
import { isMac } from "../../common/vars";
import { defaultHotbarCells } from "../../common/hotbar-types";
import { defaultHotbarCells } from "../../common/hotbars/types";
import { type ListNamespaceForbiddenArgs, clusterListNamespaceForbiddenChannel, isListNamespaceForbiddenArgs } from "../../common/ipc/cluster";
import { hotbarTooManyItemsChannel } from "../../common/ipc/hotbar";
@ -31,7 +31,7 @@ function RenderYesButtons(props: { backchannel: string; notificationId: string }
<Button
light
label="Yes"
onClick={() => sendToBackchannel(props.backchannel, props.notificationId, { doUpdate: true, now: true })}
onClick={() => sendToBackchannel(props.backchannel, props.notificationId, { doUpdate: true, now: true })}
/>
);
}
@ -41,13 +41,13 @@ function RenderYesButtons(props: { backchannel: string; notificationId: string }
<Button
light
label="Yes, now"
onClick={() => sendToBackchannel(props.backchannel, props.notificationId, { doUpdate: true, now: true })}
onClick={() => sendToBackchannel(props.backchannel, props.notificationId, { doUpdate: true, now: true })}
/>
<Button
active
outlined
label="Yes, later"
onClick={() => sendToBackchannel(props.backchannel, props.notificationId, { doUpdate: true, now: false })}
onClick={() => sendToBackchannel(props.backchannel, props.notificationId, { doUpdate: true, now: false })}
/>
</>
);
@ -72,7 +72,7 @@ function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, update
active
outlined
label="No"
onClick={() => sendToBackchannel(backchannel, notificationId, { doUpdate: false })}
onClick={() => sendToBackchannel(backchannel, notificationId, { doUpdate: false })}
/>
</div>
</div>