diff --git a/src/components/feature-board-file-element/FileContentElement.unit.ts b/src/components/feature-board-file-element/FileContentElement.unit.ts index 0aecf000af..80742e1dac 100644 --- a/src/components/feature-board-file-element/FileContentElement.unit.ts +++ b/src/components/feature-board-file-element/FileContentElement.unit.ts @@ -1,4 +1,8 @@ -import { FileRecordScanStatus, PreviewStatus } from "@/fileStorageApi/v3"; +import { + FileRecordScanStatus, + PreviewStatus, + PreviewWidth, +} from "@/fileStorageApi/v3"; import NotifierModule from "@/store/notifier"; import { AnyContentElement } from "@/types/board/ContentElement"; import { convertDownloadToPreviewUrl } from "@/utils/fileHelper"; @@ -10,8 +14,8 @@ import { fileElementResponseFactory } from "@@/tests/test-utils/factory/fileElem import { fileRecordResponseFactory } from "@@/tests/test-utils/factory/filerecordResponse.factory"; import { MountOptions, shallowMount } from "@vue/test-utils"; import Vue from "vue"; -import FileContent from "./content/FileContent.vue"; import FileContentElement from "./FileContentElement.vue"; +import FileContent from "./content/FileContent.vue"; import { FileProperties } from "./shared/types/file-properties"; import FileUpload from "./upload/FileUpload.vue"; @@ -131,7 +135,10 @@ describe("FileContentElement", () => { name: fileRecordResponse.name, isDownloadAllowed: true, url: fileRecordResponse.url, - previewUrl: convertDownloadToPreviewUrl(fileRecordResponse.url), + previewUrl: convertDownloadToPreviewUrl( + fileRecordResponse.url, + PreviewWidth._500 + ), size: fileRecordResponse.size, previewStatus: fileRecordResponse.previewStatus, element, @@ -441,7 +448,10 @@ describe("FileContentElement", () => { name: fileRecordResponse.name, isDownloadAllowed: true, url: fileRecordResponse.url, - previewUrl: convertDownloadToPreviewUrl(fileRecordResponse.url), + previewUrl: convertDownloadToPreviewUrl( + fileRecordResponse.url, + PreviewWidth._500 + ), size: fileRecordResponse.size, previewStatus: fileRecordResponse.previewStatus, element, diff --git a/src/components/feature-board-file-element/FileContentElement.vue b/src/components/feature-board-file-element/FileContentElement.vue index 41bd32e0b7..7ce005a3c0 100644 --- a/src/components/feature-board-file-element/FileContentElement.vue +++ b/src/components/feature-board-file-element/FileContentElement.vue @@ -35,7 +35,7 @@ diff --git a/src/components/feature-date-time-picker/DateTimePicker.unit.ts b/src/components/feature-date-time-picker/DateTimePicker.unit.ts index ade22cfb68..8f57a6d2c9 100644 --- a/src/components/feature-date-time-picker/DateTimePicker.unit.ts +++ b/src/components/feature-date-time-picker/DateTimePicker.unit.ts @@ -36,46 +36,65 @@ describe("DateTimePicker", () => { expect(wrapper.findComponent(DateTimePicker).exists()).toBe(true); }); - it("should emit input event on date input", async () => { - setup({ dateTime: new Date().toISOString() }); + describe("if date and time are set", () => { + it("should emit input event on date input", async () => { + setup({ dateTime: new Date().toISOString() }); - const datePicker = wrapper.findComponent({ name: "date-picker" }); - expect(datePicker.exists()).toBe(true); - const tomorrow = new Date(); - tomorrow.setDate(tomorrow.getDate() + 1); - datePicker.vm.$emit("update:date", tomorrow); + const datePicker = wrapper.findComponent({ name: "date-picker" }); + expect(datePicker.exists()).toBe(true); + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + datePicker.vm.$emit("update:date", tomorrow); - await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); - expect(wrapper.emitted("input")).toHaveLength(1); - }); + expect(wrapper.emitted("input")).toHaveLength(1); + }); - it("should emit input event on time input", async () => { - jest.useFakeTimers(); - setup({ dateTime: new Date().toISOString() }); + it("should emit input event on time input", async () => { + jest.useFakeTimers(); + setup({ dateTime: new Date().toISOString() }); - const timePicker = wrapper.findComponent({ name: "time-picker" }); - expect(timePicker.exists()).toBe(true); - timePicker.vm.$emit("update:time", "00:00"); + const timePicker = wrapper.findComponent({ name: "time-picker" }); + expect(timePicker.exists()).toBe(true); + timePicker.vm.$emit("update:time", "00:00"); - jest.advanceTimersByTime(1000); + jest.advanceTimersByTime(1000); - expect(wrapper.emitted("input")).toHaveLength(1); + expect(wrapper.emitted("input")).toHaveLength(1); + }); }); - it("should restrict timepicker when date is today", async () => { - setup({ dateTime: new Date().toISOString() }); + describe("if only date is set", () => { + it("should emit input event with time set to 23:59", async () => { + jest.useFakeTimers(); + setup({ dateTime: "" }); - const timePicker = wrapper.findComponent({ name: "time-picker" }); - expect(timePicker.exists()).toBe(true); - expect(timePicker.props("allowPast")).toBe(false); + const datePicker = wrapper.findComponent({ name: "date-picker" }); + const date = new Date("2030-01-01"); + datePicker.vm.$emit("update:date", date); + + jest.advanceTimersByTime(1000); + + const emits = wrapper.emitted("input"); + expect(emits?.length).toEqual(1); + date.setHours(23); + date.setMinutes(59); + expect(emits?.[0]).toEqual([date.toISOString()]); + }); }); - it("should not restrict timepicker when date is in the future", async () => { - setup({ dateTime: new Date("2300-01-01T00:00:00").toISOString() }); + describe("if only time is set", () => { + it("should emit no input event", async () => { + jest.useFakeTimers(); + setup({ dateTime: "" }); - const timePicker = wrapper.findComponent({ name: "time-picker" }); - expect(timePicker.exists()).toBe(true); - expect(timePicker.props("allowPast")).toBe(true); + const timePicker = wrapper.findComponent({ name: "time-picker" }); + timePicker.vm.$emit("update:time", "00:00"); + + jest.advanceTimersByTime(1000); + + expect(wrapper.emitted("input")).toBe(undefined); + }); }); }); diff --git a/src/components/feature-date-time-picker/DateTimePicker.vue b/src/components/feature-date-time-picker/DateTimePicker.vue index 16b41671b1..efb5604181 100644 --- a/src/components/feature-date-time-picker/DateTimePicker.vue +++ b/src/components/feature-date-time-picker/DateTimePicker.vue @@ -2,22 +2,21 @@
@@ -26,9 +25,8 @@ import DatePicker from "./DatePicker.vue"; import TimePicker from "./TimePicker.vue"; import { useVModel } from "@vueuse/core"; -import { isToday } from "@/plugins/datetime"; -import { I18N_KEY, injectStrict } from "@/utils/inject"; -import { defineComponent, ref } from "vue"; +import { computed, defineComponent, ref } from "vue"; +import { useI18n } from "@/composables/i18n.composable"; import dayjs from "dayjs"; export default defineComponent({ @@ -48,15 +46,12 @@ export default defineComponent({ timeInputAriaLabel: { type: String, default: "" }, minDate: { type: String }, maxDate: { type: String }, - required: { - type: Boolean, - }, - allowPast: { type: Boolean }, }, emits: ["input"], setup(props, { emit }) { - const i18n = injectStrict(I18N_KEY); - const locale = i18n.locale; + const { locale } = useI18n(); + + const dateTimeInPast = ref(false); const getTime = (dateIsoString: string) => { if (dateIsoString === "") { @@ -73,39 +68,45 @@ export default defineComponent({ dateTime.value ? dayjs(dateTime.value).format("YYYY-MM-DD") : "" ); const time = ref(dateTime.value ? getTime(dateTime.value) : ""); - const dateIsToday = ref(isToday(date.value)); + const dateRequired = computed(() => time.value !== ""); const emitDateTime = () => { - if (date.value !== "" && time.value !== "") { - const dateTime = new Date(date.value); - const hoursAndMinutes = time.value.split(":"); - dateTime.setHours( - parseInt(hoursAndMinutes[0]), - parseInt(hoursAndMinutes[1]) - ); - emit("input", dateTime.toISOString()); + if (date.value === "") { + return; + } + + let timeValue = time.value; + if (timeValue === "") { + timeValue = "23:59"; } + + const dateTime = new Date(date.value); + const hoursAndMinutes = timeValue.split(":"); + dateTime.setHours( + parseInt(hoursAndMinutes[0]), + parseInt(hoursAndMinutes[1]) + ); + dateTimeInPast.value = dateTime < new Date(); + emit("input", dateTime.toISOString()); }; - const handleDateInput = (newDate: string) => { + const onDateUpdate = (newDate: string) => { date.value = newDate; - dateIsToday.value = isToday(date.value); - emitDateTime(); }; - const handleTimeInput = (newTime: string) => { + const onTimeUpdate = (newTime: string) => { time.value = newTime; - emitDateTime(); }; return { date, time, - dateIsToday, - handleDateInput, - handleTimeInput, + onDateUpdate, + onTimeUpdate, + dateRequired, + dateTimeInPast, }; }, }); diff --git a/src/components/feature-date-time-picker/TimePicker.unit.ts b/src/components/feature-date-time-picker/TimePicker.unit.ts index 7b947d6a42..721b4adc86 100644 --- a/src/components/feature-date-time-picker/TimePicker.unit.ts +++ b/src/components/feature-date-time-picker/TimePicker.unit.ts @@ -144,108 +144,5 @@ describe("TimePicker", () => { expect(wrapper.emitted("update:time")).toBeUndefined(); }); }); - - describe("when time in the past is not allowed", () => { - it("should disable selection of time in the past", async () => { - setup({ - time: "12:30", - allowPast: false, - }); - - const textField = wrapper.findComponent({ name: "v-text-field" }); - const input = textField.find("input"); - expect(input.exists()).toBe(true); - await input.trigger("click"); - - const oneOClockListItem = wrapper - .findAll({ name: "v-list-item" }) - .at(0); // 00:00 - expect(oneOClockListItem.attributes()["aria-disabled"]).toBeDefined(); - }); - - it("should enable selection of time in the future", async () => { - setup({ - time: "12:30", - allowPast: false, - }); - - const textField = wrapper.findComponent({ name: "v-text-field" }); - const input = textField.find("input"); - expect(input.exists()).toBe(true); - await input.trigger("click"); - - const fiveOClockListItem = wrapper - .findAll({ name: "v-list-item" }) - .at(10); // 05:00 - expect( - fiveOClockListItem.attributes()["aria-disabled"] - ).toBeUndefined(); - }); - - it("should not emit update:time event when time inserted is in the past", async () => { - setup({ - time: "12:30", - allowPast: false, - }); - - const input = wrapper - .findComponent({ name: "v-text-field" }) - .find("input"); - - await input.trigger("focus"); - await input.setValue("03:01"); - await input.trigger("blur"); - await wrapper.vm.$nextTick(); - - expect(wrapper.emitted("update:time")).toBeUndefined(); - }); - - it("should emit input event when time inserted is in the future", async () => { - setup({ - time: "12:30", - allowPast: false, - }); - - const input = wrapper - .findComponent({ name: "v-text-field" }) - .find("input"); - - await input.trigger("focus"); - await input.setValue("03:11"); - - jest.advanceTimersByTime(1000); - expect(wrapper.emitted("update:time")).toHaveLength(1); - }); - }); - - describe("when time in the past is allowed", () => { - it("should enable selection of time in the past", async () => { - setup({ time: "12:30" }); - - const textField = wrapper.findComponent({ name: "v-text-field" }); - const input = textField.find("input"); - expect(input.exists()).toBe(true); - await input.trigger("click"); - - const oneOClockListItem = wrapper - .findAll({ name: "v-list-item" }) - .at(0); // 00:00 - expect(oneOClockListItem.attributes()["aria-disabled"]).toBeUndefined(); - }); - - it("should emit input event when time inserted is in the past", async () => { - setup({ time: "12:30" }); - - const input = wrapper - .findComponent({ name: "v-text-field" }) - .find("input"); - - await input.trigger("focus"); - await input.setValue("02:00"); - - jest.advanceTimersByTime(1000); - expect(wrapper.emitted("update:time")).toHaveLength(1); - }); - }); }); }); diff --git a/src/components/feature-date-time-picker/TimePicker.vue b/src/components/feature-date-time-picker/TimePicker.vue index 3c86e2da27..3e5c94be77 100644 --- a/src/components/feature-date-time-picker/TimePicker.vue +++ b/src/components/feature-date-time-picker/TimePicker.vue @@ -37,7 +37,6 @@ :data-testid="`time-select-${index}`" class="time-list-item text-left" @click="onSelect(timeOfDay.value)" - :disabled="timeOfDay.disabled" > {{ timeOfDay.value }} @@ -51,7 +50,7 @@ + + diff --git a/src/components/ui-light-box/index.ts b/src/components/ui-light-box/index.ts new file mode 100644 index 0000000000..f077df65fe --- /dev/null +++ b/src/components/ui-light-box/index.ts @@ -0,0 +1,4 @@ +import { LightBoxOptions, useLightBox } from "./LightBox.composable"; +import LightBox from "./LightBox.vue"; + +export { LightBox, LightBoxOptions, useLightBox }; diff --git a/src/composables/i18n.composable.ts b/src/composables/i18n.composable.ts index ea7bb79c22..1b9c9ec12a 100644 --- a/src/composables/i18n.composable.ts +++ b/src/composables/i18n.composable.ts @@ -12,8 +12,11 @@ export const useI18n = () => { return i18n.te(key, locale); }; + const locale = i18n.locale; + return { t, te, + locale, }; }; diff --git a/src/locales/de.json b/src/locales/de.json index 54ea984fe3..2b0ce093bb 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -137,6 +137,7 @@ "common.words.languages.en": "Englisch", "common.words.languages.es": "Spanisch", "common.words.languages.uk": "Ukrainisch", + "components.datePicker.messages.future": "Bitte Datum und Uhrzeit in der Zukunft angeben.", "components.datePicker.validation.required": "Bitte Datum angeben.", "components.timePicker.validation.required": "Bitte Uhrzeit angeben.", "components.timePicker.validation.format": "Bitte Format HH:MM verwenden.", @@ -735,8 +736,8 @@ "pages.administration.school.index.authSystems.copyLink": "Link kopieren", "pages.administration.school.index.authSystems.edit": "{system} bearbeiten", "pages.administration.school.index.authSystems.delete": "{system} löschen", - "pages.administration.classes.index.title": "Klassen verwalten", - "pages.administration.classes.index.add": "Klasse hinzufügen", + "pages.administration.classes.index.title": "Klassen verwalten", + "pages.administration.classes.index.add": "Klasse hinzufügen", "pages.content._id.addToTopic": "Hinzufügen zu", "pages.content._id.collection.selectElements": "Wählen Sie die Elemente, die Sie zum Thema hinzufügen möchten", "pages.content._id.metadata.author": "Autor", diff --git a/src/locales/en.json b/src/locales/en.json index bf558ace10..1e6dadebce 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -136,6 +136,7 @@ "common.words.languages.en": "English", "common.words.languages.es": "Spanish", "common.words.languages.uk": "Ukrainian", + "components.datePicker.messages.future": "Please specify a date and time in the future.", "components.datePicker.validation.required": "Please enter a date.", "components.timePicker.validation.required": "Please enter a time.", "components.timePicker.validation.format": "Please use format HH:MM", @@ -733,8 +734,8 @@ "pages.administration.school.index.authSystems.copyLink": "Copy Link", "pages.administration.school.index.authSystems.edit": "Edit {system}", "pages.administration.school.index.authSystems.delete": "Delete {system}", - "pages.administration.classes.index.title": "Manage classes", - "pages.administration.classes.index.add": "Add class", + "pages.administration.classes.index.title": "Manage classes", + "pages.administration.classes.index.add": "Add class", "pages.content._id.addToTopic": "To be added to", "pages.content._id.collection.selectElements": "Select the items you want to add to the topic", "pages.content._id.metadata.author": "Author", diff --git a/src/locales/es.json b/src/locales/es.json index 896bc1b7a9..9e199408ad 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -136,6 +136,7 @@ "common.words.languages.en": "Inglés", "common.words.languages.es": "Español", "common.words.languages.uk": "Ucranio", + "components.datePicker.messages.future": "Por favor, especifique una fecha y una hora en el futuro.", "components.datePicker.validation.required": "Por favor ingrese una fecha.", "components.timePicker.validation.required": "Por favor ingrese un tiempo.", "components.timePicker.validation.format": "Por favor utilice el formato HH:MM", @@ -722,8 +723,8 @@ "pages.administration.school.index.authSystems.copyLink": "Copiar enlace", "pages.administration.school.index.authSystems.edit": "Editar {system}", "pages.administration.school.index.authSystems.delete": "Eliminar {system}", - "pages.administration.classes.index.title": "Administrar clases", - "pages.administration.classes.index.add": "Agregar clase", + "pages.administration.classes.index.title": "Administrar clases", + "pages.administration.classes.index.add": "Agregar clase", "pages.content._id.addToTopic": "Para ser añadido a", "pages.content._id.collection.selectElements": "Selecciona los elementos que deses añadir al tema", "pages.content._id.metadata.author": "Autor", diff --git a/src/locales/uk.json b/src/locales/uk.json index 3748f37625..0c2741cc2f 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -137,6 +137,7 @@ "common.words.languages.en": "Англійська", "common.words.languages.es": "Іспанська", "common.words.languages.uk": "Українська", + "components.datePicker.messages.future": "Будь ласка, вкажіть дату та час у майбутньому.", "components.datePicker.validation.required": "Будь ласка, введіть дату.", "components.timePicker.validation.required": "Будь ласка, введіть час.", "components.timePicker.validation.format": "Використовуйте формат ГГ:ХХ", @@ -812,8 +813,8 @@ "pages.administration.teachers.new.success": "Викладача успішно створено!", "pages.administration.teachers.new.title": "Додати викладача", "pages.administration.teachers.table.edit.ariaLabel": "Редагування вчителя", - "pages.administration.classes.index.title": "Керувати заняттями", - "pages.administration.classes.index.add": "Додати клас", + "pages.administration.classes.index.title": "Керувати заняттями", + "pages.administration.classes.index.add": "Додати клас", "pages.content._id.addToTopic": "Для додавання в", "pages.content._id.collection.selectElements": "Виберіть елементи, які треба додати до теми", "pages.content._id.metadata.author": "Автор", diff --git a/src/pages/rooms/tools/RoomExternalToolsSection.unit.ts b/src/pages/rooms/tools/RoomExternalToolsSection.unit.ts index d23ae92ab2..015ec19160 100644 --- a/src/pages/rooms/tools/RoomExternalToolsSection.unit.ts +++ b/src/pages/rooms/tools/RoomExternalToolsSection.unit.ts @@ -1,7 +1,6 @@ import { createMock, DeepMocked } from "@golevelup/ts-jest"; import { mount, MountOptions, Wrapper } from "@vue/test-utils"; import createComponentMocks from "@@/tests/test-utils/componentMocks"; -import { AxiosError } from "axios"; import Vue from "vue"; import { AUTH_MODULE_KEY, @@ -13,27 +12,34 @@ import { import { businessErrorFactory, externalToolDisplayDataFactory, + toolLaunchRequestResponseFactory, } from "@@/tests/test-utils/factory"; import { ExternalToolDisplayData, + ToolConfigurationStatus, ToolContextType, } from "@/store/external-tool"; import AuthModule from "@/store/auth"; import ContextExternalToolsModule from "@/store/context-external-tools"; -import ExternalToolsModule from "@/store/external-tools"; -import { BusinessError } from "@/store/types/commons"; import { createModuleMocks } from "@/utils/mock-store-module"; import VueRouter from "vue-router"; import * as routerComposables from "vue-router/composables"; -import EnvConfigModule from "../../../store/env-config"; +import EnvConfigModule from "@/store/env-config"; import RoomExternalToolsSection from "./RoomExternalToolsSection.vue"; +import ExternalToolsModule from "@/store/external-tools"; +import flushPromises from "flush-promises"; +import { BusinessError } from "@/store/types/commons"; +import { AxiosError } from "axios"; describe("RoomExternalToolsSection", () => { let router: DeepMocked; const getWrapper = ( - props: { tools: ExternalToolDisplayData[]; roomId: string }, - externalToolsModuleGetter?: Partial + props: { + tools: ExternalToolDisplayData[]; + roomId: string; + }, + externalToolsModuleMock = createModuleMocks(ExternalToolsModule) ) => { document.body.setAttribute("data-app", "true"); @@ -41,15 +47,6 @@ describe("RoomExternalToolsSection", () => { ContextExternalToolsModule ); - const externalToolsModule = createModuleMocks(ExternalToolsModule, { - getBusinessError: { - statusCode: "", - message: "", - error: undefined, - }, - ...externalToolsModuleGetter, - }); - const authModule = createModuleMocks(AuthModule, { getUserPermissions: ["CONTEXT_TOOL_ADMIN"], getUserRoles: ["teacher"], @@ -77,7 +74,7 @@ describe("RoomExternalToolsSection", () => { }, [CONTEXT_EXTERNAL_TOOLS_MODULE_KEY.valueOf()]: contextExternalToolsModule, - [EXTERNAL_TOOLS_MODULE_KEY.valueOf()]: externalToolsModule, + [EXTERNAL_TOOLS_MODULE_KEY.valueOf()]: externalToolsModuleMock, [AUTH_MODULE_KEY.valueOf()]: authModule, [ENV_CONFIG_MODULE_KEY.valueOf()]: envConfigModule, }, @@ -87,7 +84,6 @@ describe("RoomExternalToolsSection", () => { return { wrapper, contextExternalToolsModule, - externalToolsModule, authModule, }; }; @@ -256,135 +252,132 @@ describe("RoomExternalToolsSection", () => { }); describe("when clicking on a tool", () => { - const setup = () => { - const tool: ExternalToolDisplayData = - externalToolDisplayDataFactory.build(); - - const { wrapper, externalToolsModule } = getWrapper({ - tools: [tool], - roomId: "roomId", - }); - - return { - wrapper, - externalToolsModule, - tool, + describe("when the tool has missing auto parameters and loading requestData throw an error", () => { + const setup = async () => { + const tool: ExternalToolDisplayData = + externalToolDisplayDataFactory.build({ + status: ToolConfigurationStatus.Latest, + }); + + const error: BusinessError = businessErrorFactory.build({ + error: new AxiosError("this error is expected"), + message: "MISSING_TOOL_PARAMETER_VALUE some value is missing", + }); + + const externalToolsModule = createModuleMocks(ExternalToolsModule); + externalToolsModule.loadToolLaunchData.mockRejectedValueOnce(error); + + const { wrapper } = getWrapper( + { tools: [tool], roomId: "roomId" }, + externalToolsModule + ); + + await flushPromises(); + + return { + wrapper, + tool, + }; }; - }; - it("should fetch the launch data", async () => { - const { wrapper, externalToolsModule, tool } = setup(); + it("should open up the error dialog", async () => { + const { wrapper } = await setup(); - const card = wrapper.findComponent({ - name: "room-external-tool-card", - }); + const card = wrapper.findComponent({ + name: "room-external-tool-card", + }); - await card.trigger("click"); + await card.trigger("click"); - expect(externalToolsModule.loadToolLaunchData).toHaveBeenCalledWith( - tool.contextExternalToolId - ); + const dialog = wrapper.find('[data-testId="error-dialog"]'); + expect(dialog.exists()).toBeTruthy(); + expect(wrapper.vm.isErrorDialogOpen).toBeTruthy(); + }); }); - }); - - describe("when clicking on a tool which has missing auto parameters", () => { - const setup = () => { - const tool: ExternalToolDisplayData = - externalToolDisplayDataFactory.build(); - const error: BusinessError = businessErrorFactory.build({ - error: new AxiosError("this error is expected"), - message: "MISSING_TOOL_PARAMETER_VALUE some value is missing", - }); + describe("when the tool is launchable", () => { + const setup = async () => { + const tool: ExternalToolDisplayData = + externalToolDisplayDataFactory.build({ + status: ToolConfigurationStatus.Latest, + }); + + const externalToolsModule = createModuleMocks(ExternalToolsModule); + externalToolsModule.loadToolLaunchData.mockResolvedValue( + toolLaunchRequestResponseFactory.build() + ); + + const { wrapper } = getWrapper( + { + tools: [tool], + roomId: "roomId", + }, + externalToolsModule + ); - const { wrapper } = getWrapper( - { tools: [tool], roomId: "roomId" }, - { - getBusinessError: error, - } - ); + await flushPromises(); - return { - wrapper, - tool, + return { + wrapper, + }; }; - }; - it("should display a dialog", async () => { - const { wrapper, tool } = setup(); + it("should not open up the error dialog", async () => { + const { wrapper } = await setup(); - const card = wrapper.findComponent({ - name: "room-external-tool-card", - }); - await card.vm.$emit("click", tool); + const card = wrapper.findComponent({ + name: "room-external-tool-card", + }); - const dialog = wrapper.find('[data-testId="error-dialog"]'); + await card.trigger("click"); - expect(dialog.exists()).toBeTruthy(); - expect(wrapper.vm.isErrorDialogOpen).toBeTruthy(); - }); - }); - - describe("when click on a outdated tool", () => { - const setup = () => { - const tool: ExternalToolDisplayData = - externalToolDisplayDataFactory.build(); + wrapper.find('[data-testId="error-dialog"]'); - const error: BusinessError = businessErrorFactory.build({ - error: new AxiosError("this error is expected"), - message: "TOOL_STATUS_OUTDATED this tool is outdated", + expect(wrapper.vm.isErrorDialogOpen).toBeFalsy(); }); + }); - const { wrapper } = getWrapper( - { tools: [tool], roomId: "roomId" }, - { - getBusinessError: error, - } - ); - - return { - wrapper, - tool, - }; - }; - - it("should display a dialog", async () => { - const { wrapper, tool } = setup(); - - const card = wrapper.findComponent({ - name: "room-external-tool-card", - }); - await card.vm.$emit("click", tool); + describe("and tool is not launchable because it is outdated", () => { + const setup = async () => { + const tool: ExternalToolDisplayData = + externalToolDisplayDataFactory.build({ + status: ToolConfigurationStatus.Outdated, + }); + + const externalToolsModule = createModuleMocks(ExternalToolsModule); + externalToolsModule.loadToolLaunchData.mockResolvedValueOnce( + toolLaunchRequestResponseFactory.build() + ); + + const { wrapper } = getWrapper( + { + tools: [tool], + roomId: "roomId", + }, + externalToolsModule + ); - const dialog = wrapper.find('[data-testId="error-dialog"]'); + await flushPromises(); - expect(dialog.exists()).toBeTruthy(); - expect(wrapper.vm.isErrorDialogOpen).toBeTruthy(); - }); - }); + return { + wrapper, + }; + }; - describe("when click on a latest tool", () => { - const setup = () => { - const tool: ExternalToolDisplayData = - externalToolDisplayDataFactory.build(); + it("should open up the error dialog", async () => { + const { wrapper } = await setup(); - const { wrapper } = getWrapper({ tools: [tool], roomId: "roomId" }); + const card = wrapper.findComponent({ + name: "room-external-tool-card", + }); - return { - wrapper, - tool, - }; - }; + await card.trigger("click"); - it("should not display a dialog", async () => { - const { wrapper, tool } = setup(); + const dialog = wrapper.find('[data-testId="error-dialog"]'); - const card = wrapper.findComponent({ - name: "room-external-tool-card", + expect(dialog.exists()).toBeTruthy(); + expect(wrapper.vm.isErrorDialogOpen).toBeTruthy(); }); - await card.vm.$emit("click", tool); - - expect(wrapper.vm.isErrorDialogOpen).toBeFalsy(); }); }); }); diff --git a/src/pages/rooms/tools/RoomExternalToolsSection.vue b/src/pages/rooms/tools/RoomExternalToolsSection.vue index a8cb87f66b..b643685cd0 100644 --- a/src/pages/rooms/tools/RoomExternalToolsSection.vue +++ b/src/pages/rooms/tools/RoomExternalToolsSection.vue @@ -10,6 +10,7 @@ @delete="onOpenDeleteDialog" @edit="onEditTool" @click="onClickTool" + @error="onError" :data-testid="`external-tool-card-${index}`" > @@ -85,19 +86,12 @@