diff --git a/src/common/k8s-api/api-manager.ts b/src/common/k8s-api/api-manager.ts index 2480506d37..b2befc4771 100644 --- a/src/common/k8s-api/api-manager.ts +++ b/src/common/k8s-api/api-manager.ts @@ -27,11 +27,11 @@ import type { KubeApi } from "./kube-api"; import type { KubeObject } from "./kube-object"; import { IKubeObjectRef, parseKubeApi, createKubeApiURL } from "./kube-api-parse"; -export interface IGettableStore { +export interface IApiManager { getStore>(api: string | KubeApi): TKubeObjectStore | undefined; } -export class ApiManager implements IGettableStore { +export class ApiManager implements IApiManager { private apis = observable.map>(); private stores = observable.map>(); diff --git a/src/extensions/registries/kube-object-menu-registry.ts b/src/extensions/registries/kube-object-menu-registry.ts index cfc198ee04..a6ce70322b 100644 --- a/src/extensions/registries/kube-object-menu-registry.ts +++ b/src/extensions/registries/kube-object-menu-registry.ts @@ -32,11 +32,11 @@ export interface KubeObjectMenuRegistration { components: KubeObjectMenuComponents; } -export interface IHasGettableItemsForKind { +export interface IKubeObjectMenuRegistry { getItemsForKind(kind: string, apiVersion: string): any; } -export class KubeObjectMenuRegistry extends BaseRegistry implements IHasGettableItemsForKind { +export class KubeObjectMenuRegistry extends BaseRegistry implements IKubeObjectMenuRegistry { getItemsForKind = (kind: string, apiVersion: string) => this.getItems().filter((item) => item.kind === kind && item.apiVersions.includes(apiVersion), diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 2ec97c792a..bb20a718db 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -37,12 +37,16 @@ import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../com import { disposer, storedKubeConfigFolder, toJS } from "../common/utils"; import type { Response } from "request"; +export interface ICluster { + name: string +} + /** * Cluster * * @beta */ -export class Cluster implements ClusterModel, ClusterState { +export class Cluster implements ClusterModel, ClusterState, ICluster { /** Unique id for a cluster */ public readonly id: ClusterId; private kubeCtl: Kubectl; diff --git a/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx b/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx index eae0e10332..1ae8d20552 100644 --- a/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx +++ b/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx @@ -24,7 +24,7 @@ import "./deployment-replicasets.scss"; import React from "react"; import { observer } from "mobx-react"; import type { ReplicaSet } from "../../../common/k8s-api/endpoints"; -import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object-menu"; +import type { KubeObjectMenuProps } from "../kube-object-menu"; import { Spinner } from "../spinner"; import { prevDefault, stopPropagation } from "../../utils"; import { DrawerTitle } from "../drawer"; @@ -32,8 +32,8 @@ import { Table, TableCell, TableHead, TableRow } from "../table"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { replicaSetStore } from "../+workloads-replicasets/replicasets.store"; import { showDetails } from "../kube-detail-params"; -import { InjectNaive } from "../kube-object-menu/inject-naive/inject-naive"; - +import { Inject } from "@ogre-tools/injectable-react"; +import KubeObjectMenuInjectable from "../kube-object-menu/kube-object-menu.injectable"; enum sortBy { name = "name", @@ -115,7 +115,6 @@ export class DeploymentReplicaSets extends React.Component { } export function ReplicaSetMenu(props: KubeObjectMenuProps) { - return ( - - ); + return ; } + diff --git a/src/renderer/components/kube-object-details/kube-object-details.tsx b/src/renderer/components/kube-object-details/kube-object-details.tsx index dacd0b61de..6c9cfa286d 100644 --- a/src/renderer/components/kube-object-details/kube-object-details.tsx +++ b/src/renderer/components/kube-object-details/kube-object-details.tsx @@ -29,14 +29,13 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object"; import { Spinner } from "../spinner"; import { apiManager } from "../../../common/k8s-api/api-manager"; import { crdStore } from "../+custom-resources/crd.store"; -import { KubeObjectMenu } from "../kube-object-menu"; import { KubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; import logger from "../../../main/logger"; import { CrdResourceDetails } from "../+custom-resources"; import { KubeObjectMeta } from "../kube-object-meta"; import { hideDetails, kubeDetailsUrlParam } from "../kube-detail-params"; -import { InjectNaive } from "../kube-object-menu/inject-naive/inject-naive"; - +import { Inject } from "@ogre-tools/injectable-react"; +import KubeObjectMenuInjectable from "../kube-object-menu/kube-object-menu.injectable"; export interface KubeObjectDetailsProps { className?: string; @@ -105,7 +104,7 @@ export class KubeObjectDetails extends React.Component { className="KubeObjectDetails flex column" open={isOpen} title="" - toolbar={} + toolbar={} onClose={hideDetails} > {isLoading && } @@ -145,7 +144,7 @@ export class KubeObjectDetails extends React.Component { className="KubeObjectDetails flex column" open={isOpen} title={title} - toolbar={} + toolbar={} onClose={hideDetails} > {isLoading && } diff --git a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx index f0d9591e62..9dba0cd219 100644 --- a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx +++ b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx @@ -28,7 +28,6 @@ import { cssNames, Disposer } from "../../utils"; import type { KubeObject } from "../../../common/k8s-api/kube-object"; import { ItemListLayout, ItemListLayoutProps } from "../item-object-list/item-list-layout"; import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import { KubeObjectMenu } from "../kube-object-menu"; import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter"; import { ResourceKindMap, ResourceNames } from "../../utils/rbac"; @@ -36,7 +35,8 @@ import { kubeSelectedUrlParam, toggleDetails } from "../kube-detail-params"; import { Icon } from "../icon"; import { TooltipPosition } from "../tooltip"; import type { ClusterContext } from "../../../common/k8s-api/cluster-context"; -import { InjectNaive } from "../kube-object-menu/inject-naive/inject-naive"; +import { Inject } from "@ogre-tools/injectable-react"; +import KubeObjectMenuInjectable from "../kube-object-menu/kube-object-menu.injectable"; export interface KubeObjectListLayoutProps extends ItemListLayoutProps { store: KubeObjectStore; @@ -141,7 +141,7 @@ export class KubeObjectListLayout extends React.Component< }), ...[customizeHeader].flat(), ]} - renderItemMenu={item => } + renderItemMenu={item => } /> ); } diff --git a/src/renderer/components/kube-object-menu/__snapshots__/kube-object-menu.test.tsx.snap b/src/renderer/components/kube-object-menu/__snapshots__/kube-object-menu.test.tsx.snap index 14844c43a9..8d173b079c 100644 --- a/src/renderer/components/kube-object-menu/__snapshots__/kube-object-menu.test.tsx.snap +++ b/src/renderer/components/kube-object-menu/__snapshots__/kube-object-menu.test.tsx.snap @@ -17,7 +17,7 @@ exports[`kube-object-menu given kube object renders 1`] = ` > from - Some cluster name + Some name ?

@@ -166,7 +166,7 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob > from - Some cluster name + Some name ?

@@ -267,7 +267,7 @@ exports[`kube-object-menu given kube object without namespace when removing kube > from - Some cluster name + Some name ?

diff --git a/src/renderer/components/kube-object-menu/dependencies/apiManager.injectable.ts b/src/renderer/components/kube-object-menu/dependencies/apiManager.injectable.ts new file mode 100644 index 0000000000..a8ab6a1bc6 --- /dev/null +++ b/src/renderer/components/kube-object-menu/dependencies/apiManager.injectable.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import { IApiManager, apiManager } from "../../../../common/k8s-api/api-manager"; +import type { IInjectable } from "@ogre-tools/injectable"; + +const apiManagerInjectable: IInjectable = { + getDependencies: () => ({}), + instantiate: () => apiManager, +}; + +export default apiManagerInjectable; diff --git a/src/renderer/components/kube-object-menu/inject-naive/inject-naive.tsx b/src/renderer/components/kube-object-menu/dependencies/cluster.injectable.ts similarity index 55% rename from src/renderer/components/kube-object-menu/inject-naive/inject-naive.tsx rename to src/renderer/components/kube-object-menu/dependencies/cluster.injectable.ts index 43d682ac14..b3d7467121 100644 --- a/src/renderer/components/kube-object-menu/inject-naive/inject-naive.tsx +++ b/src/renderer/components/kube-object-menu/dependencies/cluster.injectable.ts @@ -18,37 +18,16 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -import React from "react"; -import { editResourceTab } from "../../dock/edit-resource.store"; -import { hideDetails } from "../../kube-detail-params"; -import { apiManager } from "../../../../common/k8s-api/api-manager"; -import { KubeObjectMenuRegistry } from "../../../../extensions/registries"; import { getActiveClusterEntity } from "../../../api/catalog-entity-registry"; -import type { KubeObjectMenuDependencies } from "../kube-object-menu"; -import type { KubeObject } from "../../../../common/k8s-api/kube-object"; -interface Props { - Component: React.ElementType - [key: string]: any, -} +import type { IInjectable } from "@ogre-tools/injectable"; +import { lifecycleEnum } from "@ogre-tools/injectable"; +import type { ICluster } from "../../../../main/cluster"; -export const InjectNaive = ({ Component, ...props }: Props) => { - const kubeObjectMenuRegistry = KubeObjectMenuRegistry.getInstance(); - const cluster = getActiveClusterEntity(); - - const dependencies: KubeObjectMenuDependencies = { - clusterName: cluster.name, - editResourceTab, - hideDetails, - apiManager, - kubeObjectMenuRegistry, - }; - - return ( - - ); +const clusterInjectable: IInjectable = { + getDependencies: () => ({}), + instantiate: () => getActiveClusterEntity(), + lifecycle: lifecycleEnum.transient, }; + +export default clusterInjectable; diff --git a/src/renderer/components/kube-object-menu/dependencies/clusterName.injectable.ts b/src/renderer/components/kube-object-menu/dependencies/clusterName.injectable.ts new file mode 100644 index 0000000000..1e1065f309 --- /dev/null +++ b/src/renderer/components/kube-object-menu/dependencies/clusterName.injectable.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { lifecycleEnum } from "@ogre-tools/injectable"; +import type { ICluster } from "../../../../main/cluster"; +import type { IInjectable } from "@ogre-tools/injectable"; +import clusterInjectable from "./cluster.injectable"; + +interface IDependencies { + cluster: ICluster; +} + +const clusterNameInjectable: IInjectable< + string | undefined, + IDependencies +> = { + getDependencies: di => ({ + cluster: di.inject(clusterInjectable), + }), + + instantiate: ({ cluster }) => cluster?.name, + + lifecycle: lifecycleEnum.transient, +}; + +export default clusterNameInjectable; diff --git a/src/renderer/components/kube-object-menu/dependencies/editResourceTab.injectable.ts b/src/renderer/components/kube-object-menu/dependencies/editResourceTab.injectable.ts new file mode 100644 index 0000000000..aa3ef38095 --- /dev/null +++ b/src/renderer/components/kube-object-menu/dependencies/editResourceTab.injectable.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import { editResourceTab } from "../../dock/edit-resource.store"; +import type { IInjectable } from "@ogre-tools/injectable"; + +const editResourceTabInjectable: IInjectable = { + getDependencies: () => ({}), + instantiate: () => editResourceTab, +}; + +export default editResourceTabInjectable; diff --git a/src/renderer/components/kube-object-menu/dependencies/hideDetails.injectable.ts b/src/renderer/components/kube-object-menu/dependencies/hideDetails.injectable.ts new file mode 100644 index 0000000000..fc25d7c178 --- /dev/null +++ b/src/renderer/components/kube-object-menu/dependencies/hideDetails.injectable.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import { hideDetails } from "../../kube-detail-params"; +import type { IInjectable } from "@ogre-tools/injectable"; + +export const hideDetailsInjectable: IInjectable = { + getDependencies: () => ({}), + instantiate: () => hideDetails, +}; + +export default hideDetailsInjectable; diff --git a/src/renderer/components/kube-object-menu/dependencies/kubeObjectMenuRegistry.injectable.ts b/src/renderer/components/kube-object-menu/dependencies/kubeObjectMenuRegistry.injectable.ts new file mode 100644 index 0000000000..9ee3ae70a2 --- /dev/null +++ b/src/renderer/components/kube-object-menu/dependencies/kubeObjectMenuRegistry.injectable.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import { IKubeObjectMenuRegistry, KubeObjectMenuRegistry } from "../../../../extensions/registries"; +import type { IInjectable } from "@ogre-tools/injectable"; + +const kubeObjectMenuRegistryInjectable: IInjectable = { + getDependencies: () => ({}), + instantiate: () => KubeObjectMenuRegistry.getInstance(), +}; + +export default kubeObjectMenuRegistryInjectable; diff --git a/src/renderer/components/kube-object-menu/kube-object-menu.injectable.ts b/src/renderer/components/kube-object-menu/kube-object-menu.injectable.ts new file mode 100644 index 0000000000..330e988932 --- /dev/null +++ b/src/renderer/components/kube-object-menu/kube-object-menu.injectable.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { KubeObjectMenu, KubeObjectMenuDependencies } from "./kube-object-menu"; +import type { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { IComponentInjectable } from "@ogre-tools/injectable"; +import apiManagerInjectable from "./dependencies/apiManager.injectable"; +import clusterNameInjectable from "./dependencies/clusterName.injectable"; +import kubeObjectMenuRegistryInjectable from "./dependencies/kubeObjectMenuRegistry.injectable"; +import editResourceTabInjectable from "./dependencies/editResourceTab.injectable"; +import hideDetailsInjectable from "./dependencies/hideDetails.injectable"; + +const KubeObjectMenuInjectable: IComponentInjectable< + typeof KubeObjectMenu, + KubeObjectMenuDependencies +> = { + getDependencies: di => ({ + clusterName: di.inject(clusterNameInjectable), + apiManager: di.inject(apiManagerInjectable), + kubeObjectMenuRegistry: di.inject(kubeObjectMenuRegistryInjectable), + editResourceTab: di.inject(editResourceTabInjectable), + hideDetails: di.inject(hideDetailsInjectable), + }), + + instantiate: KubeObjectMenu, +}; + +export default KubeObjectMenuInjectable; diff --git a/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx b/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx index 050e5c7a49..2d9ba02fe5 100644 --- a/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx +++ b/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx @@ -20,28 +20,41 @@ */ import React from "react"; -import { render, screen } from "@testing-library/react"; +import { screen } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; -import { KubeObjectMenu, KubeObjectMenuDependencies } from "./kube-object-menu"; import { KubeObject } from "../../../common/k8s-api/kube-object"; import userEvent from "@testing-library/user-event"; -import type { KubeApi } from "../../../common/k8s-api/kube-api"; +import type { IConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable"; import type { KubeObjectMenuRegistration } from "../../../extensions/registries"; -import type { IGettableStore } from "../../../common/k8s-api/api-manager"; -import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; +import { KubeObjectMenuRegistry } from "../../../extensions/registries"; import { ConfirmDialog } from "../confirm-dialog"; import asyncFn from "@async-fn/jest"; -import { KubeObjectMenuRegistry } from "../../../extensions/registries"; +import { getDiForUnitTesting } from "../getDiForUnitTesting"; + +import { Inject } from "@ogre-tools/injectable-react"; +import clusterInjectable from "./dependencies/cluster.injectable"; +import apiManagerInjectable from "./dependencies/apiManager.injectable"; +import hideDetailsInjectable from "./dependencies/hideDetails.injectable"; +import editResourceTabInjectable from "./dependencies/editResourceTab.injectable"; +import { TabKind } from "../dock/dock.store"; +import KubeObjectMenuInjectable from "./kube-object-menu.injectable"; +import kubeObjectMenuRegistryInjectable from "./dependencies/kubeObjectMenuRegistry.injectable"; +import { renderFor, IDiRender } from "../test-utils/renderFor"; describe("kube-object-menu", () => { - let hideDetailsStub: () => void; - let editResourceTabStub: () => void; - let apiManagerStub: IGettableStore; - let kubeObjectMenuRegistry: KubeObjectMenuRegistry; let objectStub: KubeObject | null; - let dependencies: KubeObjectMenuDependencies; + let di: IConfigurableDependencyInjectionContainer; + let render: IDiRender; beforeEach(() => { + di = getDiForUnitTesting(); + + // TODO: Remove global shared state + KubeObjectMenuRegistry.resetInstance(); + KubeObjectMenuRegistry.createInstance(); + + render = renderFor(di); + // TODO: Remove illegal global overwrites for what should be a dependency somewhere. // TODO: Remove usage of experimental browser API. window.requestIdleCallback = (callback: IdleRequestCallback): number => { @@ -52,62 +65,66 @@ describe("kube-object-menu", () => { window.cancelIdleCallback = () => {}; - apiManagerStub = { - getStore: >( - // eslint-disable-next-line unused-imports/no-unused-vars-ts - api: string | KubeApi, - ): TKubeObjectStore | undefined => undefined, - }; + di.override(clusterInjectable, { + name: "Some name", + }); - // TODO: Remove global shared state - KubeObjectMenuRegistry.resetInstance(); - KubeObjectMenuRegistry.createInstance(); - kubeObjectMenuRegistry = KubeObjectMenuRegistry.getInstance(); + di.override(apiManagerInjectable, { + getStore: () => undefined, + }); - const MenuItemComponent: React.FC = () =>
  • Some menu item
  • ; + di.override(hideDetailsInjectable, () => {}); + + di.override(editResourceTabInjectable, () => ({ + id: "irrelevant", + kind: TabKind.TERMINAL, + pinned: false, + title: "irrelevant", + })); addDynamicMenuItem({ - kubeObjectMenuRegistry, - MenuItemComponent, + di, apiVersions: ["some-api-version"], kind: "some-kind", }); addDynamicMenuItem({ - kubeObjectMenuRegistry, - MenuItemComponent, + di, apiVersions: ["some-unrelated-api-version"], kind: "some-kind", }); addDynamicMenuItem({ - kubeObjectMenuRegistry, - MenuItemComponent, + di, apiVersions: ["some-api-version"], kind: "some-unrelated-kind", }); + }); - hideDetailsStub = () => {}; + it("given no cluster, does not crash", () => { + di.override(clusterInjectable, null); - editResourceTabStub = () => {}; + objectStub = null; - dependencies = { - clusterName: "Some cluster name", - apiManager: apiManagerStub, - kubeObjectMenuRegistry, - hideDetails: hideDetailsStub, - editResourceTab: editResourceTabStub, - }; + expect(() => { + render( + , + ); + }).not.toThrow(); }); it("given no kube object, renders", () => { objectStub = null; const { baseElement } = render( - , ); @@ -136,9 +153,9 @@ describe("kube-object-menu", () => {
    - @@ -195,7 +212,6 @@ describe("kube-object-menu", () => { describe("given kube object with namespace", () => { let baseElement: Element; - let getByTestId: (arg0: string) => any; beforeEach(async () => { objectStub = KubeObject.create({ @@ -209,13 +225,13 @@ describe("kube-object-menu", () => { }, }); - ({ baseElement, getByTestId } = render( + ({ baseElement } = render(
    - {}} /> @@ -224,7 +240,7 @@ describe("kube-object-menu", () => { }); it("when removing kube object, renders confirmation dialog with namespace", () => { - const menuItem = getByTestId("menu-action-remove"); + const menuItem = screen.getByTestId("menu-action-remove"); userEvent.click(menuItem); @@ -234,7 +250,6 @@ describe("kube-object-menu", () => { describe("given kube object without namespace", () => { let baseElement: Element; - let getByTestId: (arg0: string) => any; beforeEach(async () => { objectStub = KubeObject.create({ @@ -248,13 +263,13 @@ describe("kube-object-menu", () => { }, }); - ({ baseElement, getByTestId } = render( + ({ baseElement } = render(
    - {}} /> @@ -263,7 +278,7 @@ describe("kube-object-menu", () => { }); it("when removing kube object, renders confirmation dialog without namespace", () => { - const menuItem = getByTestId("menu-action-remove"); + const menuItem = screen.getByTestId("menu-action-remove"); userEvent.click(menuItem); @@ -273,21 +288,25 @@ describe("kube-object-menu", () => { }); const addDynamicMenuItem = ({ - kubeObjectMenuRegistry, - MenuItemComponent, + di, apiVersions, kind, }: { - kubeObjectMenuRegistry: KubeObjectMenuRegistry; - MenuItemComponent: React.ComponentType; + di: any; apiVersions: string[]; kind: string; }) => { + const MenuItemComponent: React.FC = () =>
  • Some menu item
  • ; + const dynamicMenuItemStub: KubeObjectMenuRegistration = { apiVersions, kind, components: { MenuItem: MenuItemComponent }, }; + const kubeObjectMenuRegistry: KubeObjectMenuRegistry = di.inject( + kubeObjectMenuRegistryInjectable, + ); + kubeObjectMenuRegistry.add(dynamicMenuItemStub); }; diff --git a/src/renderer/components/kube-object-menu/kube-object-menu.tsx b/src/renderer/components/kube-object-menu/kube-object-menu.tsx index 4bb7e9ab99..27591ee790 100644 --- a/src/renderer/components/kube-object-menu/kube-object-menu.tsx +++ b/src/renderer/components/kube-object-menu/kube-object-menu.tsx @@ -22,16 +22,16 @@ import React from "react"; import { boundMethod, cssNames } from "../../utils"; import type { KubeObject } from "../../../common/k8s-api/kube-object"; -import { MenuActions, MenuActionsProps } from "../menu/menu-actions"; +import { MenuActions, MenuActionsProps } from "../menu"; import identity from "lodash/identity"; -import type { IHasGettableItemsForKind } from "../../../extensions/registries"; -import type { IGettableStore } from "../../../common/k8s-api/api-manager"; +import type { IKubeObjectMenuRegistry } from "../../../extensions/registries"; +import type { IApiManager } from "../../../common/k8s-api/api-manager"; -export interface KubeObjectMenuDependencies{ - apiManager: IGettableStore; - kubeObjectMenuRegistry: IHasGettableItemsForKind; - clusterName: string, +export interface KubeObjectMenuDependencies { + apiManager: IApiManager; + kubeObjectMenuRegistry: IKubeObjectMenuRegistry; + clusterName: string; hideDetails: () => void; editResourceTab: (kubeObject: TKubeObject) => void; } @@ -40,22 +40,21 @@ export interface KubeObjectMenuProps extends MenuActionsProps { object: TKubeObject | null | undefined; editable?: boolean; removable?: boolean; - dependencies?: KubeObjectMenuDependencies } +export interface KubeObjectMenuPropsAndDependencies + extends KubeObjectMenuProps, + KubeObjectMenuDependencies {} + export class KubeObjectMenu< TKubeObject extends KubeObject, -> extends React.Component> { - get dependencies() { - return this.props.dependencies; - } - +> extends React.Component> { get store() { const { object } = this.props; if (!object) return null; - return this.dependencies.apiManager.getStore(object.selfLink); + return this.props.apiManager.getStore(object.selfLink); } get isEditable() { @@ -68,13 +67,13 @@ export class KubeObjectMenu< @boundMethod async update() { - this.dependencies.hideDetails(); - this.dependencies.editResourceTab(this.props.object); + this.props.hideDetails(); + this.props.editResourceTab(this.props.object); } @boundMethod async remove() { - this.dependencies.hideDetails(); + this.props.hideDetails(); const { object, removeAction } = this.props; // TODO: currently only branch for removeAction() is unit tested, and store.remove() is not. @@ -90,16 +89,13 @@ export class KubeObjectMenu< return null; } - const breadcrumbParts = [ - object.getNs(), - object.getName(), - ]; + const breadcrumbParts = [object.getNs(), object.getName()]; const breadcrumb = breadcrumbParts.filter(identity).join("/"); return (

    - Remove {object.kind} {breadcrumb} from {this.dependencies.clusterName}? + Remove {object.kind} {breadcrumb} from {this.props.clusterName}?

    ); } @@ -111,16 +107,22 @@ export class KubeObjectMenu< return []; } - return this.dependencies.kubeObjectMenuRegistry + return this.props.kubeObjectMenuRegistry .getItemsForKind(object.kind, object.apiVersion) - .map(({ components: { MenuItem }}: { components: { MenuItem: React.ReactType }}, index: number) => ( - - ), + .map( + ( + { + components: { MenuItem }, + }: { components: { MenuItem: React.ReactType }}, + index: number, + ) => ( + + ), ); } diff --git a/src/renderer/components/test-utils/renderFor.tsx b/src/renderer/components/test-utils/renderFor.tsx new file mode 100644 index 0000000000..690d3d8332 --- /dev/null +++ b/src/renderer/components/test-utils/renderFor.tsx @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import React from "react"; + +import { + render as testingLibraryRender, + RenderResult, +} from "@testing-library/react"; + +import type { IConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable"; + +import { DiContextProvider } from "@ogre-tools/injectable-react"; + +export type IDiRender = (ui: React.ReactElement) => RenderResult; + +type IDiRenderFor = ( + di: IConfigurableDependencyInjectionContainer, +) => IDiRender; + +export const renderFor: IDiRenderFor = di => ui => + testingLibraryRender( + {ui}, + ); +