Skip to content

Commit

Permalink
BC-8519 - add room owner role (#3473)
Browse files Browse the repository at this point in the history
BC-8519 - add room owner role

---------

Co-authored-by: Murat Merdoglu <[email protected]>
Co-authored-by: Murat Merdoglu <[email protected]>
  • Loading branch information
3 people authored Dec 16, 2024
1 parent 15c3956 commit bfe0c96
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 31 deletions.
9 changes: 9 additions & 0 deletions src/modules/data/room/roomMembers/roomMembers.composable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RoomMemberResponse[]> = ref([]);
Expand All @@ -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<string, string> = {
[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);
Expand All @@ -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;
Expand Down
76 changes: 56 additions & 20 deletions src/modules/data/room/roomMembers/roomMembers.composable.unit.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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");
Expand Down Expand Up @@ -54,6 +56,7 @@ describe("useRoomMembers", () => {

setupStores({
schoolsModule: SchoolsModule,
authModule: AuthModule,
});

schoolsModule.setSchool(
Expand All @@ -62,6 +65,9 @@ describe("useRoomMembers", () => {
name: "Paul-Gerhardt-Gymnasium",
})
);

const mockMe = meResponseFactory.build();
authModule.setMe(mockMe);
});

afterEach(() => {
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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];
Expand Down
42 changes: 39 additions & 3 deletions src/modules/feature/room/RoomMembers/MembersTable.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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);
});
});
});
});

Expand Down
8 changes: 7 additions & 1 deletion src/modules/feature/room/RoomMembers/MembersTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -69,6 +70,7 @@
<template #[`item.actions`]="{ item, index }">
<v-btn
:data-testid="`remove-member-${index}`"
v-if="item.roleName !== RoleName.Roomowner"
size="x-small"
variant="text"
:aria-label="getRemoveAriaLabel(item)"
Expand All @@ -90,7 +92,7 @@ import {
mdiMagnify,
mdiTrashCanOutline,
} from "@icons/material";
import { RoomMemberResponse } from "@/serverApi/v3";
import { RoleName, RoomMemberResponse } from "@/serverApi/v3";
import {
ConfirmationDialog,
useConfirmationDialog,
Expand Down Expand Up @@ -189,6 +191,10 @@ const tableHeader = [
font-weight: bold;
}
:deep(.v-data-table__td .v-selection-control--disabled) {
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity));
}
.table-title-header {
min-height: 50px;
}
Expand Down
4 changes: 2 additions & 2 deletions src/modules/page/room/RoomMembers.page.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createTestingPinia } from "@pinia/testing";
import {
roomMemberListFactory,
mockedPiniaStoreTyping,
roomMemberResponseFactory,
roomMemberFactory,
roomMemberSchoolResponseFactory,
} from "@@/tests/test-utils";
import { useRoomMembers, useRoomDetailsStore } from "@data-room";
Expand Down Expand Up @@ -76,7 +76,7 @@ describe("RoomMembersPage", () => {

const room = createRoom ? buildRoom() : undefined;

const members = roomMemberResponseFactory.buildList(3);
const members = roomMemberFactory(RoleName.Roomeditor).buildList(3);
mockRoomMemberCalls.roomMembers = ref(members);

const wrapper = mount(RoomMembersPage, {
Expand Down
10 changes: 10 additions & 0 deletions src/serverApi/v3/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,9 @@ export enum AuthorizationContextParamsRequiredPermissionsEnum {
RoomEdit = 'ROOM_EDIT',
RoomView = 'ROOM_VIEW',
RoomDelete = 'ROOM_DELETE',
RoomMembersAdd = 'ROOM_MEMBERS_ADD',
RoomMembersRemove = 'ROOM_MEMBERS_REMOVE',
RoomChangeOwner = 'ROOM_CHANGE_OWNER',
SchoolChatManage = 'SCHOOL_CHAT_MANAGE',
SchoolCreate = 'SCHOOL_CREATE',
SchoolEdit = 'SCHOOL_EDIT',
Expand Down Expand Up @@ -6863,6 +6866,9 @@ export enum Permission {
RoomEdit = 'ROOM_EDIT',
RoomView = 'ROOM_VIEW',
RoomDelete = 'ROOM_DELETE',
RoomMembersAdd = 'ROOM_MEMBERS_ADD',
RoomMembersRemove = 'ROOM_MEMBERS_REMOVE',
RoomChangeOwner = 'ROOM_CHANGE_OWNER',
SchoolChatManage = 'SCHOOL_CHAT_MANAGE',
SchoolCreate = 'SCHOOL_CREATE',
SchoolEdit = 'SCHOOL_EDIT',
Expand Down Expand Up @@ -7285,6 +7291,8 @@ export enum RoleName {
Helpdesk = 'helpdesk',
Roomviewer = 'roomviewer',
Roomeditor = 'roomeditor',
Roomadmin = 'roomadmin',
Roomowner = 'roomowner',
Student = 'student',
Superhero = 'superhero',
Teacher = 'teacher',
Expand Down Expand Up @@ -9456,6 +9464,8 @@ export interface UserIdAndRole {
* @enum {string}
*/
export enum UserIdAndRoleRoleNameEnum {
Roomowner = 'roomowner',
Roomadmin = 'roomadmin',
Roomeditor = 'roomeditor',
Roomviewer = 'roomviewer'
}
Expand Down
9 changes: 4 additions & 5 deletions tests/test-utils/factory/room/roomMembersFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ import {
} from "@/serverApi/v3";
import { RoomMember } from "@data-room";

export const roomMemberResponseFactory = Factory.define<RoomMemberResponse>(
({ sequence }) => ({
export const roomMemberFactory = (roleName = RoleName.Roomowner) =>
Factory.define<RoomMemberResponse>(({ sequence }) => ({
userId: `member${sequence}`,
firstName: `firstName${sequence}`,
lastName: `lastName${sequence}`,
roleName: RoleName.Roomeditor,
roleName,
displayRoleName: RoleName.Teacher,
schoolName: "Paul-Gerhardt-Gymnasium",
})
);
}));

export const roomMemberListFactory = Factory.define<RoomMember>(
({ sequence }) => ({
Expand Down

0 comments on commit bfe0c96

Please sign in to comment.