Skip to content

Commit

Permalink
implement configuration dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
MarvinOehlerkingCap committed Oct 12, 2023
1 parent c5e38d3 commit 384cccf
Show file tree
Hide file tree
Showing 37 changed files with 23,203 additions and 15,650 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useErrorHandler } from "@/components/error-handling/ErrorHandler.composable";
import { ToolContextType } from "@/serverApi/v3";
import { ExternalToolDisplayData } from "@/store/external-tool";
import { createTestableSharedComposable } from "@/utils/create-shared-composable";
import { useContextExternalToolApi } from "@data-external-tool";
import { ref, Ref } from "vue";

export const useSharedExternalToolElementDisplayState =
createTestableSharedComposable(() => {
const { handleError } = useErrorHandler();
const { fetchDisplayDataCall } = useContextExternalToolApi();
const displayDataMap: Map<string, Ref<ExternalToolDisplayData[]>> = new Map<
string,
Ref<ExternalToolDisplayData[]>
>();
const isLoading: Ref<boolean> = ref(false);

const getDisplayDataList = (
cardId: string
): Ref<ExternalToolDisplayData[]> | undefined => {
const displayDataList: Ref<ExternalToolDisplayData[]> | undefined =
displayDataMap.get(cardId);

return displayDataList;
};

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

try {
let displayDataList: Ref<ExternalToolDisplayData[]> | undefined =
getDisplayDataList(cardId);

if (!displayDataList?.value) {
displayDataList = ref([]);

displayDataMap.set(cardId, displayDataList);
}

displayDataList.value = await fetchDisplayDataCall(
cardId,
ToolContextType.BoardCard
);
} catch (error) {
handleError(error);
}

isLoading.value = false;
};

const findDisplayData = (
cardId: string,
contextExternalToolId: string
): ExternalToolDisplayData | undefined => {
const displayDataList: Ref<ExternalToolDisplayData[]> | undefined =
getDisplayDataList(cardId);

const displayData: ExternalToolDisplayData | undefined =
displayDataList?.value.find(
(externalToolDisplayData: ExternalToolDisplayData) =>
externalToolDisplayData.contextExternalToolId ===
contextExternalToolId
);

return displayData;
};

return {
isLoading,
getDisplayDataList,
fetchDisplayData,
findDisplayData,
};
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { useErrorHandler } from "@/components/error-handling/ErrorHandler.composable";
import { ToolContextType } from "@/serverApi/v3";
import { ExternalToolDisplayData } from "@/store/external-tool";
import { externalToolDisplayDataFactory } from "@@/tests/test-utils";
import { useContextExternalToolApi } from "@data-external-tool";
import { createMock, DeepMocked } from "@golevelup/ts-jest";
import { Ref } from "vue";
import { useSharedExternalToolElementDisplayState } from "./SharedExternalToolElementDisplayState.composable";

jest.mock("@data-external-tool");
jest.mock("@/components/error-handling/ErrorHandler.composable");
jest.mock<typeof import("@/utils/create-shared-composable")>(
"@/utils/create-shared-composable",
() => ({
createTestableSharedComposable: (composable) => composable,
})
);

describe("SharedExternalToolElementDisplayState.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(() => {
jest.clearAllMocks();
});

describe("when no data is loaded", () => {
it("should have no data lists", async () => {
const displayDataState = useSharedExternalToolElementDisplayState();

const result: Ref<ExternalToolDisplayData[]> | undefined =
displayDataState.getDisplayDataList("cardId");

expect(result).toBeUndefined();
});

it("should not find data", async () => {
const displayDataState = useSharedExternalToolElementDisplayState();

const result: ExternalToolDisplayData | undefined =
displayDataState.findDisplayData("cardId", "contextExternalToolId");

expect(result).toBeUndefined();
});
});

describe("when data is loaded", () => {
const setup = () => {
const displayData: ExternalToolDisplayData =
externalToolDisplayDataFactory.build();

useContextExternalToolApiMock.fetchDisplayDataCall.mockResolvedValue([
displayData,
]);

return {
displayData,
...useSharedExternalToolElementDisplayState(),
};
};

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

await fetchDisplayData("cardId");

expect(
useContextExternalToolApiMock.fetchDisplayDataCall
).toHaveBeenCalledWith("cardId", ToolContextType.BoardCard);
});

it("should set the display data in the state", async () => {
const { fetchDisplayData, getDisplayDataList, displayData } = setup();

await fetchDisplayData("cardId");
const result: Ref<ExternalToolDisplayData[]> | undefined =
getDisplayDataList("cardId");

expect(result?.value).toEqual([displayData]);
});

it("should find the display data for a specific tool", async () => {
const { fetchDisplayData, findDisplayData, displayData } = setup();

await fetchDisplayData("cardId");
const result: ExternalToolDisplayData | undefined = findDisplayData(
"cardId",
displayData.contextExternalToolId
);

expect(result).toEqual(displayData);
});

