diff --git a/src/modules/data/room/roomMembers/roomMembers.composable.ts b/src/modules/data/room/roomMembers/roomMembers.composable.ts index b5fceb22f1..f121d8bde3 100644 --- a/src/modules/data/room/roomMembers/roomMembers.composable.ts +++ b/src/modules/data/room/roomMembers/roomMembers.composable.ts @@ -13,6 +13,7 @@ import { $axios } from "@/utils/api"; import { useI18n } from "vue-i18n"; import { useBoardNotifier } from "@util-board"; import { schoolsModule } from "@/store"; +import { authModule } from "@/store/store-accessor"; export const useRoomMembers = (roomId: string) => { const roomMembers: Ref = ref([]); @@ -25,9 +26,13 @@ export const useRoomMembers = (roomId: string) => { id: schoolsModule.getSchool.id, name: schoolsModule.getSchool.name, }; + const currentUserId = authModule.getUser?.id ?? ""; const userRoles: Record = { + [RoleName.Roomowner]: t("common.labels.teacher"), + [RoleName.Roomadmin]: t("common.labels.teacher"), [RoleName.Roomeditor]: t("common.labels.teacher"), + [RoleName.Roomviewer]: t("common.labels.teacher"), }; const roomApi = RoomApiFactory(undefined, "/v3", $axios); @@ -42,6 +47,10 @@ export const useRoomMembers = (roomId: string) => { return { ...member, displayRoleName: userRoles[member.roleName], + isSelectable: !( + member.userId === currentUserId || + member.roleName === RoleName.Roomowner + ), }; }); isLoading.value = false; diff --git a/src/modules/data/room/roomMembers/roomMembers.composable.unit.ts b/src/modules/data/room/roomMembers/roomMembers.composable.unit.ts index c065523b7c..fa066e906b 100644 --- a/src/modules/data/room/roomMembers/roomMembers.composable.unit.ts +++ b/src/modules/data/room/roomMembers/roomMembers.composable.unit.ts @@ -1,9 +1,10 @@ import { roomMemberListFactory, mockApiResponse, - roomMemberResponseFactory, + roomMemberFactory, roomMemberSchoolResponseFactory, schoolFactory, + meResponseFactory, } from "@@/tests/test-utils"; import { createMock, DeepMocked } from "@golevelup/ts-jest"; import * as serverApi from "@/serverApi/v3/api"; @@ -18,8 +19,9 @@ import { UserIdAndRoleRoleNameEnum, } from "@/serverApi/v3/api"; import { useBoardNotifier } from "@util-board"; -import { schoolsModule } from "@/store"; +import { schoolsModule, authModule } from "@/store"; import SchoolsModule from "@/store/schools"; +import AuthModule from "@/store/auth"; import setupStores from "@@/tests/test-utils/setupStores"; jest.mock("vue-i18n"); @@ -54,6 +56,7 @@ describe("useRoomMembers", () => { setupStores({ schoolsModule: SchoolsModule, + authModule: AuthModule, }); schoolsModule.setSchool( @@ -62,6 +65,9 @@ describe("useRoomMembers", () => { name: "Paul-Gerhardt-Gymnasium", }) ); + + const mockMe = meResponseFactory.build(); + authModule.setMe(mockMe); }); afterEach(() => { @@ -70,24 +76,52 @@ describe("useRoomMembers", () => { }); describe("fetchMembers", () => { - it("should fetch members and map members with role names", async () => { - const { fetchMembers, roomMembers } = useRoomMembers(roomId); - const membersMock = roomMemberResponseFactory.buildList(3); + describe("when the user is not room owner", () => { + it("should fetch members and map members with role names", async () => { + const { fetchMembers, roomMembers } = useRoomMembers(roomId); + const membersMock = roomMemberFactory(RoleName.Roomeditor).buildList(3); + + roomApiMock.roomControllerGetMembers.mockResolvedValue( + mockApiResponse({ + data: { data: membersMock }, + }) + ); + + await fetchMembers(); + + expect(roomMembers.value).toEqual( + membersMock.map((member) => ({ + ...member, + displayRoleName: "common.labels.teacher", + isSelectable: true, + })) + ); + }); + }); - roomApiMock.roomControllerGetMembers.mockResolvedValue( - mockApiResponse({ - data: { data: membersMock }, - }) - ); + describe("when the user is room owner", () => { + it("should fetch members and map members with role names", async () => { + const { fetchMembers, roomMembers } = useRoomMembers(roomId); + const membersMock = roomMemberFactory(RoleName.Roomowner).buildList(3); - await fetchMembers(); - - expect(roomMembers.value).toEqual( - membersMock.map((member) => ({ - ...member, - displayRoleName: "common.labels.teacher", - })) - ); + roomApiMock.roomControllerGetMembers.mockResolvedValue( + mockApiResponse({ + data: { + data: membersMock, + }, + }) + ); + + await fetchMembers(); + + expect(roomMembers.value).toEqual( + membersMock.map((member) => ({ + ...member, + displayRoleName: "common.labels.teacher", + isSelectable: false, + })) + ); + }); }); it("should throw an error if the API call fails", async () => { @@ -151,7 +185,9 @@ describe("useRoomMembers", () => { const { getPotentialMembers, potentialRoomMembers, roomMembers } = useRoomMembers(roomId); - const membersMock: RoomMemberResponse = roomMemberResponseFactory.build(); + const membersMock: RoomMemberResponse = roomMemberFactory( + RoleName.Roomeditor + ).build(); roomMembers.value = [membersMock]; @@ -280,7 +316,7 @@ describe("useRoomMembers", () => { mockApiResponse({}) ); - const membersMock = roomMemberResponseFactory.buildList(3); + const membersMock = roomMemberFactory(RoleName.Roomeditor).buildList(3); roomMembers.value = membersMock; const firstMember = membersMock[0]; diff --git a/src/modules/feature/room/RoomMembers/MembersTable.unit.ts b/src/modules/feature/room/RoomMembers/MembersTable.unit.ts index a0da092c36..e087fe58a5 100644 --- a/src/modules/feature/room/RoomMembers/MembersTable.unit.ts +++ b/src/modules/feature/room/RoomMembers/MembersTable.unit.ts @@ -3,13 +3,14 @@ import { createTestingVuetify, } from "@@/tests/test-utils/setup"; import MembersTable from "./MembersTable.vue"; -import { ref } from "vue"; +import { nextTick, ref } from "vue"; import { mdiMenuDown, mdiMenuUp, mdiMagnify } from "@icons/material"; -import { roomMemberResponseFactory } from "@@/tests/test-utils"; +import { roomMemberFactory } from "@@/tests/test-utils"; import { DOMWrapper, VueWrapper } from "@vue/test-utils"; import { VDataTable, VTextField } from "vuetify/lib/components/index.mjs"; import { useConfirmationDialog } from "@ui-confirmation-dialog"; import setupConfirmationComposableMock from "@@/tests/test-utils/composable-mocks/setupConfirmationComposableMock"; +import { RoleName } from "@/serverApi/v3"; jest.mock("@ui-confirmation-dialog"); const mockedUseRemoveConfirmationDialog = jest.mocked(useConfirmationDialog); @@ -37,7 +38,7 @@ describe("MembersTable", () => { ]; const setup = () => { - const mockMembers = roomMemberResponseFactory.buildList(3); + const mockMembers = roomMemberFactory(RoleName.Roomeditor).buildList(3); const wrapper = mount(MembersTable, { attachTo: document.body, global: { @@ -344,6 +345,41 @@ describe("MembersTable", () => { expect(wrapper.emitted()).not.toHaveProperty("remove:members"); }); + + describe("when members are 'roomowner'", () => { + const ownerMembers = roomMemberFactory(RoleName.Roomowner) + .buildList(3) + .map((member) => ({ ...member, isSelectable: false })); + + it("should not render remove button for room owner", async () => { + const { wrapper } = setup(); + + wrapper.setProps({ members: ownerMembers }); + await nextTick(); + + const dataTable = wrapper.getComponent(VDataTable); + const removeButton = dataTable.findComponent( + "[data-testid=remove-member-0]" + ); + + expect(removeButton.exists()).toBe(false); + }); + + it("members should not be selectable", async () => { + const { wrapper } = setup(); + + wrapper.setProps({ members: ownerMembers }); + await nextTick(); + + const dataTable = wrapper.getComponent(VDataTable); + + const checkboxes = dataTable.findAllComponents({ + name: "VSelectionControl", + }); + + expect(checkboxes[1].vm.disabled).toBe(true); + }); + }); }); }); diff --git a/src/modules/feature/room/RoomMembers/MembersTable.vue b/src/modules/feature/room/RoomMembers/MembersTable.vue index d8ba934427..8a2ba88e60 100644 --- a/src/modules/feature/room/RoomMembers/MembersTable.vue +++ b/src/modules/feature/room/RoomMembers/MembersTable.vue @@ -56,6 +56,7 @@ item-value="userId" mobile-breakpoint="sm" :items="memberList" + item-selectable="isSelectable" :headers="tableHeader" :items-per-page-options="[5, 10, 25, 50, 100]" :items-per-page="50" @@ -69,6 +70,7 @@