diff --git a/src/modules/feature/media-shelf/MediaBoardExternalToolElement.unit.ts b/src/modules/feature/media-shelf/MediaBoardExternalToolElement.unit.ts index 54afe20e08..e578b23d9c 100644 --- a/src/modules/feature/media-shelf/MediaBoardExternalToolElement.unit.ts +++ b/src/modules/feature/media-shelf/MediaBoardExternalToolElement.unit.ts @@ -1,12 +1,24 @@ +import EnvConfigModule from "@/store/env-config"; +import NotifierModule from "@/store/notifier"; +import { AlertPayload } from "@/store/types/alert-payload"; import { ComponentProps } from "@/types/vue"; +import { ENV_CONFIG_MODULE_KEY, NOTIFIER_MODULE_KEY } from "@/utils/inject"; +import { createModuleMocks } from "@/utils/mock-store-module"; import { axiosErrorFactory, + businessErrorFactory, + envsFactory, externalToolDisplayDataFactory, mediaExternalToolElementResponseFactory, } from "@@/tests/test-utils"; -import { useContextExternalToolApi } from "@data-external-tool"; +import { createTestingI18n } from "@@/tests/test-utils/setup"; +import { + useContextExternalToolApi, + useExternalToolLaunchState, +} from "@data-external-tool"; import { createMock, DeepMocked } from "@golevelup/ts-jest"; import { flushPromises, shallowMount } from "@vue/test-utils"; +import { nextTick, ref } from "vue"; import { MediaElementDisplay } from "./data"; import MediaBoardElementDisplay from "./MediaBoardElementDisplay.vue"; import MediaBoardExternalToolElement from "./MediaBoardExternalToolElement.vue"; @@ -17,108 +29,248 @@ describe("MediaBoardExternalToolElement", () => { let useContextExternalToolApiMock: DeepMocked< ReturnType >; + let useExternalToolLaunchStateMock: DeepMocked< + ReturnType + >; const getWrapper = ( props: ComponentProps ) => { + const refreshTime = 299000; + const envConfigModule = createModuleMocks(EnvConfigModule, { + getEnv: envsFactory.build({ CTL_TOOLS_RELOAD_TIME_MS: refreshTime }), + }); + const notifierModule = createModuleMocks(NotifierModule); + const wrapper = shallowMount(MediaBoardExternalToolElement, { + global: { + plugins: [createTestingI18n()], + provide: { + [ENV_CONFIG_MODULE_KEY.valueOf()]: envConfigModule, + [NOTIFIER_MODULE_KEY.valueOf()]: notifierModule, + }, + }, props, }); return { wrapper, + notifierModule, + refreshTime, }; }; beforeEach(() => { useContextExternalToolApiMock = createMock>(); + useExternalToolLaunchStateMock = createMock< + ReturnType + >({ + error: ref(), + }); jest .mocked(useContextExternalToolApi) .mockReturnValue(useContextExternalToolApiMock); + jest + .mocked(useExternalToolLaunchState) + .mockReturnValue(useExternalToolLaunchStateMock); + + jest.useFakeTimers("legacy"); }); afterEach(() => { jest.resetAllMocks(); }); - describe("when the api returns display data", () => { - const setup = async () => { - const externalToolElement = - mediaExternalToolElementResponseFactory.build(); - const displayDataResponse = externalToolDisplayDataFactory.build({ - name: "name", - description: "description", + describe("when loading external tool data", () => { + describe("when the api returns data", () => { + const setup = async () => { + const externalToolElement = + mediaExternalToolElementResponseFactory.build(); + const displayDataResponse = externalToolDisplayDataFactory.build({ + name: "name", + description: "description", + }); + + useContextExternalToolApiMock.fetchDisplayDataCall.mockResolvedValue( + displayDataResponse + ); + + const { wrapper } = getWrapper({ + element: externalToolElement, + }); + + await flushPromises(); + + return { + wrapper, + externalToolElement, + displayDataResponse, + }; + }; + + it("should call the api to load display data", async () => { + const { externalToolElement } = await setup(); + + expect( + useContextExternalToolApiMock.fetchDisplayDataCall + ).toHaveBeenCalledWith( + externalToolElement.content.contextExternalToolId + ); + }); + + it("should call the state to load the launch request", async () => { + const { externalToolElement } = await setup(); + + expect( + useExternalToolLaunchStateMock.fetchLaunchRequest + ).toHaveBeenCalledWith( + externalToolElement.content.contextExternalToolId + ); }); - useContextExternalToolApiMock.fetchDisplayDataCall.mockResolvedValue( - displayDataResponse - ); + it("should map to the display props", async () => { + const { wrapper, displayDataResponse } = await setup(); + + const displayComponent = wrapper.findComponent( + MediaBoardElementDisplay + ); - const { wrapper } = getWrapper({ - element: externalToolElement, + expect(displayComponent.props().element).toEqual({ + title: displayDataResponse.name, + description: displayDataResponse.description, + thumbnail: undefined, + }); }); + }); - await flushPromises(); + describe("when the display data api returns an error", () => { + const setup = async () => { + const externalToolElement = + mediaExternalToolElementResponseFactory.build(); + const error = axiosErrorFactory.build(); + + useContextExternalToolApiMock.fetchDisplayDataCall.mockRejectedValue( + error + ); + + const logger = jest.spyOn(console, "error").mockImplementation(); + + const { wrapper } = getWrapper({ + element: externalToolElement, + }); + + await flushPromises(); + + return { + wrapper, + logger, + error, + }; + }; + + it("should log the error", async () => { + const { logger, error } = await setup(); + + expect(logger).toHaveBeenCalledWith(error); + }); + }); + }); + + describe("when the refresh time is over", () => { + const setup = () => { + const { wrapper, refreshTime } = getWrapper({ + element: mediaExternalToolElementResponseFactory.build(), + }); return { wrapper, - externalToolElement, - displayDataResponse, + refreshTime, }; }; - it("should call the api", async () => { - const { externalToolElement } = await setup(); + it("should refresh the display data", async () => { + const { refreshTime } = setup(); + await nextTick(); expect( - useContextExternalToolApiMock.fetchDisplayDataCall - ).toHaveBeenCalledWith(externalToolElement.content.contextExternalToolId); - }); - - it("should map to the display props", async () => { - const { wrapper, displayDataResponse } = await setup(); + useExternalToolLaunchStateMock.fetchLaunchRequest + ).toHaveBeenCalledTimes(1); - const displayComponent = wrapper.findComponent(MediaBoardElementDisplay); + jest.advanceTimersByTime(refreshTime + 1000); + await nextTick(); - expect(displayComponent.props().element).toEqual({ - title: displayDataResponse.name, - description: displayDataResponse.description, - thumbnail: undefined, - }); + expect( + useExternalToolLaunchStateMock.fetchLaunchRequest + ).toHaveBeenCalledTimes(2); }); }); - describe("when the api returns an error", () => { - const setup = async () => { - const externalToolElement = - mediaExternalToolElementResponseFactory.build(); - const error = axiosErrorFactory.build(); + describe("when clicking the element", () => { + describe("when a launch request is available", () => { + const setup = () => { + const externalToolElement = + mediaExternalToolElementResponseFactory.build(); + const { wrapper } = getWrapper({ + element: externalToolElement, + }); + + return { + wrapper, + externalToolElement, + }; + }; - useContextExternalToolApiMock.fetchDisplayDataCall.mockRejectedValue( - error - ); + it("should launch the tool", async () => { + const { wrapper } = setup(); - const logger = jest.spyOn(console, "error").mockImplementation(); + await wrapper.trigger("click"); - const { wrapper } = getWrapper({ - element: externalToolElement, + expect(useExternalToolLaunchStateMock.launchTool).toHaveBeenCalled(); }); - await flushPromises(); + it("should load the next launch request", async () => { + const { wrapper, externalToolElement } = setup(); - return { - wrapper, - logger, - error, + await wrapper.trigger("click"); + + expect( + useExternalToolLaunchStateMock.fetchLaunchRequest + ).toHaveBeenCalledWith( + externalToolElement.content.contextExternalToolId + ); + }); + }); + + describe("when loading the launch request failed", () => { + const setup = () => { + const externalToolElement = + mediaExternalToolElementResponseFactory.build(); + const { wrapper, notifierModule } = getWrapper({ + element: externalToolElement, + }); + + useExternalToolLaunchStateMock.error.value = + businessErrorFactory.build(); + + return { + wrapper, + externalToolElement, + notifierModule, + }; }; - }; - it("should log the error", async () => { - const { logger, error } = await setup(); + it("should show an error notification", async () => { + const { wrapper, notifierModule } = setup(); + + await wrapper.trigger("click"); - expect(logger).toHaveBeenCalledWith(error); + expect(notifierModule.show).toHaveBeenCalledWith<[AlertPayload]>({ + status: "error", + text: "error.generic", + }); + }); }); }); }); diff --git a/src/modules/feature/media-shelf/MediaBoardExternalToolElement.vue b/src/modules/feature/media-shelf/MediaBoardExternalToolElement.vue index 4a9839508c..6e9f3afd4f 100644 --- a/src/modules/feature/media-shelf/MediaBoardExternalToolElement.vue +++ b/src/modules/feature/media-shelf/MediaBoardExternalToolElement.vue @@ -1,12 +1,25 @@