/** * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ import React from "react"; import { screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; 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 { getDiForUnitTesting } from "../../getDiForUnitTesting"; import type { DiContainer } from "@ogre-tools/injectable"; import catalogEntityStoreInjectable from "./catalog-entity-store/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"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import type { AppEvent } from "../../../common/app-event-bus/event-bus"; import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable"; import { computed } from "mobx"; import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable"; import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; import { flushPromises } from "../../../common/test-utils/flush-promises"; import userStoreInjectable from "../../../common/user-store/user-store.injectable"; import releaseChannelInjectable from "../../../common/vars/release-channel.injectable"; import defaultUpdateChannelInjectable from "../../../features/application-update/common/selected-update-channel/default-update-channel.injectable"; import currentlyInClusterFrameInjectable from "../../routes/currently-in-cluster-frame.injectable"; class MockCatalogEntity extends CatalogEntity { public apiVersion = "api"; public kind = "kind"; constructor(data: CatalogEntityData, public onRun: (context: CatalogEntityActionContext) => void | Promise) { super(data); } } function createMockCatalogEntity(onRun: (context: CatalogEntityActionContext) => void | Promise) { return new MockCatalogEntity({ metadata: { uid: "a_catalogEntity_uid", name: "a catalog entity", labels: { test: "label", }, }, status: { phase: "", }, spec: {}, }, onRun); } describe("", () => { let di: DiContainer; let catalogEntityStore: CatalogEntityStore; let catalogEntityRegistry: CatalogEntityRegistry; let appEventListener: jest.MockedFunction<(event: AppEvent) => void>; let onRun: jest.MockedFunction<(context: CatalogEntityActionContext) => void | Promise>; let catalogEntityItem: MockCatalogEntity; let render: DiRender; beforeEach(async () => { di = getDiForUnitTesting({ doGeneralOverrides: true }); di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); di.override(currentlyInClusterFrameInjectable, () => false); di.override(broadcastMessageInjectable, () => async () => {}); di.permitSideEffects(getConfigurationFileModelInjectable); render = renderFor(di); onRun = jest.fn(); catalogEntityItem = createMockCatalogEntity(onRun); catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); appEventListener = jest.fn(); di.inject(appEventBusInjectable).addListener(appEventListener); catalogEntityStore = di.inject(catalogEntityStoreInjectable); Object.assign(catalogEntityStore, { selectedItem: computed(() => catalogEntityItem), }); di.override(releaseChannelInjectable, () => ({ get: () => "latest" as const, init: async () => {}, })); await di.inject(defaultUpdateChannelInjectable).init(); di.inject(userStoreInjectable).load(); }); describe("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", () => { let onBeforeRunMock: AsyncFnMock; beforeEach(() => { onBeforeRunMock = asyncFn(); catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock); render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); }); it("calls on before run event", () => { const target = onBeforeRunMock.mock.calls[0][0].target; const actual = { id: target.getId(), name: target.getName() }; expect(actual).toEqual({ id: "a_catalogEntity_uid", name: "a catalog entity", }); }); it("does not call onRun yet", () => { expect(onRun).not.toHaveBeenCalled(); }); it("when before run event resolves, calls onRun", async () => { await onBeforeRunMock.resolve(); expect(onRun).toHaveBeenCalled(); }); }); it("onBeforeRun prevents event => onRun wont be triggered", async () => { const onBeforeRunMock = jest.fn((event) => event.preventDefault()); catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock); render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); await flushPromises(); expect(onRun).not.toHaveBeenCalled(); }); it("addOnBeforeRun throw an exception => onRun will be triggered", async () => { const onBeforeRunMock = jest.fn(() => { throw new Error("some error"); }); catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock); render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); await flushPromises(); expect(onRun).toHaveBeenCalled(); }); it("addOnRunHook return a promise and does not prevent run event => onRun()", (done) => { onRun.mockImplementation(() => done()); catalogEntityRegistry.addOnBeforeRun( async () => { // no op }, ); render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); }); it("addOnRunHook return a promise and prevents event wont be triggered", async () => { const onBeforeRunMock = asyncFn(); catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock); render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); onBeforeRunMock.mock.calls[0][0].preventDefault(); await onBeforeRunMock.resolve(); expect(onRun).not.toHaveBeenCalled(); }); it("addOnRunHook return a promise and reject => onRun will be triggered", async () => { const onBeforeRunMock = asyncFn(); catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock); render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); await onBeforeRunMock.reject(); expect(onRun).toHaveBeenCalled(); }); it("emits catalog open AppEvent", () => { render(); expect(appEventListener).toHaveBeenCalledWith( { action: "open", name: "catalog", }); }); it("emits catalog change AppEvent when changing the category", () => { render(); userEvent.click(screen.getByText("Web Links")); expect(appEventListener).toHaveBeenCalledWith({ action: "change-category", name: "catalog", params: { category: "Web Links", }, }); }); });