Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EW-694 Common Cartridge Course Import #3010

Merged
merged 43 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
4bf5766
EW-694 creating upload dialog
psachmann Jan 16, 2024
c6e368e
EW-694 adding feature flag
psachmann Jan 17, 2024
f914c79
EW-694 improving upload modal
psachmann Jan 18, 2024
dbe8dbf
Merge branch 'main' into EW-694
psachmann Jan 19, 2024
8a5fd11
EW-694 adding translations and tests
psachmann Jan 19, 2024
5df08d5
EW-694 little changes
psachmann Jan 22, 2024
83f151f
EW-694 removing waiting time
psachmann Jan 22, 2024
2423717
EW-694 adding loading animation
psachmann Jan 25, 2024
fd9c2a3
EW-694 adding alert after upload
psachmann Jan 25, 2024
caf5e6c
EW-694 working on unit tests
psachmann Jan 25, 2024
d72d000
EW-694 fixing unit tests
psachmann Jan 26, 2024
7e25512
Merge branch 'main' into EW-694
psachmann Jan 26, 2024
46e0921
EW-694 fixing tests
psachmann Jan 26, 2024
924c3d2
EW-694 working on ui improvements
psachmann Jan 26, 2024
0e876cb
Merge branch 'main' into EW-694
psachmann Jan 29, 2024
b4f549d
Merge branch 'main' into EW-694
psachmann Jan 30, 2024
b6b2edc
Merge branch 'main' into EW-694
psachmann Jan 31, 2024
b1ba6d9
EW-694 working on UX requirements
psachmann Feb 1, 2024
1b70109
Merge branch 'main' into EW-694
psachmann Feb 1, 2024
dd9c0f7
EW-694 changing wording
psachmann Feb 6, 2024
f69cfa6
EW-694 improving ui
psachmann Feb 6, 2024
aeb80be
Merge branch 'main' into EW-694
psachmann Feb 26, 2024
ab39d69
EW-694 fixing unit test
psachmann Feb 26, 2024
9c30d6e
EW-694 fixing unit test
psachmann Feb 26, 2024
7b00ee8
EW-694 fixing race condition
psachmann Feb 27, 2024
03633d0
Merge branch 'main' into EW-694
psachmann Feb 28, 2024
8fc7817
EW-694 restoring locals
psachmann Feb 28, 2024
3f11b45
Merge branch 'main' into EW-694
psachmann Feb 28, 2024
829594e
Merge branch 'main' into EW-694
psachmann Feb 28, 2024
4aa4a4c
EW-694 fixing compile errors
psachmann Feb 28, 2024
1f8e0ea
EW-694 adding separate state module for cc import
psachmann Mar 4, 2024
f5621a3
EW-694 using computed props for v-model with get and set
psachmann Mar 5, 2024
0f7f4f2
EW-694 translation is now working
psachmann Mar 5, 2024
85eabde
EW-694 renamed injection key
psachmann Mar 6, 2024
8bccf36
EW-694 renamed method
psachmann Mar 6, 2024
3d92276
EW-694 can test the component, but it is a bit dirty
psachmann Mar 6, 2024
abf1077
EW-694 adding some unit tests
psachmann Mar 7, 2024
7806630
EW-694 unit tests are now working
psachmann Mar 7, 2024
3f14375
Update src/locales/en.ts
psachmann Mar 13, 2024
d846f72
EW-694 removed useless test
psachmann Mar 13, 2024
d5356f9
Merge branch 'main' into EW-694
psachmann Mar 13, 2024
43e2360
EW-694 updating api.ts
psachmann Mar 13, 2024
7da6d3c
EW-694 fixing failing unit tests
psachmann Mar 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions src/components/molecules/CommonCartridgeImportModal.unit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import RoomsModule from "@/store/rooms";
import CommonCartridgeImportModal from "./CommonCartridgeImportModal.vue";
import { mount } from "@vue/test-utils";
import LoadingStateModule from "@/store/loading-state";
import NotifierModule from "@/store/notifier";
import {
COMMON_CARTRIDGE_IMPORT_MODULE_KEY,
LOADING_STATE_MODULE_KEY,
NOTIFIER_MODULE_KEY,
ROOMS_MODULE_KEY,
} from "@/utils/inject";
import { createModuleMocks } from "@/utils/mock-store-module";
import {
createTestingI18n,
createTestingVuetify,
} from "@@/tests/test-utils/setup";
import CommonCartridgeImportModule from "@/store/common-cartridge-import";

