/** * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ import { anyObject } from "jest-mock-extended"; import mockFs from "mock-fs"; 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 "../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"; import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; function getMockCatalogEntity(data: Partial & CatalogEntityKindData): CatalogEntity { return { getName: jest.fn(() => data.metadata?.name), getId: jest.fn(() => data.metadata?.uid), getSource: jest.fn(() => data.metadata?.source ?? "unknown"), isEnabled: jest.fn(() => data.status?.enabled ?? true), onContextMenuOpen: jest.fn(), onSettingsOpen: jest.fn(), metadata: {}, spec: {}, status: {}, ...data, } as CatalogEntity; } describe("HotbarStore", () => { let di: DiContainer; let hotbarStore: HotbarStore; let testCluster: CatalogEntity; let minikubeCluster: CatalogEntity; let awsCluster: CatalogEntity; let loggerMock: jest.Mocked; beforeEach(async () => { di = getDiForUnitTesting({ doGeneralOverrides: true }); (di as any).unoverride(hotbarStoreInjectable); 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); loggerMock = { warn: jest.fn(), debug: jest.fn(), error: jest.fn(), info: jest.fn(), silly: jest.fn(), }; di.override(loggerInjectable, () => loggerMock); di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); 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.permitSideEffects(hotbarStoreInjectable); }); afterEach(() => { mockFs.restore(); }); describe("given no previous data in store, running all migrations", () => { beforeEach(() => { mockFs(); hotbarStore = di.inject(hotbarStoreInjectable); hotbarStore.load(); }); describe("load", () => { it("loads one hotbar by default", () => { expect(hotbarStore.hotbars.length).toEqual(1); }); }); describe("add", () => { it("adds a hotbar", () => { hotbarStore.add({ name: "hottest" }); expect(hotbarStore.hotbars.length).toEqual(2); }); }); describe("hotbar items", () => { it("initially creates 12 empty cells", () => { expect(hotbarStore.getActive().items.length).toEqual(12); }); it("initially adds catalog entity as first item", () => { expect(hotbarStore.getActive().items[0]?.entity.name).toEqual("Catalog"); }); it("adds items", () => { hotbarStore.addToHotbar(testCluster); const items = hotbarStore.getActive().items.filter(Boolean); expect(items.length).toEqual(2); }); it("removes items", () => { hotbarStore.addToHotbar(testCluster); hotbarStore.removeFromHotbar("some-test-id"); hotbarStore.removeFromHotbar("catalog-entity"); const items = hotbarStore.getActive().items.filter(Boolean); expect(items).toStrictEqual([]); }); it("does nothing if removing with invalid uid", () => { hotbarStore.addToHotbar(testCluster); hotbarStore.removeFromHotbar("invalid uid"); const items = hotbarStore.getActive().items.filter(Boolean); expect(items.length).toEqual(2); }); it("moves item to empty cell", () => { hotbarStore.addToHotbar(testCluster); hotbarStore.addToHotbar(minikubeCluster); hotbarStore.addToHotbar(awsCluster); expect(hotbarStore.getActive().items[6]).toBeNull(); hotbarStore.restackItems(1, 5); expect(hotbarStore.getActive().items[5]).toBeTruthy(); expect(hotbarStore.getActive().items[5]?.entity.uid).toEqual("some-test-id"); }); it("moves items down", () => { hotbarStore.addToHotbar(testCluster); hotbarStore.addToHotbar(minikubeCluster); hotbarStore.addToHotbar(awsCluster); // aws -> catalog hotbarStore.restackItems(3, 0); const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null); expect(items.slice(0, 4)).toEqual(["some-aws-id", "catalog-entity", "some-test-id", "some-minikube-id"]); }); it("moves items up", () => { hotbarStore.addToHotbar(testCluster); hotbarStore.addToHotbar(minikubeCluster); hotbarStore.addToHotbar(awsCluster); // test -> aws hotbarStore.restackItems(1, 3); const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null); 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"); hotbarStore.addToHotbar(testCluster, -1); expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject()); hotbarStore.addToHotbar(testCluster, 12); expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject()); hotbarStore.addToHotbar(testCluster, 13); expect(loggerMock.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", () => { expect(() => hotbarStore.addToHotbar({} as any)).toThrowError(TypeError); expect(() => hotbarStore.addToHotbar({ getId: () => true } as any)).toThrowError(TypeError); }); it("throws an error if getName is invalid or returns not a string", () => { expect(() => hotbarStore.addToHotbar({ getId: () => "" } as any)).toThrowError(TypeError); expect(() => hotbarStore.addToHotbar({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError); }); it("does nothing when item moved to same cell", () => { hotbarStore.addToHotbar(testCluster); hotbarStore.restackItems(1, 1); expect(hotbarStore.getActive().items[1]?.entity.uid).toEqual("some-test-id"); }); it("new items takes first empty cell", () => { hotbarStore.addToHotbar(testCluster); hotbarStore.addToHotbar(awsCluster); hotbarStore.restackItems(0, 3); hotbarStore.addToHotbar(minikubeCluster); expect(hotbarStore.getActive().items[0]?.entity.uid).toEqual("some-minikube-id"); }); it("throws if invalid arguments provided", () => { // Prevent writing to stderr during this render. const { error, warn } = console; console.error = jest.fn(); console.warn = jest.fn(); hotbarStore.addToHotbar(testCluster); expect(() => hotbarStore.restackItems(-5, 0)).toThrow(); expect(() => hotbarStore.restackItems(2, -1)).toThrow(); expect(() => hotbarStore.restackItems(14, 1)).toThrow(); expect(() => hotbarStore.restackItems(11, 112)).toThrow(); // Restore writing to stderr. console.error = error; console.warn = warn; }); it("checks if entity already pinned to hotbar", () => { hotbarStore.addToHotbar(testCluster); expect(hotbarStore.isAddedToActive(testCluster)).toBeTruthy(); expect(hotbarStore.isAddedToActive(awsCluster)).toBeFalsy(); }); }); }); describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => { beforeEach(() => { const configurationToBeMigrated = { "some-directory-for-user-data": { "lens-hotbar-store.json": JSON.stringify({ __internal__: { migrations: { version: "5.0.0-beta.3", }, }, hotbars: [ { id: "3caac17f-aec2-4723-9694-ad204465d935", name: "myhotbar", items: [ { entity: { uid: "some-aws-id", }, }, { entity: { uid: "55b42c3c7ba3b04193416cda405269a5", }, }, { entity: { uid: "176fd331968660832f62283219d7eb6e", }, }, { entity: { uid: "61c4fb45528840ebad1badc25da41d14", name: "user1-context", source: "local", }, }, { entity: { uid: "27d6f99fe9e7548a6e306760bfe19969", name: "foo2", source: "local", }, }, null, { entity: { uid: "c0b20040646849bb4dcf773e43a0bf27", name: "multinode-demo", source: "local", }, }, null, null, null, null, null, ], }, ], }), }, }; mockFs(configurationToBeMigrated); di.override(appVersionInjectable, () => "5.0.0-beta.10"); hotbarStore = di.inject(hotbarStoreInjectable); hotbarStore.load(); }); it("allows to retrieve a hotbar", () => { const hotbar = hotbarStore.findById("3caac17f-aec2-4723-9694-ad204465d935"); expect(hotbar?.id).toBe("3caac17f-aec2-4723-9694-ad204465d935"); }); it("clears cells without entity", () => { const items = hotbarStore.hotbars[0].items; expect(items[2]).toBeNull(); }); it("adds extra data to cells with according entity", () => { const items = hotbarStore.hotbars[0].items; expect(items[0]).toEqual({ entity: { name: "my-aws-cluster", source: "local", uid: "some-aws-id", }, }); }); }); });