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" - /> + > + + + + +

+ {{ 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}`, }));