describe("@/components/molecules/CommonCartridgeImportModal", () => {
const setupWrapper = (getters: Partial<CommonCartridgeImportModule>) => {
document.body.setAttribute("data-app", "true");

const notifierModuleMock = createModuleMocks(NotifierModule);
const roomsModuleMock = createModuleMocks(RoomsModule, {
getAllElements: [],
});
const commonCartridgeImportModule = createModuleMocks(
CommonCartridgeImportModule,
getters
);

const wrapper = mount(CommonCartridgeImportModal, {
global: {
plugins: [createTestingVuetify(), createTestingI18n()],
provide: {
[LOADING_STATE_MODULE_KEY.valueOf()]:
createModuleMocks(LoadingStateModule),
[NOTIFIER_MODULE_KEY.valueOf()]: notifierModuleMock,
[ROOMS_MODULE_KEY.valueOf()]: roomsModuleMock,
[COMMON_CARTRIDGE_IMPORT_MODULE_KEY.valueOf()]:
commonCartridgeImportModule,
},
},
});

return {
wrapper,
roomsModuleMock,
notifierModuleMock,
commonCartridgeImportModule,
};
};

describe("when dialog is open", () => {
const setup = () => setupWrapper({ isOpen: true });

it("should contain disabled confirm button", async () => {
const { wrapper } = setup();

const confirmBtn = wrapper.findComponent(
"[data-testId='dialog-confirm-btn']"
) as any;

expect(confirmBtn.exists()).toBe(true);
expect(confirmBtn.isDisabled()).toBe(true);
});

it("should contain enabled cancel button", async () => {
const { wrapper } = setup();

const cancelBtn = wrapper.findComponent(
"[data-testid='dialog-cancel-btn']"
) as any;

expect(cancelBtn.exists()).toBe(true);
expect(cancelBtn.isDisabled()).toBe(false);
});

it("should contain file input", () => {
const { wrapper } = setup();

const fileInput = wrapper.findComponent(
"[data-testid='dialog-file-input']"
) as any;

expect(fileInput.exists()).toBe(true);
});
});

describe("when a file is selected", () => {
const setup = () =>
setupWrapper({
isOpen: true,
file: new File([], "file"),
});

it("should enable confirm button", () => {
const { wrapper } = setup();

const confirmBtn = wrapper.findComponent(
"[data-testId='dialog-confirm-btn']"
) as any;

expect(confirmBtn.isDisabled()).toBe(false);
});
});

describe("when file upload is successful", () => {
const setup = () =>
setupWrapper({
file: new File([], "file"),
isOpen: true,
isSuccess: true,
});

it("should show success message", async () => {
const { wrapper, roomsModuleMock, notifierModuleMock } = setup();
const confirmBtn = wrapper.findComponent(
"[data-testId='dialog-confirm-btn']"
);

await confirmBtn.trigger("click");

expect(roomsModuleMock.fetch).toHaveBeenCalledTimes(1);
expect(roomsModuleMock.fetchAllElements).toHaveBeenCalledTimes(1);
expect(notifierModuleMock.show).toHaveBeenCalledWith({
status: "success",
text: expect.any(String),
autoClose: true,
});
});
});

describe("when file upload is unsuccessful", () => {
const setup = () =>
setupWrapper({
file: new File([], "file"),
isOpen: true,
isSuccess: false,
});

it("should show error message", async () => {
const { wrapper, notifierModuleMock, roomsModuleMock } = setup();
const confirmBtn = wrapper.findComponent(
"[data-testId='dialog-confirm-btn']"
);

await confirmBtn.trigger("click");

expect(roomsModuleMock.fetch).not.toHaveBeenCalled();
expect(roomsModuleMock.fetchAllElements).not.toHaveBeenCalled();
expect(notifierModuleMock.show).toHaveBeenCalledWith({
status: "error",
text: expect.any(String),
autoClose: true,
});
});
});

describe("when dialog is closed", () => {
const setup = () => setupWrapper({ isOpen: true });

it("should reset the state", () => {
const { wrapper, commonCartridgeImportModule } = setup();
const cancelBtn = wrapper.findComponent(
"[data-testid='dialog-cancel-btn']"
);

cancelBtn.trigger("click");

expect(commonCartridgeImportModule.setIsOpen).toHaveBeenCalledWith(false);
});
});
});
137 changes: 137 additions & 0 deletions src/components/molecules/CommonCartridgeImportModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<template>
<v-dialog
ref="commonCartridgeImportModal"
v-model="isOpen"
:max-width="props.maxWidth"
@click:outside="onCancel()"
@keydown.esc="onCancel()"
data-testid="common-cartridge-import-modal"
>
<v-card :ripple="false">
<v-card-title>
<div ref="textTitle" class="text-h4 my-2 text-break">
{{ $t("pages.rooms.ccImportCourse.title") }}
</div>
</v-card-title>
<v-card-text class="text--primary">
<v-file-input
v-model="file"
:label="$t('pages.rooms.ccImportCourse.fileInputLabel')"
:prepend-icon="mdiTrayArrowUp"
accept=".imscc, .zip"
clearable
show-size
data-testid="dialog-file-input"
/>
</v-card-text>
<v-card-actions>
<v-spacer />
<div class="button-section">
<v-btn data-testid="dialog-cancel-btn" depressed @click="onCancel">
{{ $t("common.labels.close") }}
</v-btn>
</div>
<div class="button-section">
<v-btn
v-bind:disabled="importButtonDisabled"
v-on:click="onConfirm"
color="primary"
data-testid="dialog-confirm-btn"
>
{{ $t("pages.rooms.ccImportCourse.confirm") }}
</v-btn>
</div>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<script setup lang="ts">
import { computed, defineProps, withDefaults } from "vue";
import { mdiTrayArrowUp } from "@mdi/js";
import { useI18n } from "vue-i18n";
import {
COMMON_CARTRIDGE_IMPORT_MODULE_KEY,
LOADING_STATE_MODULE_KEY,
NOTIFIER_MODULE_KEY,
ROOMS_MODULE_KEY,
injectStrict,
} from "@/utils/inject";
const i18n = useI18n();
const roomsModule = injectStrict(ROOMS_MODULE_KEY);
const loadingStateModule = injectStrict(LOADING_STATE_MODULE_KEY);
const notifierModule = injectStrict(NOTIFIER_MODULE_KEY);
const commonCartridgeImportModule = injectStrict(
COMMON_CARTRIDGE_IMPORT_MODULE_KEY
);
const props = withDefaults(
defineProps<{
maxWidth?: number;
}>(),
{
maxWidth: 480,
}
);
const file = computed<File[]>({
get: () =>
commonCartridgeImportModule.file ? [commonCartridgeImportModule.file] : [],
set: (value: File[]) => commonCartridgeImportModule.setFile(value[0]),
});
const isOpen = computed<boolean>({
get: () => commonCartridgeImportModule.isOpen,
set: (value: boolean) => commonCartridgeImportModule.setIsOpen(value),
});
const importButtonDisabled = computed(() => {
return file.value.length === 0;
});
function onCancel(): void {
file.value = [];
commonCartridgeImportModule.setIsOpen(false);
}
async function onConfirm(): Promise<void> {
const [selectedFile] = file.value;
commonCartridgeImportModule.setIsOpen(false);
loadingStateModule.open({
text: i18n.t("pages.rooms.ccImportCourse.loading"),
});
await commonCartridgeImportModule.importCommonCartridgeFile(selectedFile);
if (commonCartridgeImportModule.isSuccess) {
await Promise.allSettled([
roomsModule.fetch(),
roomsModule.fetchAllElements(),
]);
loadingStateModule.close();
const title = roomsModule.getAllElements[0]?.title;
notifierModule.show({
status: "success",
text: i18n.t("pages.rooms.ccImportCourse.success", { name: title }),
autoClose: true,
});
} else {
loadingStateModule.close();
notifierModule.show({
status: "error",
text: i18n.t("pages.rooms.ccImportCourse.error"),
autoClose: true,
});
}
file.value = [];
}
</script>

