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

Start converting custom column tests to use ApplicationBuilder

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-02-22 15:47:23 -05:00
parent 21c47e3f82
commit c588afb27f
12 changed files with 2121 additions and 151 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,98 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RenderResult } from "@testing-library/react";
import { CatalogCategory, type CatalogCategorySpec, type CategoryColumnRegistration } from "../../common/catalog";
import catalogCategoryRegistryInjectable from "../../common/catalog/category-registry.injectable";
import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
describe("custom category columns for catalog", () => {
let builder: ApplicationBuilder;
let renderResult: RenderResult;
beforeEach(async () => {
builder = getApplicationBuilder();
renderResult = await builder.render();
const navigateToCatalog = builder.applicationWindow.only.di.inject(navigateToCatalogInjectable);
navigateToCatalog();
});
it("renders", () => {
expect(renderResult.baseElement).toMatchSnapshot();
});
it("shows 'Browse All' view", () => {
expect(renderResult.queryByTestId("catalog-list-for-browse-all")).toBeInTheDocument();
});
it("should show the 'Kind' column", () => {
expect(renderResult.queryByTestId("browse-all-category-column")).toBeInTheDocument();
});
describe("when category is added using default colemns", () => {
beforeEach(() => {
const catalogCategoryRegistry = builder.applicationWindow.only.di.inject(catalogCategoryRegistryInjectable);
catalogCategoryRegistry.add(new TestCategory());
});
it("renders", () => {
expect(renderResult.baseElement).toMatchSnapshot();
});
it("shows category in sidebar", () => {
expect(renderResult.queryByTestId("foo.bar.bat/Test-tab")).toBeInTheDocument();
});
it("still shows 'Browse All' view", () => {
expect(renderResult.queryByTestId("catalog-list-for-browse-all")).toBeInTheDocument();
});
describe.only("when the Test category tab is clicked", () => {
beforeEach(async () => {
const testCategory = renderResult.getByTestId("foo.bar.bat/Test-tab");
testCategory.click();
});
it("renders", () => {
expect(renderResult.baseElement).toMatchSnapshot();
});
it.only("shows view for category", () => {
expect(renderResult.queryByTestId("catalog-list-for-Test")).toBeInTheDocument();
});
});
});
});
class TestCategory extends CatalogCategory {
apiVersion = "catalog.k8slens.dev/v1alpha1";
kind = "CatalogCategory";
metadata = {
name: "Test",
icon: "question_mark",
};
spec: CatalogCategorySpec = {
group: "foo.bar.bat",
names: {
kind: "Test",
},
versions: [],
};
constructor(columns?: CategoryColumnRegistration[]) {
super();
this.spec = {
displayColumns: columns,
...this.spec,
};
}
}

View File

