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

Demonstrate how InjectNaive could be not naive by using injectables

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2021-11-10 14:04:23 +02:00
parent 78e1c6357b
commit 7938e9c4ef
17 changed files with 392 additions and 142 deletions

View File

@ -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<TKubeObjectStore extends KubeObjectStore<KubeObject>>(api: string | KubeApi<KubeObject>): TKubeObjectStore | undefined;
}
export class ApiManager implements IGettableStore {
export class ApiManager implements IApiManager {
private apis = observable.map<string, KubeApi<KubeObject>>();
private stores = observable.map<string, KubeObjectStore<KubeObject>>();

View File

@ -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<KubeObjectMenuRegistration> implements IHasGettableItemsForKind {
export class KubeObjectMenuRegistry extends BaseRegistry<KubeObjectMenuRegistration> implements IKubeObjectMenuRegistry {
getItemsForKind = (kind: string, apiVersion: string) =>
this.getItems().filter((item) =>
item.kind === kind && item.apiVersions.includes(apiVersion),

View File

@ -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;

View File

@ -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<Props> {
}
export function ReplicaSetMenu(props: KubeObjectMenuProps<ReplicaSet>) {
return (
<InjectNaive Component={KubeObjectMenu} {...props}/>
);
return <Inject injectableKey={KubeObjectMenuInjectable} {...props} />;
}

View File

@ -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<T extends KubeObject = KubeObject> {
className?: string;
@ -105,7 +104,7 @@ export class KubeObjectDetails extends React.Component {
className="KubeObjectDetails flex column"
open={isOpen}
title=""
toolbar={<InjectNaive Component={KubeObjectMenu} object={object} toolbar={true} />}
toolbar={<Inject injectableKey={KubeObjectMenuInjectable} object={object} toolbar={true} />}
onClose={hideDetails}
>
{isLoading && <Spinner center />}
@ -145,7 +144,7 @@ export class KubeObjectDetails extends React.Component {
className="KubeObjectDetails flex column"
open={isOpen}
title={title}
toolbar={<InjectNaive Component={KubeObjectMenu} object={object} toolbar={true}/>}
toolbar={<Inject injectableKey={KubeObjectMenuInjectable} object={object} toolbar={true}/>}
onClose={hideDetails}
>
{isLoading && <Spinner center/>}

View File

@ -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<K extends KubeObject> extends ItemListLayoutProps<K> {
store: KubeObjectStore<K>;
@ -141,7 +141,7 @@ export class KubeObjectListLayout<K extends KubeObject> extends React.Component<
}),
...[customizeHeader].flat(),
]}
renderItemMenu={item => <InjectNaive Component={KubeObjectMenu} object={item} />}
renderItemMenu={item => <Inject injectableKey={KubeObjectMenuInjectable} object={item} />}
/>
);
}

View File

@ -17,7 +17,7 @@ exports[`kube-object-menu given kube object renders 1`] = `
>
<i
class="Icon material interactive focusable"
id="tooltip_target_3"
id="tooltip_target_4"
tabindex="0"
>
<span
@ -65,7 +65,7 @@ exports[`kube-object-menu given kube object when removing kube object renders 1`
>
<i
class="Icon material interactive focusable"
id="tooltip_target_7"
id="tooltip_target_8"
tabindex="0"
>
<span
@ -116,7 +116,7 @@ exports[`kube-object-menu given kube object when removing kube object renders 1`
</b>
from
<b>
Some cluster name
Some name
</b>
?
</p>
@ -166,7 +166,7 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob
>
<i
class="Icon material interactive focusable"
id="tooltip_target_32"
id="tooltip_target_33"
tabindex="0"
>
<span
@ -217,7 +217,7 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob
</b>
from
<b>
Some cluster name
Some name
</b>
?
</p>
@ -267,7 +267,7 @@ exports[`kube-object-menu given kube object without namespace when removing kube
>
<i
class="Icon material interactive focusable"
id="tooltip_target_40"
id="tooltip_target_41"
tabindex="0"
>
<span
@ -318,7 +318,7 @@ exports[`kube-object-menu given kube object without namespace when removing kube
</b>
from
<b>
Some cluster name
Some name
</b>
?
</p>

View File

@ -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<IApiManager> = {
getDependencies: () => ({}),
instantiate: () => apiManager,
};
export default apiManagerInjectable;

View File

@ -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<any>
[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<KubeObject> = {
clusterName: cluster.name,
editResourceTab,
hideDetails,
apiManager,
kubeObjectMenuRegistry,
};
return (
<Component
dependencies={dependencies}
{...props}
/>
);
const clusterInjectable: IInjectable<ICluster | null> = {
getDependencies: () => ({}),
instantiate: () => getActiveClusterEntity(),
lifecycle: lifecycleEnum.transient,
};
export default clusterInjectable;

View File

@ -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;

View File

@ -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<typeof editResourceTab> = {
getDependencies: () => ({}),
instantiate: () => editResourceTab,
};
export default editResourceTabInjectable;

View File

@ -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<typeof hideDetails> = {
getDependencies: () => ({}),
instantiate: () => hideDetails,
};
export default hideDetailsInjectable;

View File

@ -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<IKubeObjectMenuRegistry> = {
getDependencies: () => ({}),
instantiate: () => KubeObjectMenuRegistry.getInstance(),
};
export default kubeObjectMenuRegistryInjectable;

View File

@ -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<KubeObject>
> = {
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;

View File

@ -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<KubeObject>;
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: <TKubeObjectStore extends KubeObjectStore<KubeObject>>(
// eslint-disable-next-line unused-imports/no-unused-vars-ts
api: string | KubeApi<KubeObject>,
): 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 = () => <li>Some menu item</li>;
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(
<Inject
injectableKey={KubeObjectMenuInjectable}
object={objectStub}
toolbar={true}
/>,
);
}).not.toThrow();
});
it("given no kube object, renders", () => {
objectStub = null;
const { baseElement } = render(
<KubeObjectMenu
<Inject
injectableKey={KubeObjectMenuInjectable}
object={objectStub}
toolbar={true}
dependencies={dependencies}
/>,
);
@ -136,9 +153,9 @@ describe("kube-object-menu", () => {
<div>
<ConfirmDialog />
<KubeObjectMenu
<Inject
injectableKey={KubeObjectMenuInjectable}
object={objectStub}
dependencies={dependencies}
toolbar={true}
removeAction={removeActionMock}
/>
@ -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(
<div>
<ConfirmDialog />
<KubeObjectMenu
<Inject
injectableKey={KubeObjectMenuInjectable}
object={objectStub}
dependencies={dependencies}
toolbar={true}
removeAction={() => {}}
/>
@ -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(
<div>
<ConfirmDialog />
<KubeObjectMenu
<Inject
injectableKey={KubeObjectMenuInjectable}
object={objectStub}
dependencies={dependencies}
toolbar={true}
removeAction={() => {}}
/>
@ -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 = () => <li>Some menu item</li>;
const dynamicMenuItemStub: KubeObjectMenuRegistration = {
apiVersions,
kind,
components: { MenuItem: MenuItemComponent },
};
const kubeObjectMenuRegistry: KubeObjectMenuRegistry = di.inject(
kubeObjectMenuRegistryInjectable,
);
kubeObjectMenuRegistry.add(dynamicMenuItemStub);
};

View File

@ -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<TKubeObject>{
apiManager: IGettableStore;
kubeObjectMenuRegistry: IHasGettableItemsForKind;
clusterName: string,
export interface KubeObjectMenuDependencies<TKubeObject> {
apiManager: IApiManager;
kubeObjectMenuRegistry: IKubeObjectMenuRegistry;
clusterName: string;
hideDetails: () => void;
editResourceTab: (kubeObject: TKubeObject) => void;
}
@ -40,22 +40,21 @@ export interface KubeObjectMenuProps<TKubeObject> extends MenuActionsProps {
object: TKubeObject | null | undefined;
editable?: boolean;
removable?: boolean;
dependencies?: KubeObjectMenuDependencies<TKubeObject>
}
export interface KubeObjectMenuPropsAndDependencies<TKubeObject>
extends KubeObjectMenuProps<TKubeObject>,
KubeObjectMenuDependencies<TKubeObject> {}
export class KubeObjectMenu<
TKubeObject extends KubeObject,
> extends React.Component<KubeObjectMenuProps<TKubeObject>> {
get dependencies() {
return this.props.dependencies;
}
> extends React.Component<KubeObjectMenuPropsAndDependencies<TKubeObject>> {
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 (
<p>
Remove {object.kind} <b>{breadcrumb}</b> from <b>{this.dependencies.clusterName}</b>?
Remove {object.kind} <b>{breadcrumb}</b> from <b>{this.props.clusterName}</b>?
</p>
);
}
@ -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<any> }}, index: number) => (
<MenuItem
object={object}
toolbar={toolbar}
// TODO: Fix misuse of index in key
key={`menu-item-${index}`}
/>
),
.map(
(
{
components: { MenuItem },
}: { components: { MenuItem: React.ReactType<any> }},
index: number,
) => (
<MenuItem
object={object}
toolbar={toolbar}
// TODO: Fix misuse of index in key
key={`menu-item-${index}`}
/>
),
);
}

View File

@ -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(
<DiContextProvider value={{ di }}>{ui}</DiContextProvider>,
);