diff --git a/src/locales/de.json b/src/locales/de.json
index 330047a4df..be2536bc98 100644
--- a/src/locales/de.json
+++ b/src/locales/de.json
@@ -744,6 +744,12 @@
"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.deleteDialog.title": "Klasse löschen?",
+ "pages.administration.classes.deleteDialog.content": "Möchten Sie wirklich die Klasse \"{itemName}\" löschen?",
+ "pages.administration.classes.manage": "Klasse verwalten",
+ "pages.administration.classes.edit": "Klasse bearbeiten",
+ "pages.administration.classes.delete": "Klasse löschen",
+ "pages.administration.classes.createSuccessor": "Klasse in das nächste Schuljahr versetzen",
"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 f424329836..9fc586ff11 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -742,6 +742,12 @@
"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.deleteDialog.title": "Delete class?",
+ "pages.administration.classes.deleteDialog.content": "Are you sure you want to delete class \"{itemName}\"?",
+ "pages.administration.classes.manage": "Manage class",
+ "pages.administration.classes.edit": "Edit class",
+ "pages.administration.classes.delete": "Delete class",
+ "pages.administration.classes.createSuccessor": "Move class to the next school year",
"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 aa345d84c8..e6a9800c9b 100644
--- a/src/locales/es.json
+++ b/src/locales/es.json
@@ -731,6 +731,12 @@
"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.deleteDialog.title": "¿Eliminar clase?",
+ "pages.administration.classes.deleteDialog.content": "¿Está seguro de que desea eliminar la clase \"{itemName}\"?",
+ "pages.administration.classes.manage": "Administrar clase",
+ "pages.administration.classes.edit": "Editar clase",
+ "pages.administration.classes.delete": "Eliminar clase",
+ "pages.administration.classes.createSuccessor": "Mover la clase al próximo año escolar",
"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 732fa4ef9f..b34d3769f8 100644
--- a/src/locales/uk.json
+++ b/src/locales/uk.json
@@ -822,6 +822,12 @@
"pages.administration.teachers.table.edit.ariaLabel": "Редагування вчителя",
"pages.administration.classes.index.title": "Керувати заняттями",
"pages.administration.classes.index.add": "Додати клас",
+ "pages.administration.classes.deleteDialog.title": "Видалити клас?",
+ "pages.administration.classes.deleteDialog.content": "Ви впевнені, що хочете видалити клас \"{itemName}\"?",
+ "pages.administration.classes.manage": "Керувати класом",
+ "pages.administration.classes.edit": "Редагувати клас",
+ "pages.administration.classes.delete": "Видалити клас",
+ "pages.administration.classes.createSuccessor": "Перенести клас на наступний навчальний рік",
"pages.content._id.addToTopic": "Для додавання в",
"pages.content._id.collection.selectElements": "Виберіть елементи, які треба додати до теми",
"pages.content._id.metadata.author": "Автор",
diff --git a/src/pages/administration/ClassOverview.page.unit.ts b/src/pages/administration/ClassOverview.page.unit.ts
index 5b9a24a22c..8a050f02ab 100644
--- a/src/pages/administration/ClassOverview.page.unit.ts
+++ b/src/pages/administration/ClassOverview.page.unit.ts
@@ -1,20 +1,29 @@
+import AuthModule from "@/store/auth";
import GroupModule from "@/store/group";
+import { ClassInfo, ClassRootType } from "@/store/types/class-info";
+import { Pagination } from "@/store/types/commons";
+import { SortOrder } from "@/store/types/sort-order.enum";
+import { AUTH_MODULE_KEY, GROUP_MODULE_KEY, I18N_KEY } from "@/utils/inject";
import { createModuleMocks } from "@/utils/mock-store-module";
import { classInfoFactory, i18nMock } from "@@/tests/test-utils";
-import { MountOptions, Wrapper, mount } from "@vue/test-utils";
-import ClassOverview from "./ClassOverview.page.vue";
-import { GROUP_MODULE_KEY, I18N_KEY } from "@/utils/inject";
import createComponentMocks from "@@/tests/test-utils/componentMocks";
+import { mount, MountOptions, Wrapper } from "@vue/test-utils";
import Vue from "vue";
-import { SortOrder } from "@/store/types/sort-order.enum";
-import { Pagination } from "@/store/types/commons";
+import ClassOverview from "./ClassOverview.page.vue";
describe("ClassOverview", () => {
const getWrapper = (getters: Partial = {}) => {
document.body.setAttribute("data-app", "true");
const groupModule = createModuleMocks(GroupModule, {
- getClasses: [classInfoFactory.build()],
+ getClasses: [
+ classInfoFactory.build(),
+ classInfoFactory.build({
+ externalSourceName: undefined,
+ type: ClassRootType.Class,
+ isUpgradable: true,
+ }),
+ ],
getPagination: {
limit: 10,
skip: 0,
@@ -23,6 +32,10 @@ describe("ClassOverview", () => {
...getters,
});
+ const authModule = createModuleMocks(AuthModule, {
+ getUserPermissions: ["CLASS_EDIT".toLowerCase()],
+ });
+
const wrapper: Wrapper = mount(ClassOverview as MountOptions, {
...createComponentMocks({
i18n: true,
@@ -30,6 +43,7 @@ describe("ClassOverview", () => {
provide: {
[I18N_KEY.valueOf()]: i18nMock,
[GROUP_MODULE_KEY.valueOf()]: groupModule,
+ [AUTH_MODULE_KEY.valueOf()]: authModule,
},
});
@@ -70,6 +84,37 @@ describe("ClassOverview", () => {
});
});
+ describe("when there are classes or groups to display", () => {
+ const setup = () => {
+ const classes: ClassInfo[] = [
+ classInfoFactory.build(),
+ classInfoFactory.build({
+ externalSourceName: undefined,
+ type: ClassRootType.Class,
+ isUpgradable: true,
+ }),
+ ];
+
+ const { wrapper, groupModule } = getWrapper({
+ getClasses: classes,
+ });
+
+ return {
+ classes,
+ wrapper,
+ groupModule,
+ };
+ };
+
+ it("should display the entries in the table", async () => {
+ const { classes, wrapper } = setup();
+
+ const table = wrapper.find('[data-testid="admin-class-table"]');
+
+ expect(table.props("items")).toEqual(classes);
+ });
+ });
+
describe("onUpdateSortBy", () => {
describe("when changing the sortBy", () => {
const setup = () => {
@@ -87,9 +132,10 @@ describe("ClassOverview", () => {
it("should call store to change sort by", async () => {
const { sortBy, wrapper, groupModule } = setup();
- await wrapper
+ wrapper
.find('[data-testid="admin-class-table"]')
.vm.$emit("update:sort-by", sortBy);
+ await Vue.nextTick();
expect(groupModule.loadClassesForSchool).toHaveBeenCalled();
expect(groupModule.setSortBy).toHaveBeenCalledWith(sortBy);
@@ -114,9 +160,10 @@ describe("ClassOverview", () => {
it("should call store to change sort order", async () => {
const { sortOrder, wrapper, groupModule } = setup();
- await wrapper
+ wrapper
.find('[data-testid="admin-class-table"]')
.vm.$emit("update:sort-desc", sortOrder);
+ await Vue.nextTick();
expect(groupModule.loadClassesForSchool).toHaveBeenCalled();
expect(groupModule.setSortOrder).toHaveBeenCalledWith(SortOrder.DESC);
@@ -154,9 +201,10 @@ describe("ClassOverview", () => {
it("should call store to change the limit in pagination", async () => {
const { itemsPerPage, wrapper, groupModule, pagination } = setup();
- await wrapper
+ wrapper
.find('[data-testid="admin-class-table"]')
.vm.$emit("update:items-per-page", itemsPerPage);
+ await Vue.nextTick();
expect(groupModule.loadClassesForSchool).toHaveBeenCalled();
expect(groupModule.setPagination).toHaveBeenCalledWith({
@@ -192,9 +240,10 @@ describe("ClassOverview", () => {
it("should call store to update current page", async () => {
const { page, wrapper, groupModule, pagination } = setup();
- await wrapper
+ wrapper
.find('[data-testid="admin-class-table"]')
.vm.$emit("update:page", page);
+ await Vue.nextTick();
expect(groupModule.loadClassesForSchool).toHaveBeenCalled();
expect(groupModule.setPage).toHaveBeenCalledWith(page);
@@ -202,4 +251,242 @@ describe("ClassOverview", () => {
});
});
});
+
+ describe("action buttons", () => {
+ describe("when legacy classes are available", () => {
+ const setup = () => {
+ const { wrapper } = getWrapper();
+
+ return {
+ wrapper,
+ };
+ };
+
+ it("should render 4 buttons", () => {
+ const { wrapper } = setup();
+
+ const manageBtn = wrapper.find(
+ '[data-testid="class-table-manage-btn"]'
+ );
+
+ const editBtn = wrapper.find('[data-testid="class-table-edit-btn"]');
+
+ const deleteBtn = wrapper.find(
+ '[data-testid="class-table-delete-btn"]'
+ );
+
+ const successorBtn = wrapper.find(
+ '[data-testid="class-table-successor-btn"]'
+ );
+
+ expect(manageBtn.exists()).toBeTruthy();
+ expect(editBtn.exists()).toBeTruthy();
+ expect(deleteBtn.exists()).toBeTruthy();
+ expect(successorBtn.exists()).toBeTruthy();
+ });
+ });
+
+ describe("when no classes are available", () => {
+ const setup = () => {
+ const { wrapper } = getWrapper({
+ getClasses: [classInfoFactory.build()],
+ });
+
+ return {
+ wrapper,
+ };
+ };
+
+ it("should not render any buttons", () => {
+ const { wrapper } = setup();
+
+ const manageBtn = wrapper.find(
+ '[data-testid="class-table-manage-btn"]'
+ );
+
+ const editBtn = wrapper.find('[data-testid="class-table-edit-btn"]');
+
+ const deleteBtn = wrapper.find(
+ '[data-testid="class-table-delete-btn"]'
+ );
+
+ const successorBtn = wrapper.find(
+ '[data-testid="class-table-successor-btn"]'
+ );
+
+ expect(manageBtn.exists()).toBeFalsy();
+ expect(editBtn.exists()).toBeFalsy();
+ expect(deleteBtn.exists()).toBeFalsy();
+ expect(successorBtn.exists()).toBeFalsy();
+ });
+ });
+
+ describe("when clicking on the manage class button", () => {
+ const setup = () => {
+ const { wrapper, groupModule } = getWrapper();
+
+ const classId: string = groupModule.getClasses[1].id;
+
+ return {
+ wrapper,
+ classId,
+ };
+ };
+
+ it("should redirect to legacy class manage page", async () => {
+ const { wrapper, classId } = setup();
+
+ const manageBtn = wrapper.find(
+ '[data-testid="class-table-manage-btn"]'
+ );
+
+ expect(manageBtn.attributes().href).toStrictEqual(
+ `/administration/classes/${classId}/manage`
+ );
+ });
+ });
+
+ describe("when clicking on the edit class button", () => {
+ const setup = () => {
+ const { wrapper, groupModule } = getWrapper();
+
+ const classId: string = groupModule.getClasses[1].id;
+
+ return {
+ wrapper,
+ classId,
+ };
+ };
+
+ it("should redirect to legacy class edit page", async () => {
+ const { wrapper, classId } = setup();
+
+ const editBtn = wrapper.find('[data-testid="class-table-edit-btn"]');
+
+ expect(editBtn.attributes().href).toStrictEqual(
+ `/administration/classes/${classId}/edit`
+ );
+ });
+ });
+
+ describe("when class is upgradable", () => {
+ describe("when clicking on the upgrade class button", () => {
+ const setup = () => {
+ const { wrapper, groupModule } = getWrapper();
+
+ const classId: string = groupModule.getClasses[1].id;
+
+ return {
+ wrapper,
+ classId,
+ };
+ };
+
+ it("should redirect to legacy class upgrade page", async () => {
+ const { wrapper, classId } = setup();
+
+ const successorBtn = wrapper.find(
+ '[data-testid="class-table-successor-btn"]'
+ );
+
+ expect(successorBtn.attributes().href).toStrictEqual(
+ `/administration/classes/${classId}/createSuccessor`
+ );
+ });
+ });
+ });
+
+ describe("when class is not upgradable", () => {
+ const setup = () => {
+ const { wrapper } = getWrapper({
+ getClasses: [
+ classInfoFactory.build({
+ externalSourceName: undefined,
+ type: ClassRootType.Class,
+ isUpgradable: false,
+ }),
+ ],
+ });
+
+ return {
+ wrapper,
+ };
+ };
+
+ it("should display the upgrade button as disabled", () => {
+ const { wrapper } = setup();
+
+ const successorBtn = wrapper.find(
+ '[data-testid="class-table-successor-btn"]'
+ );
+
+ expect(successorBtn.props("disabled")).toEqual(true);
+ });
+ });
+
+ describe("when clicking on the delete class button", () => {
+ const setup = () => {
+ const { wrapper } = getWrapper();
+
+ return {
+ wrapper,
+ };
+ };
+
+ it("should open the delete dialog", async () => {
+ const { wrapper } = setup();
+
+ await wrapper
+ .find('[data-testid="class-table-delete-btn"]')
+ .trigger("click");
+
+ const dialog = wrapper.find('[data-testid="delete-dialog"]');
+
+ expect(dialog.props("isOpen")).toBeTruthy();
+ });
+ });
+
+ describe("when delete dialog is open", () => {
+ const setup = () => {
+ const { wrapper, groupModule } = getWrapper();
+
+ return {
+ wrapper,
+ groupModule,
+ };
+ };
+
+ describe("when clicking on cancel button", () => {
+ it("should not delete class", async () => {
+ const { wrapper, groupModule } = setup();
+
+ await wrapper
+ .find('[data-testid="class-table-delete-btn"]')
+ .trigger("click");
+
+ const dialog = wrapper.find('[data-testid="delete-dialog"]');
+
+ await dialog.find('[data-testid="dialog-cancel"').trigger("click");
+
+ expect(groupModule.deleteClass).not.toHaveBeenCalled();
+ });
+ });
+
+ describe("when clicking on confirm button", () => {
+ it("should delete class", async () => {
+ const { wrapper, groupModule } = setup();
+
+ await wrapper
+ .find('[data-testid="class-table-delete-btn"]')
+ .trigger("click");
+
+ const dialog = wrapper.find('[data-testid="delete-dialog"]');
+
+ await dialog.find('[data-testid="dialog-confirm"').trigger("click");
+
+ expect(groupModule.deleteClass).toHaveBeenCalled();
+ });
+ });
+ });
+ });
});
diff --git a/src/pages/administration/ClassOverview.page.vue b/src/pages/administration/ClassOverview.page.vue
index 55114c83e6..e88a1c52e9 100644
--- a/src/pages/administration/ClassOverview.page.vue
+++ b/src/pages/administration/ClassOverview.page.vue
@@ -21,7 +21,86 @@
@update:sort-desc="updateSortOrder"
@update:items-per-page="onUpdateItemsPerPage"
@update:page="onUpdateCurrentPage"
- />
+ >
+
+
+
+ {{ mdiAccountGroupOutline }}
+
+
+ {{ mdiPencilOutline }}
+
+
+ {{ mdiTrashCanOutline }}
+
+
+ {{ mdiArrowUp }}
+
+
+
+
+
+
+
+ {{ t("pages.administration.classes.deleteDialog.title") }}
+
+
+
+
+
import { Breadcrumb } from "@/components/templates/default-wireframe.types";
import DefaultWireframe from "@/components/templates/DefaultWireframe.vue";
-import { computed, ComputedRef, defineComponent, onMounted } from "vue";
-import GroupModule from "@/store/group";
import { useI18n } from "@/composables/i18n.composable";
+import GroupModule from "@/store/group";
+import { ClassInfo, ClassRootType } from "@/store/types/class-info";
import { Pagination } from "@/store/types/commons";
-import { ClassInfo } from "@/store/types/class-info";
-import { GROUP_MODULE_KEY, injectStrict } from "@/utils/inject";
import { SortOrder } from "@/store/types/sort-order.enum";
+import {
+ AUTH_MODULE_KEY,
+ GROUP_MODULE_KEY,
+ injectStrict,
+} from "@/utils/inject";
+import { RenderHTML } from "@feature-render-html";
+import {
+ mdiAccountGroupOutline,
+ mdiArrowUp,
+ mdiPencilOutline,
+ mdiTrashCanOutline,
+} from "@mdi/js";
+import {
+ computed,
+ ComputedRef,
+ defineComponent,
+ onMounted,
+ ref,
+ Ref,
+} from "vue";
+import VCustomDialog from "../../components/organisms/vCustomDialog.vue";
+import AuthModule from "../../store/auth";
export default defineComponent({
- components: { DefaultWireframe },
+ components: { DefaultWireframe, RenderHTML, VCustomDialog },
setup() {
const groupModule: GroupModule = injectStrict(GROUP_MODULE_KEY);
+ const authModule: AuthModule = injectStrict(AUTH_MODULE_KEY);
const { t } = useI18n();
@@ -72,6 +172,31 @@ export default defineComponent({
() => groupModule.getClasses
);
+ const hasPermission: ComputedRef = computed(() =>
+ authModule.getUserPermissions.includes("CLASS_EDIT".toLowerCase())
+ );
+
+ const showClassAction = (item: ClassInfo) =>
+ hasPermission.value && item.type === ClassRootType.Class;
+
+ const isDeleteDialogOpen: Ref = ref(false);
+
+ const selectedItem: Ref = ref();
+
+ const selectedItemName: ComputedRef = computed(
+ () => selectedItem.value?.name || "???"
+ );
+
+ const onClickDeleteIcon = (selectedClass: ClassInfo) => {
+ selectedItem.value = selectedClass;
+ isDeleteDialogOpen.value = true;
+ };
+
+ const onCancelClassDeletion = () => {
+ selectedItem.value = undefined;
+ isDeleteDialogOpen.value = false;
+ };
+
const pagination: ComputedRef = computed(
() => groupModule.getPagination
);
@@ -98,8 +223,19 @@ export default defineComponent({
text: t("common.labels.teacher"),
sortable: true,
},
+ {
+ value: "actions",
+ text: "",
+ sortable: false,
+ },
];
+ const onConfirmClassDeletion = async () => {
+ if (selectedItem.value) {
+ await groupModule.deleteClass(selectedItem.value.id);
+ }
+ };
+
const onUpdateSortBy = async (sortBy: string) => {
groupModule.setSortBy(sortBy);
await groupModule.loadClassesForSchool();
@@ -121,9 +257,7 @@ export default defineComponent({
};
onMounted(() => {
- (async () => {
- await groupModule.loadClassesForSchool();
- })();
+ groupModule.loadClassesForSchool();
});
return {
@@ -132,14 +266,26 @@ export default defineComponent({
breadcrumbs,
headers,
classes,
+ hasPermission,
+ showClassAction,
page,
sortBy,
sortOrder,
pagination,
+ selectedItem,
+ selectedItemName,
+ isDeleteDialogOpen,
+ onClickDeleteIcon,
+ onCancelClassDeletion,
+ onConfirmClassDeletion,
onUpdateSortBy,
updateSortOrder,
onUpdateCurrentPage,
onUpdateItemsPerPage,
+ mdiAccountGroupOutline,
+ mdiPencilOutline,
+ mdiTrashCanOutline,
+ mdiArrowUp,
};
},
});
diff --git a/src/serverApi/v3/api.ts b/src/serverApi/v3/api.ts
index 057eee0784..7b1fdfee28 100644
--- a/src/serverApi/v3/api.ts
+++ b/src/serverApi/v3/api.ts
@@ -401,6 +401,12 @@ export interface ClassInfoResponse {
* @memberof ClassInfoResponse
*/
schoolYear?: string;
+ /**
+ *
+ * @type {boolean}
+ * @memberof ClassInfoResponse
+ */
+ isUpgradable?: boolean;
}
/**
@@ -3264,7 +3270,7 @@ export interface OauthConfigResponse {
* @type {string}
* @memberof OauthConfigResponse
*/
- logoutEndpoint: string;
+ logoutEndpoint?: string;
/**
* Issuer
* @type {string}
@@ -4098,7 +4104,7 @@ export interface SubmissionContainerContentBody {
* @type {string}
* @memberof SubmissionContainerContentBody
*/
- dueDate?: string | null;
+ dueDate?: string;
}
/**
*
diff --git a/src/store/group.ts b/src/store/group.ts
index cb9c751f24..d27dc28750 100644
--- a/src/store/group.ts
+++ b/src/store/group.ts
@@ -6,10 +6,10 @@ import {
import { $axios, mapAxiosErrorToResponseError } from "@/utils/api";
import { AxiosResponse } from "axios";
import { Action, Module, Mutation, VuexModule } from "vuex-module-decorators";
+import { GroupMapper } from "./group/group.mapper";
import { ClassInfo } from "./types/class-info";
import { BusinessError, Pagination } from "./types/commons";
import { SortOrder } from "./types/sort-order.enum";
-import { GroupMapper } from "./group/group.mapper";
@Module({
name: "groupModule",
@@ -104,6 +104,29 @@ export default class GroupModule extends VuexModule {
this.page = page;
}
+ @Action
+ async deleteClass(classId: string): Promise {
+ this.setLoading(true);
+
+ try {
+ await $axios.delete(`/v1/classes/${classId}`);
+
+ await this.loadClassesForSchool();
+ } catch (error) {
+ const apiError = mapAxiosErrorToResponseError(error);
+
+ console.log(apiError);
+
+ this.setBusinessError({
+ error: apiError,
+ statusCode: apiError.code,
+ message: `${apiError.type}: ${apiError.message}`,
+ });
+ }
+
+ this.setLoading(false);
+ }
+
@Action
async loadClassesForSchool(): Promise {
this.setLoading(true);
diff --git a/src/store/group.unit.ts b/src/store/group.unit.ts
index 074411b43b..3c92c477b0 100644
--- a/src/store/group.unit.ts
+++ b/src/store/group.unit.ts
@@ -1,33 +1,38 @@
-import * as serverApi from "@/serverApi/v3/api";
import {
ClassInfoResponse,
ClassInfoSearchListResponse,
GroupApiInterface,
} from "@/serverApi/v3";
+import * as serverApi from "@/serverApi/v3/api";
+import { initializeAxios, mapAxiosErrorToResponseError } from "@/utils/api";
import {
axiosErrorFactory,
businessErrorFactory,
classInfoResponseFactory,
classInfoSearchListResponseFactory,
} from "@@/tests/test-utils";
+import { classInfoFactory } from "@@/tests/test-utils/factory/classInfoFactory";
+import { mockApiResponse } from "@@/tests/test-utils/mockApiResponse";
+import { createMock, DeepMocked } from "@golevelup/ts-jest";
+import { AxiosInstance } from "axios";
+import GroupModule from "./group";
import { ClassInfo, ClassRootType } from "./types/class-info";
import { BusinessError, Pagination } from "./types/commons";
import { SortOrder } from "./types/sort-order.enum";
-import GroupModule from "./group";
-import { createMock, DeepMocked } from "@golevelup/ts-jest";
-import { mockApiResponse } from "@@/tests/test-utils/mockApiResponse";
-import { mapAxiosErrorToResponseError } from "@/utils/api";
describe("GroupModule", () => {
let module: GroupModule;
let apiMock: DeepMocked;
+ let axiosMock: DeepMocked;
beforeEach(() => {
module = new GroupModule({});
apiMock = createMock();
+ axiosMock = createMock();
+ initializeAxios(axiosMock);
jest.spyOn(serverApi, "GroupApiFactory").mockReturnValue(apiMock);
});
@@ -257,4 +262,72 @@ describe("GroupModule", () => {
});
});
});
+
+ describe("deleteClass", () => {
+ describe("when called", () => {
+ const setup = () => {
+ const class1: ClassInfo = classInfoFactory.build();
+
+ module.setClasses([class1]);
+
+ return {
+ class1,
+ };
+ };
+
+ it("should delete the class", async () => {
+ const { class1 } = setup();
+
+ await module.deleteClass(class1.id);
+
+ expect(axiosMock.delete).toHaveBeenCalled();
+ });
+
+ it("should load classes for school", async () => {
+ const { class1 } = setup();
+
+ await module.deleteClass(class1.id);
+
+ expect(apiMock.groupControllerFindClassesForSchool).toHaveBeenCalled();
+ });
+ });
+
+ describe("when an error occurs during the api call", () => {
+ const setup = () => {
+ const error = axiosErrorFactory.build();
+ const apiError = mapAxiosErrorToResponseError(error);
+ const class1: ClassInfo = classInfoFactory.build();
+ const class2: ClassInfo = classInfoFactory.build();
+
+ module.setClasses([class1, class2]);
+ axiosMock.delete.mockRejectedValue(error);
+
+ return {
+ apiError,
+ class1,
+ class2,
+ };
+ };
+
+ it("should update the stores error", async () => {
+ const { apiError, class1 } = setup();
+
+ await module.deleteClass(class1.id);
+
+ expect(module.getBusinessError).toEqual({
+ error: apiError,
+ statusCode: apiError.code,
+ message: `${apiError.type}: ${apiError.message}`,
+ });
+ });
+
+ it("should not remove the class from the store", async () => {
+ const { class1, class2 } = setup();
+
+ await module.deleteClass(class1.id);
+
+ expect(module.getClasses).toEqual([class1, class2]);
+ });
+ });
+ });
});
diff --git a/src/store/group/group.mapper.ts b/src/store/group/group.mapper.ts
index 8335273cfd..602b759f63 100644
--- a/src/store/group/group.mapper.ts
+++ b/src/store/group/group.mapper.ts
@@ -18,6 +18,7 @@ export class GroupMapper {
teachers: classInfoResponse.teachers,
type: ClassRootTypeMapping[classInfoResponse.type],
id: classInfoResponse.id,
+ isUpgradable: classInfoResponse.isUpgradable,
})
);
diff --git a/src/store/types/class-info.ts b/src/store/types/class-info.ts
index 22080dd13a..bb36fe2ac2 100644
--- a/src/store/types/class-info.ts
+++ b/src/store/types/class-info.ts
@@ -4,6 +4,7 @@ export type ClassInfo = {
teachers: string[];
type: ClassRootType;
id: string;
+ isUpgradable?: boolean;
};
export enum ClassRootType {
diff --git a/tests/test-utils/factory/classInfoFactory.ts b/tests/test-utils/factory/classInfoFactory.ts
index adbaf1b509..3d44a9797c 100644
--- a/tests/test-utils/factory/classInfoFactory.ts
+++ b/tests/test-utils/factory/classInfoFactory.ts
@@ -5,6 +5,6 @@ export const classInfoFactory = Factory.define(({ sequence }) => ({
name: `className${sequence}`,
externalSourceName: "Source",
teachers: ["TestTeacher"],
- type: ClassRootType.Class,
+ type: ClassRootType.Group,
id: `id-${sequence}`,
}));