@ -3,12 +3,17 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainer } from "@ogre-tools/injectable";
import type { CatalogCategoryMetadata, CatalogCategorySpec } from "../../../../common/catalog";
import { CatalogEntity, categoryVersion } from "../../../../common/catalog";
import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable";
import { CatalogCategory } from "../../../api/catalog-entity";
import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry";
import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import { noop } from "../../../utils";
import type { CatalogEntityStore } from "../catalog-entity-store/catalog-entity.store";
import { catalogEntityStore } from "../catalog-entity-store/catalog-entity.store";
import type { CatalogEntityStore } from "../catalog-entity-store.injectable";
import catalogEntityStoreInjectable from "../catalog-entity-store.injectable";
class TestEntityOne extends CatalogEntity {
public static readonly apiVersion: string = "entity.k8slens.dev/v1alpha1";
@ -63,6 +68,12 @@ class TestCategoryTwo extends CatalogCategory {
}
describe("CatalogEntityStore", () => {
let di: DiContainer;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
});
describe("getTotalCount", () => {
let store: CatalogEntityStore;
let testCategoryOne: TestCategoryOne;
@ -129,21 +140,22 @@ describe("CatalogEntityStore", () => {
testCategoryOne = new TestCategoryOne();
testCategoryTwo = new TestCategoryTwo();
store = catalogEntityStore({
catalogRegistry: {
items: [
testCategoryOne,
testCategoryTwo,
],
di.override(catalogCategoryRegistryInjectable, () => ({
items: [
testCategoryOne,
testCategoryTwo,
],
}));
di.override(catalogEntityRegistryInjectable, () => ({
onRun: noop,
filteredItems: entityItems,
getItemsForCategory: <T extends CatalogEntity>(category: CatalogCategory): T[] => {
return entityItems.filter(item => category.spec.versions.some(version => item instanceof version.entityClass)) as T[];
},
entityRegistry: {
onRun: noop,
filteredItems: entityItems,
getItemsForCategory: <T extends CatalogEntity>(category: CatalogCategory): T[] => {
return entityItems.filter(item => category.spec.versions.some(version => item instanceof version.entityClass)) as T[];
},
},
});
} as CatalogEntityRegistry));
store = di.inject(catalogEntityStoreInjectable);
});
it("given no active category, returns count of all kinds", () => {

View File

@ -0,0 +1,91 @@
/**
* 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 catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable";
import type { IComputedValue, IObservableValue } from "mobx";
import { computed, observable, reaction } from "mobx";
import type { CatalogEntity, CatalogCategory } from "../../api/catalog-entity";
import type { Disposer } from "../../utils";
import { disposer } from "../../utils";
import type { ItemListStore } from "../item-object-list";
export type CatalogEntityStore = ItemListStore<CatalogEntity, false> & {
readonly entities: IComputedValue<CatalogEntity[]>;
readonly activeCategory: IComputedValue<CatalogCategory | undefined>;
readonly selectedItemId: IObservableValue<string | undefined>;
readonly selectedItem: IComputedValue<CatalogEntity | undefined>;
watch(): Disposer;
onRun(entity: CatalogEntity): void;
};
export type ActiveCategory = {
browseAll: true;
} | {
browseAll: false;
activeTab: string;
};
const catalogEntityStoreInjectable = getInjectable({
id: "catalog-entity-store",
instantiate: (di): CatalogEntityStore => {
const entityRegistry = di.inject(catalogEntityRegistryInjectable);
const catalogRegistry = di.inject(catalogCategoryRegistryInjectable);
const activeCategory = observable.box<CatalogCategory | undefined>(undefined);
const selectedItemId = observable.box<string | undefined>(undefined);
const entities = computed(() => {
const category = activeCategory.get();
return category
? entityRegistry.getItemsForCategory(category, { filtered: true })
: entityRegistry.filteredItems;
});
const selectedItem = computed(() => {
const id = selectedItemId.get();
if (!id) {
return undefined;
}
return entities.get().find(entity => entity.getId() === id);
});
const loadAll = () => {
const category = activeCategory.get();
if (category) {
category.emit("load");
} else {
for (const category of catalogRegistry.items) {
category.emit("load");
}
}
};
return {
entities,
selectedItem,
activeCategory,
selectedItemId,
watch: () => disposer(
reaction(() => entities.get(), loadAll),
reaction(() => activeCategory.get(), loadAll, { delay: 100 }),
),
onRun: entity => entityRegistry.onRun(entity),
failedLoading: false,
getTotalCount: () => entities.get().length,
isLoaded: true,
isSelected: (item) => item.getId() === selectedItemId.get(),
isSelectedAll: () => false,
pickOnlySelected: () => [],
toggleSelection: () => {},
toggleSelectionAll: () => {},
removeSelectedItems: async () => {},
};
},
});
export default catalogEntityStoreInjectable;

View File

@ -1,19 +0,0 @@
/**
* 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 { catalogEntityStore } from "./catalog-entity.store";
import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable";
import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable";
const catalogEntityStoreInjectable = getInjectable({
id: "catalog-entity-store",
instantiate: (di) => catalogEntityStore({
entityRegistry: di.inject(catalogEntityRegistryInjectable),
catalogRegistry: di.inject(catalogCategoryRegistryInjectable),
}),
});
export default catalogEntityStoreInjectable;

View File

@ -1,86 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { IComputedValue, IObservableValue } from "mobx";
import { computed, observable, reaction } from "mobx";
import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry";
import type { CatalogEntity } from "../../../api/catalog-entity";
import type { CatalogCategory, CatalogCategoryRegistry } from "../../../../common/catalog";
import type { Disposer } from "../../../../common/utils";
import { disposer } from "../../../../common/utils";
import type { ItemListStore } from "../../item-object-list";
type EntityRegistry = Pick<CatalogEntityRegistry, "getItemsForCategory" | "filteredItems" | "onRun">;
type CatalogRegistry = Pick<CatalogCategoryRegistry, "items">;
interface Dependencies {
entityRegistry: EntityRegistry;
catalogRegistry: CatalogRegistry;
}
export type CatalogEntityStore = ItemListStore<CatalogEntity, false> & {
readonly entities: IComputedValue<CatalogEntity[]>;
readonly activeCategory: IObservableValue<CatalogCategory | undefined>;
readonly selectedItemId: IObservableValue<string | undefined>;
readonly selectedItem: IComputedValue<CatalogEntity | undefined>;
watch(): Disposer;
onRun(entity: CatalogEntity): void;
};
export function catalogEntityStore({
entityRegistry,
catalogRegistry,
}: Dependencies): CatalogEntityStore {
const activeCategory = observable.box<CatalogCategory | undefined>(undefined);
const selectedItemId = observable.box<string | undefined>(undefined);
const entities = computed(() => {
const category = activeCategory.get();
return category
? entityRegistry.getItemsForCategory(category, { filtered: true })
: entityRegistry.filteredItems;
});
const selectedItem = computed(() => {
const id = selectedItemId.get();
if (!id) {
return undefined;
}
return entities.get().find(entity => entity.getId() === id);
});
const loadAll = () => {
const category = activeCategory.get();
if (category) {
category.emit("load");
} else {
for (const category of catalogRegistry.items) {
category.emit("load");
}
}
};
return {
entities,
selectedItem,
activeCategory,
selectedItemId,
watch: () => disposer(
reaction(() => entities.get(), loadAll),
reaction(() => activeCategory.get(), loadAll, { delay: 100 }),
),
onRun: entity => entityRegistry.onRun(entity),
failedLoading: false,
getTotalCount: () => entities.get().length,
isLoaded: true,
isSelected: (item) => item.getId() === selectedItemId.get(),
isSelectedAll: () => false,
pickOnlySelected: () => [],
toggleSelection: () => {},
toggleSelectionAll: () => {},
removeSelectedItems: async () => {},
};
}

View File

@ -7,7 +7,6 @@ import treeStyles from "./catalog-tree.module.scss";
import styles from "./catalog-menu.module.scss";
import React from "react";
import type { TreeItemProps } from "@material-ui/lab";
import { TreeItem, TreeView } from "@material-ui/lab";
import { Icon } from "../icon";
import { StylesProvider } from "@material-ui/core";
@ -36,12 +35,6 @@ function getCategoryIcon(category: CatalogCategory) {
return null;
}
function Item(props: TreeItemProps) {
return (
<TreeItem classes={treeStyles} {...props}/>
);
}
interface Dependencies {
filteredCategories: IComputedValue<CatalogCategory[]>;
}
@ -60,13 +53,17 @@ const NonInjectedCatalogMenu = observer(({
defaultCollapseIcon={<Icon material="expand_more" />}
defaultExpandIcon={<Icon material="chevron_right" />}
selected={activeTab || "browse"}
onNodeSelect={console.log}
>
<Item
<TreeItem
classes={treeStyles}
nodeId="browse"
label="Browse"
data-testid="*-tab"
onClick={() => onItemClick("*")} />
<Item
onClick={() => onItemClick("*")}
/>
<TreeItem
classes={treeStyles}
nodeId="catalog"
label={<div className={styles.parent}>Categories</div>}
className={cssNames(styles.bordered)}
@ -74,16 +71,23 @@ const NonInjectedCatalogMenu = observer(({
{
filteredCategories.get()
.map(category => (
<Item
<TreeItem
classes={treeStyles}
icon={getCategoryIcon(category)}
key={category.getId()}
nodeId={category.getId()}
label={<CatalogCategoryLabel category={category} />}
data-testid={`${category.getId()}-tab`}
onClick={() => onItemClick(category.getId())} />
onLabelClick={console.log}
onIconClick={console.log}
onClick={() => {
console.log("clicking", category);
onItemClick(category.getId());
}}
/>
))
}
</Item>
</TreeItem>
</TreeView>
</div>
</StylesProvider>

View File

@ -10,10 +10,10 @@ import { Catalog } from "./catalog";
import type { CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog";
import { CatalogEntity } from "../../../common/catalog";
import type { CatalogEntityOnBeforeRun, CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
import type { CatalogEntityStore } from "./catalog-entity-store.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import type { DiContainer } from "@ogre-tools/injectable";
import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable";
import catalogEntityStoreInjectable from "./catalog-entity-store.injectable";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
import type { DiRender } from "../test-utils/renderFor";
import { renderFor } from "../test-utils/renderFor";

View File

@ -10,7 +10,7 @@ import { disposeOnUnmount, observer } from "mobx-react";
import { ItemListLayout } from "../item-object-list";
import type { IComputedValue } from "mobx";
import { action, computed, makeObservable, observable, reaction, runInAction, when } from "mobx";
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
import type { CatalogEntityStore } from "./catalog-entity-store.injectable";
import { MenuItem, MenuActions } from "../menu";
import type { CatalogEntityContextMenu } from "../../api/catalog-entity";
import type { CatalogCategory, CatalogCategoryRegistry, CatalogEntity } from "../../../common/catalog";
@ -27,7 +27,7 @@ import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
import { Avatar } from "../avatar";
import { withInjectables } from "@ogre-tools/injectable-react";
import catalogPreviousActiveTabStorageInjectable from "./catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable";
import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable";
import catalogEntityStoreInjectable from "./catalog-entity-store.injectable";
import type { GetCategoryColumnsParams, CategoryColumns } from "./columns/get.injectable";
import getCategoryColumnsInjectable from "./columns/get.injectable";
import type { RegisteredCustomCategoryViewDecl } from "./custom-views.injectable";
@ -133,16 +133,19 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
}
}, { fireImmediately: true }),
// If active category is filtered out, automatically switch to the first category
reaction(() => catalogCategoryRegistry.filteredItems, () => {
if (!catalogCategoryRegistry.filteredItems.find(item => item.getId() === catalogEntityStore.activeCategory.get()?.getId())) {
const item = catalogCategoryRegistry.filteredItems[0];
reaction(() => [...catalogCategoryRegistry.filteredItems], (categories) => {
const currentCategory = catalogEntityStore.activeCategory.get();
const someCategory = categories[0];
runInAction(() => {
if (item) {
this.activeTab = item.getId();
this.props.catalogEntityStore.activeCategory.set(item);
}
});
if (this.routeActiveTab === browseCatalogTab || !someCategory) {
return;
}
const currentCategoryShouldBeShown = Boolean(categories.find(item => item.getId() === someCategory.getId()));
if (!currentCategory || !currentCategoryShouldBeShown) {
this.activeTab = someCategory.getId();
this.props.catalogEntityStore.activeCategory.set(someCategory);
}
}),
]);
@ -174,6 +177,7 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
}
onTabChange = action((tabId: string | null) => {
console.log(tabId);
const activeCategory = this.categories.find(category => category.getId() === tabId);
this.props.emitEvent({

View File

@ -17,6 +17,7 @@ const defaultBrowseAllColumns: RegisteredAdditionalCategoryColumn[] = [
id: "kind",
sortBy: "kind",
title: "Kind",
"data-testid": "browse-all-category-column",
},
sortCallback: entity => entity.kind,
},

View File

@ -13,18 +13,15 @@ import { HotbarMenu } from "../hotbar/hotbar-menu";
import { DeleteClusterDialog } from "../delete-cluster-dialog";
import { withInjectables } from "@ogre-tools/injectable-react";
import { TopBar } from "../layout/top-bar/top-bar";
import catalogPreviousActiveTabStorageInjectable from "../+catalog/catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable";
import type { IComputedValue } from "mobx";
import currentRouteComponentInjectable from "../../routes/current-route-component.injectable";
import welcomeRouteInjectable from "../../../common/front-end-routing/routes/welcome/welcome-route.injectable";
import { buildURL } from "../../../common/utils/buildUrl";
import type { StorageLayer } from "../../utils";
import type { WatchForGeneralEntityNavigation } from "../../api/helpers/watch-for-general-entity-navigation.injectable";
import watchForGeneralEntityNavigationInjectable from "../../api/helpers/watch-for-general-entity-navigation.injectable";
import currentPathInjectable from "../../routes/current-path.injectable";
interface Dependencies {
catalogPreviousActiveTabStorage: StorageLayer<string | null>;
currentRouteComponent: IComputedValue<React.ElementType | undefined>;
welcomeUrl: string;
watchForGeneralEntityNavigation: WatchForGeneralEntityNavigation;
@ -84,7 +81,6 @@ class NonInjectedClusterManager extends React.Component<Dependencies> {
export const ClusterManager = withInjectables<Dependencies>(NonInjectedClusterManager, {
getProps: (di) => ({
catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable),
currentRouteComponent: di.inject(currentRouteComponentInjectable),
welcomeUrl: buildURL(di.inject(welcomeRouteInjectable).path),
watchForGeneralEntityNavigation: di.inject(watchForGeneralEntityNavigationInjectable),

View File

@ -70,6 +70,11 @@ export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
* indicator, might come from parent <TableHead>, don't use this prop outside (!)
*/
_nowrap?: boolean;
/**
* For passing in the testid
*/
"data-testid"?: string;
}
export class TableCell extends React.Component<TableCellProps> {