<style lang="scss" scoped>
.button-section {
margin-bottom: calc(var(--space-base-vuetify) * 2);
}
.button-section > button {
margin-left: calc(var(--space-base-vuetify) * 2);
}
</style>
5 changes: 4 additions & 1 deletion src/components/templates/DefaultWireframe.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
:icon="action.icon"
:href="action.href"
:to="action.to"
@click="action.customEvent"
@click="$emit('onFabItemClick', action.customEvent)"
>{{ action.label }}</speed-dial-menu-action
>
</template>
Expand Down Expand Up @@ -100,6 +100,9 @@ export default defineComponent({
default: null,
},
},
emits: {
onFabItemClick: (event: string) => (event ? true : false),
},
computed: {
showBorder(): boolean {
return !!(this.headline || this.$slots.header);
Expand Down
14 changes: 14 additions & 0 deletions src/components/templates/RoomWrapper.unit.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { authModule, roomsModule } from "@/store";
import { ComponentMountingOptions, mount } from "@vue/test-utils";
import RoomWrapper from "./RoomWrapper.vue";
import { createModuleMocks } from "@/utils/mock-store-module";
import setupStores from "@@/tests/test-utils/setupStores";
import RoomsModule from "@/store/rooms";
import AuthModule from "@/store/auth";
import EnvConfigModule from "@/store/env-config";
import {
LOADING_STATE_MODULE_KEY,
NOTIFIER_MODULE_KEY,
ROOMS_MODULE_KEY,
} from "@/utils/inject";
import LoadingStateModule from "@/store/loading-state";
import NotifierModule from "@/store/notifier";
import {
createTestingI18n,
createTestingVuetify,
Expand All @@ -27,6 +35,12 @@ const getWrapper = (
},
},
...options,
provide: {
[LOADING_STATE_MODULE_KEY.valueOf()]:
createModuleMocks(LoadingStateModule),
[NOTIFIER_MODULE_KEY.valueOf()]: createModuleMocks(NotifierModule),
[ROOMS_MODULE_KEY.valueOf()]: createModuleMocks(RoomsModule),
},
});
};

Expand Down
Loading
Loading