it("should not find data for non-existing tools", async () => {
const { fetchDisplayData, findDisplayData } = setup();

await fetchDisplayData("cardId");
const result: ExternalToolDisplayData | undefined = findDisplayData(
"cardId",
"otherId"
);

expect(result).toBeUndefined();
});
});

describe("when an error occurs during loading", () => {
const setup = () => {
const error = new Error("unable to load");

useContextExternalToolApiMock.fetchDisplayDataCall.mockRejectedValue(
error
);

return {
error,
...useSharedExternalToolElementDisplayState(),
};
};

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

await fetchDisplayData("cardId");

expect(useErrorHandlerMock.handleError).toHaveBeenCalledWith(error);
});
});
});
1 change: 1 addition & 0 deletions src/components/data-board-external-tool-element/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./SharedExternalToolElementDisplayState.composable";
15 changes: 12 additions & 3 deletions src/components/data-board/BoardApi.composable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ContentElementType,
CreateCardBodyParamsRequiredEmptyElementsEnum,
CreateContentElementBodyParams,
ExternalToolElementContent,
ExternalToolElementResponse,
FileElementContent,
RichTextElementContent,
RoomsApiFactory,
Expand Down Expand Up @@ -91,16 +91,25 @@ export const useBoardApi = () => {
};
}

if (element.type === ContentElementType.ExternalTool) {
if (isExternalToolElement(element)) {
return {
content: element.content as ExternalToolElementContent,
content: {
contextExternalToolId:
element.content.contextExternalToolId ?? undefined,
},
type: ContentElementType.ExternalTool,
};
}

throw new Error("element.type mapping is undefined for updateElementCall");
};

const isExternalToolElement = (
element: AnyContentElement
): element is ExternalToolElementResponse => {
return element.type === ContentElementType.ExternalTool;
};

const createElementCall = async (
cardId: string,
params: CreateContentElementBodyParams
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
ToolApiFactory,
ToolApiInterface,
ToolContextType,
ToolReferenceListResponse,
} from "@/serverApi/v3";
import { ExternalToolDisplayData } from "@/store/external-tool";
import { ExternalToolMapper } from "@/store/external-tool/mapper";
import { $axios } from "@/utils/api";
import { AxiosResponse } from "axios";

export const useContextExternalToolApi = () => {
const toolApi: ToolApiInterface = ToolApiFactory(undefined, "/v3", $axios);

const fetchDisplayDataCall = async (
contextId: string,
contextType: ToolContextType
): Promise<ExternalToolDisplayData[]> => {
const response: AxiosResponse<ToolReferenceListResponse> =
await toolApi.toolControllerGetToolReferences(contextId, contextType);

const mapped: ExternalToolDisplayData[] =
ExternalToolMapper.mapToExternalToolDisplayData(response.data);

return mapped;
};

return {
fetchDisplayDataCall,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as serverApi from "@/serverApi/v3/api";
import { ToolContextType, ToolReferenceResponse } from "@/serverApi/v3/api";
import {
ExternalToolDisplayData,
ToolConfigurationStatus,
} from "@/store/external-tool";
import {
mockApiResponse,
toolReferenceResponseFactory,
} from "@@/tests/test-utils";
import { createMock, DeepMocked } from "@golevelup/ts-jest";
import { useContextExternalToolApi } from "./ContextExternalToolApi.composable";

describe("ContextExternalToolApi.composable", () => {
let toolApi: DeepMocked<serverApi.ToolApiInterface>;

beforeEach(() => {
toolApi = createMock<serverApi.ToolApiInterface>();

jest.spyOn(serverApi, "ToolApiFactory").mockReturnValue(toolApi);
});

afterEach(() => {
jest.clearAllMocks();
});

describe("fetchDisplayDataCall", () => {
const setup = () => {
const displayData: ToolReferenceResponse =
toolReferenceResponseFactory.build({ logoUrl: "mockLogoUrl" });

toolApi.toolControllerGetToolReferences.mockResolvedValue(
mockApiResponse({ data: { data: [displayData] } })
);

return {
displayData,
};
};

it("should call the api for tool references", async () => {
setup();

await useContextExternalToolApi().fetchDisplayDataCall(
"cardId",
ToolContextType.BoardCard
);

expect(toolApi.toolControllerGetToolReferences).toHaveBeenCalledWith(
"cardId",
ToolContextType.BoardCard
);
});

it("should return an array of display data", async () => {
const { displayData } = setup();

const result: ExternalToolDisplayData[] =
await useContextExternalToolApi().fetchDisplayDataCall(
"courseId",
ToolContextType.Course
);

expect(result).toEqual<ExternalToolDisplayData[]>([
{
contextExternalToolId: displayData.contextToolId,
name: displayData.displayName,
logoUrl: displayData.logoUrl,
status: ToolConfigurationStatus.Latest,
openInNewTab: displayData.openInNewTab,
},
]);
});
});
});
1 change: 1 addition & 0 deletions src/components/data-external-tool/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./ContextExternalToolApi.composable";
Loading

0 comments on commit 384cccf

Please sign in to comment.