Skip to content

Commit

Permalink
N21-1415 External Tool Board Element status (#2913)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarvinOehlerkingCap authored Nov 14, 2023
1 parent bb8805a commit b0f4062
Show file tree
Hide file tree
Showing 10 changed files with 358 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
import { ExternalToolDisplayData } from "@/store/external-tool";
import { BusinessError } from "@/store/types/commons";
import { mapAxiosErrorToResponseError } from "@/utils/api";
import { ref, Ref } from "vue";
import { ExternalToolDisplayData } from "../../store/external-tool";
import { useErrorHandler } from "../error-handling/ErrorHandler.composable";
import { useContextExternalToolApi } from "./index";

export const useExternalToolElementDisplayState = () => {
const { handleError } = useErrorHandler();
const { fetchDisplayDataCall } = useContextExternalToolApi();
const displayData: Ref<ExternalToolDisplayData | undefined> = ref();
const isLoading: Ref<boolean> = ref(false);
const error: Ref<BusinessError | undefined> = ref(undefined);

const fetchDisplayData = async (
contextExternalToolId: string
): Promise<void> => {
isLoading.value = true;
error.value = undefined;

try {
displayData.value = await fetchDisplayDataCall(contextExternalToolId);
} catch (error) {
handleError(error);
} catch (errorResponse) {
const apiError = mapAxiosErrorToResponseError(errorResponse);

error.value = {
error: apiError,
statusCode: apiError.code,
message: apiError.message,
};
}

isLoading.value = false;
};

return {
isLoading,
displayData,
isLoading,
error,
fetchDisplayData,
};
};
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { ExternalToolDisplayData } from "@/store/external-tool";
import { externalToolDisplayDataFactory } from "@@/tests/test-utils";
import {
axiosErrorFactory,
externalToolDisplayDataFactory,
} from "@@/tests/test-utils";
import { createMock, DeepMocked } from "@golevelup/ts-jest";
import { useErrorHandler } from "../error-handling/ErrorHandler.composable";
import { BusinessError } from "../../store/types/commons";
import { mapAxiosErrorToResponseError } from "../../utils/api";
import { useContextExternalToolApi } from "./ContextExternalToolApi.composable";
import { useExternalToolElementDisplayState } from "./ExternalToolElementDisplayState.composable";

jest.mock("@data-external-tool/ContextExternalToolApi.composable");
jest.mock("@/components/error-handling/ErrorHandler.composable");

describe("ExternalToolElementDisplayState.composable", () => {
let useContextExternalToolApiMock: DeepMocked<
ReturnType<typeof useContextExternalToolApi>
>;
let useErrorHandlerMock: DeepMocked<ReturnType<typeof useErrorHandler>>;

beforeEach(() => {
useContextExternalToolApiMock =
createMock<ReturnType<typeof useContextExternalToolApi>>();
useErrorHandlerMock = createMock<ReturnType<typeof useErrorHandler>>();

jest
.mocked(useContextExternalToolApi)
.mockReturnValue(useContextExternalToolApiMock);
jest.mocked(useErrorHandler).mockReturnValue(useErrorHandlerMock);
});

afterEach(() => {
Expand All @@ -46,12 +46,27 @@ describe("ExternalToolElementDisplayState.composable", () => {
displayDataMock
);

const composable = useExternalToolElementDisplayState();

composable.error.value = {
statusCode: 418,
message: "error",
};

return {
displayDataMock,
...useExternalToolElementDisplayState(),
...composable,
};
};

it("should reset the error", async () => {
const { fetchDisplayData, error } = setup();

await fetchDisplayData("contextId");

expect(error.value).toBeUndefined();
});

it("should call the api for display data of the card", async () => {
const { fetchDisplayData } = setup();

Expand All @@ -73,24 +88,30 @@ describe("ExternalToolElementDisplayState.composable", () => {

describe("when an error occurs during loading", () => {
const setup = () => {
const error = new Error("unable to load");
const errorResponse = axiosErrorFactory.build();
const apiError = mapAxiosErrorToResponseError(errorResponse);

useContextExternalToolApiMock.fetchDisplayDataCall.mockRejectedValue(
error
useContextExternalToolApiMock.fetchDisplayDataCall.mockRejectedValueOnce(
errorResponse
);

return {
error,
errorResponse,
apiError,
...useExternalToolElementDisplayState(),
};
};

it("should handle the error", async () => {
const { fetchDisplayData, error } = setup();
it("should set the error", async () => {
const { fetchDisplayData, error, apiError } = setup();

await fetchDisplayData("contextId");

expect(useErrorHandlerMock.handleError).toHaveBeenCalledWith(error);
expect(error.value).toEqual<BusinessError>({
error: apiError,
statusCode: apiError.code,
message: apiError.message,
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ToolConfigurationStatus,
} from "@/store/external-tool";
import { ContextExternalTool } from "@/store/external-tool/context-external-tool";
import { BusinessError } from "@/store/types/commons";
import { I18N_KEY } from "@/utils/inject";
import {
contextExternalToolFactory,
Expand All @@ -22,15 +23,13 @@ import {
} from "@data-external-tool";
import { createMock, DeepMocked } from "@golevelup/ts-jest";
import { mdiPuzzleOutline } from "@mdi/js";
import { useDeleteConfirmationDialog } from "@ui-confirmation-dialog";
import { useSharedLastCreatedElement } from "@util-board";
import { MountOptions, shallowMount, Wrapper } from "@vue/test-utils";
import Vue, { ref } from "vue";
import ExternalToolElement from "./ExternalToolElement.vue";
import { useSharedLastCreatedElement } from "@util-board";

jest.mock("@data-board");
jest.mock("@data-external-tool");
jest.mock("@ui-confirmation-dialog");
jest.mock("@util-board");

const EMPTY_TEST_ELEMENT: ExternalToolElementResponse = {
Expand All @@ -49,7 +48,7 @@ describe("ExternalToolElement", () => {
let useBoardFocusHandlerMock: DeepMocked<
ReturnType<typeof useBoardFocusHandler>
>;
let useSharedExternalToolElementDisplayStateMock: DeepMocked<
let useExternalToolElementDisplayStateMock: DeepMocked<
ReturnType<typeof useExternalToolElementDisplayState>
>;
let useExternalToolLaunchStateMock: DeepMocked<
Expand All @@ -64,7 +63,7 @@ describe("ExternalToolElement", () => {
createMock<ReturnType<typeof useContentElementState>>();
useBoardFocusHandlerMock =
createMock<ReturnType<typeof useBoardFocusHandler>>();
useSharedExternalToolElementDisplayStateMock =
useExternalToolElementDisplayStateMock =
createMock<ReturnType<typeof useExternalToolElementDisplayState>>();
useExternalToolLaunchStateMock =
createMock<ReturnType<typeof useExternalToolLaunchState>>();
Expand All @@ -77,7 +76,7 @@ describe("ExternalToolElement", () => {
jest.mocked(useBoardFocusHandler).mockReturnValue(useBoardFocusHandlerMock);
jest
.mocked(useExternalToolElementDisplayState)
.mockReturnValue(useSharedExternalToolElementDisplayStateMock);
.mockReturnValue(useExternalToolElementDisplayStateMock);
jest
.mocked(useExternalToolLaunchState)
.mockReturnValue(useExternalToolLaunchStateMock);
Expand All @@ -99,14 +98,9 @@ describe("ExternalToolElement", () => {
) => {
document.body.setAttribute("data-app", "true");

const useDeleteConfirmationDialogReturnValue =
createMock<ReturnType<typeof useDeleteConfirmationDialog>>();
jest
.mocked(useDeleteConfirmationDialog)
.mockReturnValue(useDeleteConfirmationDialogReturnValue);

useContentElementStateMock.modelValue = ref(props.element.content);
useSharedExternalToolElementDisplayStateMock.displayData = ref(displayData);
useExternalToolElementDisplayStateMock.displayData = ref(displayData);
useExternalToolElementDisplayStateMock.error = ref(undefined);
useSharedLastCreatedElementMock.lastCreatedElementId = ref(undefined);

const wrapper: Wrapper<Vue> = shallowMount(
Expand All @@ -130,7 +124,6 @@ describe("ExternalToolElement", () => {

return {
wrapper,
useDeleteConfirmationDialogReturnValue,
};
};

Expand All @@ -157,7 +150,7 @@ describe("ExternalToolElement", () => {
await Vue.nextTick();

expect(
useSharedExternalToolElementDisplayStateMock.fetchDisplayData
useExternalToolElementDisplayStateMock.fetchDisplayData
).toHaveBeenCalledWith("contextExternalToolId");
});

Expand Down Expand Up @@ -234,7 +227,7 @@ describe("ExternalToolElement", () => {
await Vue.nextTick();

expect(
useSharedExternalToolElementDisplayStateMock.fetchDisplayData
useExternalToolElementDisplayStateMock.fetchDisplayData
).not.toHaveBeenCalled();
});

Expand Down Expand Up @@ -463,7 +456,7 @@ describe("ExternalToolElement", () => {
const setup = () => {
const contextExternalToolId = "context-external-tool-id";

useSharedExternalToolElementDisplayStateMock.isLoading = ref(true);
useExternalToolElementDisplayStateMock.isLoading = ref(true);

const { wrapper } = getWrapper({
element: {
Expand Down Expand Up @@ -491,7 +484,7 @@ describe("ExternalToolElement", () => {
const setup = () => {
const contextExternalToolId = "context-external-tool-id";

useSharedExternalToolElementDisplayStateMock.isLoading = ref(false);
useExternalToolElementDisplayStateMock.isLoading = ref(false);

const { wrapper } = getWrapper(
{
Expand Down Expand Up @@ -639,7 +632,7 @@ describe("ExternalToolElement", () => {
await Vue.nextTick();

expect(
useSharedExternalToolElementDisplayStateMock.fetchDisplayData
useExternalToolElementDisplayStateMock.fetchDisplayData
).toHaveBeenCalledWith(savedTool.id);
});
});
Expand Down Expand Up @@ -675,4 +668,54 @@ describe("ExternalToolElement", () => {
});
});
});

describe("Alert", () => {
describe("when there is an error or the tool is outdated", () => {
const setup = () => {
const error: BusinessError = {
statusCode: 418,
message: "Loading error",
};

const { wrapper } = getWrapper(
{
element: EMPTY_TEST_ELEMENT,
isEditMode: true,
},
externalToolDisplayDataFactory.build({
status: ToolConfigurationStatus.Outdated,
})
);

useExternalToolElementDisplayStateMock.error.value = error;

return {
wrapper,
error,
};
};

it("should display an outdated alert", async () => {
const { wrapper } = setup();

const alert = wrapper.find(
'[data-testid="board-external-tool-element-alert"]'
);

expect(alert.props("isToolOutdated")).toEqual(true);
});

it("should display an error alert", async () => {
const { wrapper, error } = setup();

await Vue.nextTick();

const alert = wrapper.find(
'[data-testid="board-external-tool-element-alert"]'
);

expect(alert.props("error")).toEqual(error);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
@edit:element="onEditElement"
/>
</div>
<ExternalToolElementAlert
:error="error"
:is-tool-outdated="isToolOutdated"
data-testid="board-external-tool-element-alert"
/>
<ExternalToolElementConfigurationDialog
:is-open="isConfigurationDialogOpen"
:context-id="element.id"
Expand All @@ -64,6 +69,7 @@ import {
useExternalToolLaunchState,
} from "@data-external-tool";
import { mdiPuzzleOutline } from "@mdi/js";
import { useSharedLastCreatedElement } from "@util-board";
import {
computed,
ComputedRef,
Expand All @@ -75,12 +81,13 @@ import {
toRef,
watch,
} from "vue";
import ExternalToolElementAlert from "./ExternalToolElementAlert.vue";
import ExternalToolElementConfigurationDialog from "./ExternalToolElementConfigurationDialog.vue";
import ExternalToolElementMenu from "./ExternalToolElementMenu.vue";
import { useSharedLastCreatedElement } from "@util-board";
export default defineComponent({
components: {
ExternalToolElementAlert,
ExternalToolElementConfigurationDialog,
ExternalToolElementMenu,
},
Expand All @@ -106,6 +113,7 @@ export default defineComponent({
fetchDisplayData,
displayData,
isLoading: isDisplayDataLoading,
error,
} = useExternalToolElementDisplayState();
const { launchTool, fetchLaunchRequest } = useExternalToolLaunchState();
Expand Down Expand Up @@ -202,7 +210,9 @@ export default defineComponent({
hasLinkedTool,
toolDisplayName,
displayData,
error,
isLoading,
isToolOutdated,
isConfigurationDialogOpen,
mdiPuzzleOutline,
onMoveElementDown,
Expand Down
Loading

0 comments on commit b0f4062

Please sign in to comment.