From 55ad2bcbb61ea9fbdfcf5bced639aeff839596e5 Mon Sep 17 00:00:00 2001 From: Martin Schuhmacher <55735359+MartinSchuhmacher@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:31:10 +0100 Subject: [PATCH] BC-3933 - draft-status / visibility of course-board to students (#3132) * add room card menus for boards * added board menu to update visibility * adjusted draft chip to layout from archive chip --------- Co-authored-by: Odalys Adam Co-authored-by: Uwe Ilgenstein --- .../data-board/BoardApi.composable.ts | 8 + .../data-board/BoardApi.composable.unit.ts | 12 + .../data-board/BoardState.composable.ts | 15 + .../data-board/BoardState.composable.unit.ts | 30 + .../feature-board/board/Board.unit.ts | 26 +- src/components/feature-board/board/Board.vue | 39 +- .../feature-board/board/BoardHeader.unit.ts | 12 + .../feature-board/board/BoardHeader.vue | 152 +- .../shared/BoardAnyTitleInput.vue | 3 +- .../molecules/RoomBoardCard.unit.ts | 229 +- src/components/molecules/RoomBoardCard.vue | 142 +- .../molecules/RoomLessonCard.unit.ts | 80 +- src/components/molecules/RoomLessonCard.vue | 26 +- src/components/molecules/RoomTaskCard.unit.ts | 108 +- src/components/molecules/RoomTaskCard.vue | 30 +- .../templates/RoomDashboard.unit.ts | 23 +- src/components/templates/RoomDashboard.vue | 64 +- src/components/ui-board/BoardMenu.vue | 2 +- .../ui-board/BoardMenuActionPublish.unit.ts | 27 + .../ui-board/BoardMenuActionPublish.vue | 21 + .../ui-board/BoardMenuActionRevert.unit.ts | 27 + .../ui-board/BoardMenuActionRevert.vue | 21 + src/components/ui-board/index.ts | 4 + src/locales/de.ts | 10 +- src/locales/en.ts | 7 +- src/locales/es.ts | 8 +- src/locales/uk.ts | 10 +- src/pages/rooms/RoomDetails.page.vue | 7 +- src/serverApi/v3/api.ts | 4492 +++++++++-------- .../factory/boardResponseFactory.ts | 1 + 30 files changed, 3173 insertions(+), 2463 deletions(-) create mode 100644 src/components/ui-board/BoardMenuActionPublish.unit.ts create mode 100644 src/components/ui-board/BoardMenuActionPublish.vue create mode 100644 src/components/ui-board/BoardMenuActionRevert.unit.ts create mode 100644 src/components/ui-board/BoardMenuActionRevert.vue diff --git a/src/components/data-board/BoardApi.composable.ts b/src/components/data-board/BoardApi.composable.ts index f56cba8756..b326696582 100644 --- a/src/components/data-board/BoardApi.composable.ts +++ b/src/components/data-board/BoardApi.composable.ts @@ -207,6 +207,13 @@ export const useBoardApi = () => { }; }; + const updateBoardVisibilityCall = async ( + boardId: string, + isVisible: boolean + ) => { + return boardApi.boardControllerUpdateVisibility(boardId, { isVisible }); + }; + return { fetchBoardCall, createColumnCall, @@ -218,6 +225,7 @@ export const useBoardApi = () => { moveColumnCall, moveElementCall, updateBoardTitleCall, + updateBoardVisibilityCall, updateCardHeightCall, updateCardTitle, updateColumnTitleCall, diff --git a/src/components/data-board/BoardApi.composable.unit.ts b/src/components/data-board/BoardApi.composable.unit.ts index fc2b8dc44a..71b718174d 100644 --- a/src/components/data-board/BoardApi.composable.unit.ts +++ b/src/components/data-board/BoardApi.composable.unit.ts @@ -520,4 +520,16 @@ describe("BoardApi.composable", () => { ); }); }); + + describe("updateBoardVisibilityCall", () => { + it("should call boardControllerUpdateVisibility api", async () => { + const { updateBoardVisibilityCall } = useBoardApi(); + + await updateBoardVisibilityCall("board-id", true); + expect(boardApi.boardControllerUpdateVisibility).toHaveBeenCalledWith( + "board-id", + { isVisible: true } + ); + }); + }); }); diff --git a/src/components/data-board/BoardState.composable.ts b/src/components/data-board/BoardState.composable.ts index 98c7bc9784..d1f68e3335 100644 --- a/src/components/data-board/BoardState.composable.ts +++ b/src/components/data-board/BoardState.composable.ts @@ -23,6 +23,7 @@ export const useBoardState = (id: string) => { moveColumnCall, updateBoardTitleCall, updateColumnTitleCall, + updateBoardVisibilityCall, createCardCall, } = useBoardApi(); const { setEditModeId } = useSharedEditMode(); @@ -255,6 +256,19 @@ export const useBoardState = (id: string) => { } }; + const updateBoardVisibility = async (newVisibility: boolean) => { + if (board.value === undefined) return; + + try { + await updateBoardVisibilityCall(board.value.id, newVisibility); + board.value.isVisible = newVisibility; + } catch (error) { + handleError(error, { + 404: notifyWithTemplateAndReload("notUpdated", "board"), + }); + } + }; + const notifyWithTemplateAndReload = ( errorType: ErrorType, boardObjectType?: BoardObjectType @@ -293,6 +307,7 @@ export const useBoardState = (id: string) => { notifyWithTemplateAndReload, reloadBoard, updateBoardTitle, + updateBoardVisibility, updateColumnTitle, }; }; diff --git a/src/components/data-board/BoardState.composable.unit.ts b/src/components/data-board/BoardState.composable.unit.ts index 6c3e85e7b2..f5fdcb00bd 100644 --- a/src/components/data-board/BoardState.composable.unit.ts +++ b/src/components/data-board/BoardState.composable.unit.ts @@ -624,4 +624,34 @@ describe("BoardState.composable", () => { }); }); }); + + describe("updateBoardVisibility", () => { + it("should update board visibility", async () => { + const { updateBoardVisibility, board } = setup(); + board.value = testBoard; + + await updateBoardVisibility(true); + await nextTick(); + + expect( + mockedBoardApiCalls.updateBoardVisibilityCall + ).toHaveBeenCalledWith(board.value.id, true); + + expect(board.value.isVisible).toStrictEqual(true); + }); + + it("should handle error when api returns an error code", async () => { + const { updateBoardVisibility, board } = setup(); + board.value = testBoard; + + mockedBoardApiCalls.updateBoardVisibilityCall.mockRejectedValue( + setupErrorResponse() + ); + + await updateBoardVisibility(false); + await nextTick(); + + expect(mockedErrorHandlerCalls.handleError).toHaveBeenCalled(); + }); + }); }); diff --git a/src/components/feature-board/board/Board.unit.ts b/src/components/feature-board/board/Board.unit.ts index 65800d3a92..6c845dbb43 100644 --- a/src/components/feature-board/board/Board.unit.ts +++ b/src/components/feature-board/board/Board.unit.ts @@ -248,15 +248,21 @@ describe("Board", () => { jest.clearAllMocks(); }); - it("should call the board notifier when the user is teacher", () => { + it("should call the board notifier when the user is teacher", async () => { + jest.useFakeTimers(); + setup(); - expect(mockedBoardNotifierCalls.showInfo).toHaveBeenCalled(); + jest.runAllTimers(); + + expect(mockedBoardNotifierCalls.showCustomNotifier).toHaveBeenCalled(); }); it("should not call the board notifier when the user is not a teacher", async () => { defaultPermissions.isTeacher = false; setup(); - expect(mockedBoardNotifierCalls.showInfo).not.toHaveBeenCalled(); + expect( + mockedBoardNotifierCalls.showCustomNotifier + ).not.toHaveBeenCalled(); }); }); @@ -603,5 +609,19 @@ describe("Board", () => { expect(mockedBoardStateCalls.reloadBoard).toHaveBeenCalled(); }); }); + + describe("@onUpdateBoardVisibility", () => { + it("should update board visibility", async () => { + const { wrapper } = setup(); + + const boardHeader = wrapper.findComponent({ + name: "BoardHeader", + }); + boardHeader.vm.$emit("update:visibility"); + await nextTick(); + + expect(mockedBoardStateCalls.updateBoardVisibility).toHaveBeenCalled(); + }); + }); }); }); diff --git a/src/components/feature-board/board/Board.vue b/src/components/feature-board/board/Board.vue index 1f0690d70b..0eb04bb0ed 100644 --- a/src/components/feature-board/board/Board.vue +++ b/src/components/feature-board/board/Board.vue @@ -5,6 +5,8 @@ :boardId="board.id" :title="board.title" :titlePlaceholder="$t('pages.room.boardCard.label.courseBoard')" + :isDraft="!board.isVisible" + @update:visibility="onUpdateBoardVisibility" @update:title="onUpdateBoardTitle" />
@@ -84,7 +86,7 @@ import { ConfirmationDialog } from "@ui-confirmation-dialog"; import { LightBox } from "@ui-light-box"; import { extractDataAttribute, useBoardNotifier } from "@util-board"; import { useTouchDetection } from "@util-device-detection"; -import { useMediaQuery } from "@vueuse/core"; +import { useDebounceFn, useMediaQuery } from "@vueuse/core"; import { SortableEvent } from "sortablejs"; import { Sortable } from "sortablejs-vue3"; import { @@ -118,7 +120,7 @@ export default defineComponent({ }, setup(props) { const { t } = useI18n(); - const { showInfo, resetNotifier } = useBoardNotifier(); + const { resetNotifier, showCustomNotifier } = useBoardNotifier(); const { editModeId } = useSharedEditMode(); const isEditMode = computed(() => editModeId.value !== undefined); const { @@ -131,6 +133,7 @@ export default defineComponent({ moveColumn, reloadBoard, updateBoardTitle, + updateBoardVisibility, updateColumnTitle, } = useBoardState(toRef(props, "boardId").value); @@ -221,6 +224,13 @@ export default defineComponent({ await reloadBoard(); }; + const onUpdateBoardVisibility = async (newVisibility: boolean) => { + if (!hasEditPermission) return; + + await updateBoardVisibility(newVisibility); + await setAlert(); + }; + const onUpdateCardPosition = async (_: unknown, cardMove: CardMove) => { if (hasMovePermission) await moveCard(cardMove); }; @@ -233,10 +243,8 @@ export default defineComponent({ if (hasEditPermission) await updateBoardTitle(newTitle); }; - onMounted(() => { - if (isTeacher) { - showInfo(t("components.board.alert.info.teacher"), false); - } + onMounted(async () => { + await setAlert(); }); const debounceTime = computed(() => { @@ -247,6 +255,24 @@ export default defineComponent({ resetNotifier(); }); + const setAlert = useDebounceFn(() => { + if (!isTeacher) return; + + if (!board.value?.isVisible) { + showCustomNotifier( + t("components.board.alert.info.draft"), + "info", + 10000 + ); + } else { + showCustomNotifier( + t("components.board.alert.info.teacher"), + "info", + 10000 + ); + } + }, 100); + return { board, columnDropPlaceholderOptions, @@ -267,6 +293,7 @@ export default defineComponent({ onMoveColumnRight, onReloadBoard, onUpdateBoardTitle, + onUpdateBoardVisibility, onUpdateCardPosition, onUpdateColumnTitle, }; diff --git a/src/components/feature-board/board/BoardHeader.unit.ts b/src/components/feature-board/board/BoardHeader.unit.ts index a18982610d..c0b13562c9 100644 --- a/src/components/feature-board/board/BoardHeader.unit.ts +++ b/src/components/feature-board/board/BoardHeader.unit.ts @@ -11,6 +11,7 @@ import { useBoardPermissions, useEditMode, } from "@data-board"; +import { BoardMenuActionEdit } from "@ui-board"; import { shallowMount } from "@vue/test-utils"; import { computed } from "vue"; import BoardAnyTitleInput from "../shared/BoardAnyTitleInput.vue"; @@ -77,6 +78,17 @@ describe("BoardHeader", () => { }); }); + describe("when the 'edit' menu button is clicked", () => { + it("should call startEditMode", async () => { + const { startEditMode, wrapper } = setup(); + + const editButton = wrapper.findComponent(BoardMenuActionEdit); + editButton.vm.$emit("click"); + + expect(startEditMode).toBeCalled(); + }); + }); + describe("user permissions", () => { describe("when user is not permitted to edit the board", () => { it("should not find the BoardMenu in the DOM", () => { diff --git a/src/components/feature-board/board/BoardHeader.vue b/src/components/feature-board/board/BoardHeader.vue index 3755609d5c..a0d1d4702d 100644 --- a/src/components/feature-board/board/BoardHeader.vue +++ b/src/components/feature-board/board/BoardHeader.vue @@ -1,22 +1,48 @@ - diff --git a/src/components/feature-board/shared/BoardAnyTitleInput.vue b/src/components/feature-board/shared/BoardAnyTitleInput.vue index 304c472e86..f82a5db47b 100644 --- a/src/components/feature-board/shared/BoardAnyTitleInput.vue +++ b/src/components/feature-board/shared/BoardAnyTitleInput.vue @@ -5,7 +5,6 @@ v-model="modelValue" variant="solo" density="compact" - auto-grow flat :placeholder="placeholder" bg-color="transparent" @@ -13,10 +12,10 @@ :readonly="!isEditMode" role="heading" :aria-level="ariaLevel" - @keydown.enter="onEnter" :tabindex="isEditMode ? 0 : -1" :autofocus="internalIsFocused" :maxlength="maxLength" + @keydown.enter="onEnter" /> useRouter; -const mockBoardData = { +type BoardData = { + id: string; + title?: string; + published: boolean; + createdAt: string; + updatedAt: string; + columnBoardId: string; +}; + +const mockDraftBoardData = { id: "test-id", title: "title", published: false, @@ -20,16 +28,22 @@ const mockBoardData = { columnBoardId: "column-board-id", }; +const mockPublishedBoardData = { + id: "test-id-2", + title: "title-2", + published: true, + createdAt: "2023-05-31T15:34:59.276Z", + updatedAt: "2023-05-31T15:34:59.276Z", + columnBoardId: "column-board-id-2", +}; + const mockCourseData = { courseId: "test-course-id", courseName: "test-course-name", }; describe("RoomBoardCard", () => { - const setup = (props?: { - title?: string | undefined; - role?: Roles | undefined; - }) => { + const setup = (props: { boardData: BoardData }, options?: object) => { const router = createMock(); useRouterMock.mockReturnValue(router); // Note: router has to be mocked before mounting the component @@ -38,30 +52,40 @@ describe("RoomBoardCard", () => { plugins: [createTestingVuetify(), createTestingI18n()], }, props: { - role: props?.role ?? Roles.Teacher, dragInProgress: false, keyDrag: false, columnBoardItem: { - ...mockBoardData, - title: props ? props.title : mockBoardData.title, + ...props.boardData, }, courseData: mockCourseData, + ...options, }, }); return { wrapper, router }; }; + afterEach(() => { + jest.resetAllMocks(); + }); + describe("when a board card is rendered", () => { + const userRole = "teacher"; it("should be found in dom", () => { - const { wrapper } = setup(); + const { wrapper } = setup( + { boardData: mockDraftBoardData }, + { userRole } + ); expect(wrapper.findComponent(RoomBoardCard).exists()).toBe(true); }); describe("when board title is defined and not empty", () => { it("should have correct board title", () => { - const { wrapper } = setup(); - const expectedBoardTitle = mockBoardData.title; + const { wrapper } = setup( + { boardData: mockDraftBoardData }, + { userRole } + ); + const expectedBoardTitle = mockDraftBoardData.title; const boardTitle = wrapper.find(".board-title").element.textContent; expect(boardTitle).toContain(expectedBoardTitle); @@ -70,8 +94,10 @@ describe("RoomBoardCard", () => { describe("when title is defined but empty", () => { it("should have correct board title", () => { - const props = { title: "" }; - const { wrapper } = setup(props); + const { wrapper } = setup( + { boardData: { ...mockDraftBoardData, title: "" } }, + { userRole } + ); const expectedBoardTitle = "pages.room.boardCard.label.courseBoard"; const boardTitle = wrapper.find(".board-title").element.textContent; @@ -81,42 +107,135 @@ describe("RoomBoardCard", () => { describe("when title is undefined", () => { it("should have correct board title", () => { - const props = { title: undefined }; - const { wrapper } = setup(props); + const { wrapper } = setup( + { boardData: { ...mockDraftBoardData, title: undefined } }, + { userRole } + ); const expectedBoardTitle = "pages.room.boardCard.label.courseBoard"; const boardTitle = wrapper.find(".board-title").element.textContent; expect(boardTitle).toContain(expectedBoardTitle); }); }); - }); - describe("when user is a teacher", () => { - it("should show three dot menu", () => { - const { wrapper } = setup({ role: Roles.Teacher }); - const threeDotMenu = wrapper.find( - '[aria-label="pages.room.boardCard.menu.ariaLabel"]' + it("should have correct combined card title for draft board", () => { + const { wrapper } = setup( + { boardData: mockDraftBoardData }, + { userRole } ); + const boardCardTitle = + wrapper.find(".title-board-card").element.textContent; - expect(threeDotMenu.exists()).toBe(true); + expect(boardCardTitle).toContain( + "pages.room.boardCard.label.columnBoard - common.words.draft" + ); + }); + + it("should use hidden UI only for draft board cards", async () => { + const { wrapper: wrapperDraft } = setup( + { boardData: mockDraftBoardData }, + { userRole } + ); + const boardDraftCard = wrapperDraft.find(".board-card"); + expect(boardDraftCard.element.className).toContain("board-hidden"); + + const { wrapper: wrapperPublished } = setup( + { boardData: mockPublishedBoardData }, + { userRole } + ); + const boardPublishedCard = wrapperPublished.find(".board-card"); + expect(boardPublishedCard.element.className).not.toContain( + "board-hidden" + ); + }); + + it("should show three dot menu and find unpublish button for teachers on published boards", async () => { + const { wrapper: wrapperPublished } = setup( + { boardData: mockPublishedBoardData }, + { userRole } + ); + + const threeDotMenuPublished = wrapperPublished.find(".three-dot-button"); + await threeDotMenuPublished.trigger("click"); + + const moreActionButtons = wrapperPublished.findAllComponents( + `[data-testid="content-card-board-menu-unpublish"]` + ); + expect(moreActionButtons).toHaveLength(1); }); - }); - describe("when user is a student", () => { - it("should don't show three dot menu", () => { - const { wrapper } = setup({ role: Roles.Student }); - const threeDotMenu = wrapper.find( - '[aria-label="pages.room.boardCard.menu.ariaLabel"]' + it("should show publish action button for teachers on draft boards", async () => { + const { wrapper: wrapperDraft } = setup( + { boardData: mockDraftBoardData }, + { userRole } ); - expect(threeDotMenu.exists()).toBe(false); + const cardActionButtons = wrapperDraft.findAllComponents( + `[data-testid="content-card-board-menu-publish"]` + ); + expect(cardActionButtons).toHaveLength(1); + }); + + it("should not show three dot menu for students at all or for teachers on draft boards", async () => { + const { wrapper: wrapperPublishedStudent } = setup( + { boardData: mockPublishedBoardData }, + { userRole: "student" } + ); + const threeDotMenuPublishedStudent = + wrapperPublishedStudent.find(".three-dot-button"); + expect(threeDotMenuPublishedStudent.exists()).toBe(false); + + const { wrapper: wrapperDraftStudent } = setup( + { boardData: mockDraftBoardData }, + { userRole: "student" } + ); + const threeDotMenuDraftStudent = + wrapperDraftStudent.find(".three-dot-button"); + expect(threeDotMenuDraftStudent.exists()).toBe(false); + + const { wrapper: wrapperDraft } = setup( + { boardData: mockDraftBoardData }, + { userRole } + ); + const threeDotMenuDraft = wrapperDraft.find(".three-dot-button"); + expect(threeDotMenuDraft.exists()).toBe(false); + }); + + it("should not show card actions for students at all or for teachers on published boards", async () => { + const { wrapper: wrapperDraftStudent } = setup( + { boardData: mockDraftBoardData }, + { userRole: "student" } + ); + const boardDraftCardStudent = wrapperDraftStudent.find(".board-card"); + const actionButtonsDraftStudent = + boardDraftCardStudent.findAll(".action-button"); + expect(actionButtonsDraftStudent).toHaveLength(0); + + const { wrapper: wrapperPublishedStudent } = setup( + { boardData: mockPublishedBoardData }, + { userRole: "student" } + ); + const boardPublishedCardStudent = + wrapperPublishedStudent.find(".board-card"); + const actionButtonsPublishedStudent = + boardPublishedCardStudent.findAll(".action-button"); + expect(actionButtonsPublishedStudent).toHaveLength(0); + + const { wrapper: wrapperPublished } = setup( + { boardData: mockPublishedBoardData }, + { userRole } + ); + const boardPublishedCard = wrapperPublished.find(".board-card"); + const actionButtonsPublished = + boardPublishedCard.findAll(".action-button"); + expect(actionButtonsPublished).toHaveLength(0); }); }); describe("when interacting with a board card", () => { it("should redirect to column board when clicking on the card", () => { - const { wrapper, router } = setup(); - const boardId = mockBoardData.columnBoardId; + const { wrapper, router } = setup({ boardData: mockDraftBoardData }); + const boardId = mockDraftBoardData.columnBoardId; const boardCard = wrapper.findComponent({ name: "VCard" }); boardCard.vm.$emit("click"); @@ -126,8 +245,8 @@ describe("RoomBoardCard", () => { }); it("should redirect to column board when pressing enter on the card", async () => { - const { wrapper, router } = setup(); - const boardId = mockBoardData.columnBoardId; + const { wrapper, router } = setup({ boardData: mockDraftBoardData }); + const boardId = mockDraftBoardData.columnBoardId; const boardCard = wrapper.findComponent({ name: "VCard" }); await boardCard.trigger("keydown.enter"); @@ -137,7 +256,7 @@ describe("RoomBoardCard", () => { }); it("should not redirect to column board if a card is dragged", async () => { - const { wrapper, router } = setup(); + const { wrapper, router } = setup({ boardData: mockDraftBoardData }); await wrapper.setProps({ dragInProgress: true }); const boardCard = wrapper.findComponent({ name: "VCard" }); @@ -149,7 +268,7 @@ describe("RoomBoardCard", () => { }); it("should emit 'onDrag' when pressing space", async () => { - const { wrapper } = setup(); + const { wrapper } = setup({ boardData: mockDraftBoardData }); const boardCard = wrapper.findComponent({ name: "VCard" }); await boardCard.trigger("keydown.space"); @@ -158,7 +277,7 @@ describe("RoomBoardCard", () => { }); it("should emit 'tab-pressed' when pressing tab", async () => { - const { wrapper } = setup(); + const { wrapper } = setup({ boardData: mockDraftBoardData }); const boardCard = wrapper.findComponent({ name: "VCard" }); await boardCard.trigger("keydown.tab"); @@ -169,7 +288,7 @@ describe("RoomBoardCard", () => { it.each([["up"], ["down"]])( "should emit 'move-element' when pressing the %s arrow key and keyDrag is true", async (key) => { - const { wrapper } = setup(); + const { wrapper } = setup({ boardData: mockDraftBoardData }); await wrapper.setProps({ keyDrag: true }); const boardCard = wrapper.findComponent({ name: "VCard" }); @@ -182,7 +301,7 @@ describe("RoomBoardCard", () => { it.each([["up"], ["down"]])( "should not emit 'move-element' when pressing the %s arrow key and keyDrag is false", async (key) => { - const { wrapper } = setup(); + const { wrapper } = setup({ boardData: mockDraftBoardData }); const boardCard = wrapper.findComponent({ name: "VCard" }); await boardCard.trigger(`keydown.${key}`); @@ -190,5 +309,37 @@ describe("RoomBoardCard", () => { expect(wrapper.emitted("move-element")).toBeUndefined(); } ); + + it("should emit 'update-visibility' when card action button is pressed on draft board", async () => { + const { wrapper } = setup( + { boardData: mockDraftBoardData }, + { userRole: "teacher" } + ); + const boardCard = wrapper.find(".board-card"); + const cardActionButtons = boardCard.findAllComponents( + `[data-testid="content-card-board-menu-publish"]` + ); + + await cardActionButtons[0].trigger("click"); + const emitted = wrapper.emitted("update-visibility"); + expect(emitted).toHaveLength(1); + }); + + it("should emit 'update-visibility' when three dot menu button and action button is pressed on published board", async () => { + const { wrapper } = setup( + { boardData: mockPublishedBoardData }, + { userRole: "teacher" } + ); + + const threeDotMenuPublished = wrapper.find(".three-dot-button"); + await threeDotMenuPublished.trigger("click"); + const moreActionButtons = wrapper.findAllComponents( + `[data-testid="content-card-board-menu-unpublish"]` + ); + + await moreActionButtons[0].trigger("click"); + const emitted = wrapper.emitted("update-visibility"); + expect(emitted).toHaveLength(1); + }); }); }); diff --git a/src/components/molecules/RoomBoardCard.vue b/src/components/molecules/RoomBoardCard.vue index db05c14e4b..6426e1c445 100644 --- a/src/components/molecules/RoomBoardCard.vue +++ b/src/components/molecules/RoomBoardCard.vue @@ -1,9 +1,12 @@ - diff --git a/src/components/molecules/RoomLessonCard.unit.ts b/src/components/molecules/RoomLessonCard.unit.ts index 2530a494b1..d93ea41850 100644 --- a/src/components/molecules/RoomLessonCard.unit.ts +++ b/src/components/molecules/RoomLessonCard.unit.ts @@ -66,9 +66,9 @@ describe("@/components/molecules/RoomLessonCard", () => { }); describe("common behaviors and actions", () => { - const role = "teacher"; + const userRole = "teacher"; it("should have correct props", () => { - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); expect(wrapper.vm.ariaLabel).toStrictEqual(baseTestProps.ariaLabel); expect(wrapper.vm.lesson).toStrictEqual(baseTestProps.lesson); @@ -76,7 +76,7 @@ describe("@/components/molecules/RoomLessonCard", () => { }); it("should redirect to lesson page", async () => { - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); Object.defineProperty(window, "location", { set: jest.fn(), @@ -95,7 +95,7 @@ describe("@/components/molecules/RoomLessonCard", () => { it("should NOT redirect to lesson page if dragging is in progress", () => { const wrapper = getWrapper({ ...baseTestProps, - role, + userRole, dragInProgress: true, }); @@ -113,24 +113,24 @@ describe("@/components/molecules/RoomLessonCard", () => { }); it("should have correct title", () => { - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); const title = wrapper.find(".title-section"); expect(title.element.textContent).toContain("common.words.topic"); }); it("should use hidden lesson UI only for hidden lesson cards", async () => { - const hiddenLessonWrapper = getWrapper({ ...hiddenTestProps, role }); + const hiddenLessonWrapper = getWrapper({ ...hiddenTestProps, userRole }); const hiddenLessonCard = hiddenLessonWrapper.find(".lesson-card"); expect(hiddenLessonCard.element.className).toContain("hidden-lesson"); - const regularLessonWrapper = getWrapper({ ...baseTestProps, role }); + const regularLessonWrapper = getWrapper({ ...baseTestProps, userRole }); const lessonCard = regularLessonWrapper.find(".lesson-card"); expect(lessonCard.element.className).not.toContain("hidden-lesson"); }); it("should show information about the visibility of tasks for hidden lesson card", async () => { - const wrapper = getWrapper({ ...hiddenTestProps, role }); + const wrapper = getWrapper({ ...hiddenTestProps, userRole }); const chipElement = wrapper.find(".chip-value"); expect(chipElement.element.textContent).toContain( "pages.room.lessonCard.label.notVisible" @@ -140,9 +140,9 @@ describe("@/components/molecules/RoomLessonCard", () => { describe("user role based behaviors and actions", () => { describe("teachers", () => { - const role = "teacher"; + const userRole = "teacher"; it("should have one action button if lesson is hidden", () => { - const wrapper = getWrapper({ ...hiddenTestProps, role }); + const wrapper = getWrapper({ ...hiddenTestProps, userRole }); const actionButtons = wrapper.findAll(".action-button"); expect(actionButtons).toHaveLength(1); @@ -150,7 +150,7 @@ describe("@/components/molecules/RoomLessonCard", () => { }); it("should have no action button when lesson is visible", () => { - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); const actionButtons = wrapper.findAll(".action-button"); expect(actionButtons).toHaveLength(0); @@ -158,7 +158,7 @@ describe("@/components/molecules/RoomLessonCard", () => { it("should trigger the 'redirectAction' method when 'more action' edit button is clicked", async () => { const redirectActionMock = jest.fn(); - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); wrapper.vm.redirectAction = redirectActionMock; const threeDotButton = wrapper.find(".three-dot-button"); @@ -175,20 +175,20 @@ describe("@/components/molecules/RoomLessonCard", () => { ); }); - it("should trigger the 'post' method when 'post' button is clicked", async () => { - const postLessonMock = jest.fn(); - const wrapper = getWrapper({ ...hiddenTestProps, role }); - wrapper.vm.postLesson = postLessonMock; + it("should trigger the 'publishLesson' method when 'publish' button is clicked", async () => { + const publishLessonMock = jest.fn(); + const wrapper = getWrapper({ ...hiddenTestProps, userRole }); + wrapper.vm.publishLesson = publishLessonMock; const actionButton = wrapper.find(".action-button"); await actionButton.trigger("click"); - expect(postLessonMock).toHaveBeenCalled(); + expect(publishLessonMock).toHaveBeenCalled(); }); it("should have 'copy' more action if copying feature is enabled", async () => { envConfigModule.setEnvs({ FEATURE_COPY_SERVICE_ENABLED: true } as Envs); - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); const hasCopyMenuItem = wrapper.vm.moreActionsMenuItems.teacher.some( (item: any) => { @@ -202,7 +202,7 @@ describe("@/components/molecules/RoomLessonCard", () => { envConfigModule.setEnvs({ FEATURE_COPY_SERVICE_ENABLED: false, } as Envs); - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); const hasCopyMenuItem = wrapper.vm.moreActionsMenuItems.teacher.some( (item: any) => { @@ -215,7 +215,7 @@ describe("@/components/molecules/RoomLessonCard", () => { it("should trigger the 'copyCard' method when 'more action' copy button is clicked", async () => { envConfigModule.setEnvs({ FEATURE_COPY_SERVICE_ENABLED: true } as Envs); const copyCard = jest.fn(); - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); wrapper.vm.copyCard = copyCard; const threeDotButton = wrapper.find(".three-dot-button"); @@ -229,10 +229,10 @@ describe("@/components/molecules/RoomLessonCard", () => { expect(copyCard).toHaveBeenCalled(); }); - it("should trigger the 'revertPublishedCard' method when 'more action' revert button is clicked", async () => { - const revertPublishedCardMock = jest.fn(); - const wrapper = getWrapper({ ...baseTestProps, role }); - wrapper.vm.revertPublishedCard = revertPublishedCardMock; + it("should trigger the 'unPublishCard' method when 'more action' unpublish button is clicked", async () => { + const unPublishCardMock = jest.fn(); + const wrapper = getWrapper({ ...baseTestProps, userRole }); + wrapper.vm.unPublishCard = unPublishCardMock; const threeDotButton = wrapper.find(".three-dot-button"); await threeDotButton.trigger("click"); @@ -243,12 +243,12 @@ describe("@/components/molecules/RoomLessonCard", () => { await wrapper.vm.$nextTick(); - expect(revertPublishedCardMock).toHaveBeenCalled(); + expect(unPublishCardMock).toHaveBeenCalled(); }); it("should have 'share' more action if env flag is set", async () => { envConfigModule.setEnvs({ FEATURE_LESSON_SHARE: true } as Envs); - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); const hasShareMenuItem = wrapper.vm.moreActionsMenuItems.teacher.some( (item: any) => { @@ -259,7 +259,7 @@ describe("@/components/molecules/RoomLessonCard", () => { }); it("should emit 'delete-lesson' when delete action button clicked'", async () => { - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); const threeDotButton = wrapper.find(".three-dot-button"); await threeDotButton.trigger("click"); @@ -295,7 +295,7 @@ describe("@/components/molecules/RoomLessonCard", () => { dragInProgress: false, }; - const wrapper = getWrapper({ ...lessonObject, role }); + const wrapper = getWrapper({ ...lessonObject, userRole }); const expectedString = `common.words.tasks: 3 common.words.published / 4 common.words.planned / 2 common.words.drafts`; const chipElement = wrapper.find(".chip-value"); @@ -325,7 +325,7 @@ describe("@/components/molecules/RoomLessonCard", () => { dragInProgress: false, }; - const wrapper = getWrapper({ ...lessonObject, role }); + const wrapper = getWrapper({ ...lessonObject, userRole }); const expectedString = `common.words.tasks: 3 common.words.ready / 4 common.words.planned / 2 common.words.drafts`; const chipElement = wrapper.find(".chip-value"); @@ -355,7 +355,7 @@ describe("@/components/molecules/RoomLessonCard", () => { dragInProgress: false, }; - const wrapper = getWrapper({ ...lessonObject, role }); + const wrapper = getWrapper({ ...lessonObject, userRole }); const expectedString = `common.words.tasks: 3 common.words.published / 2 common.words.drafts`; const chipElement = wrapper.find(".chip-value"); @@ -385,7 +385,7 @@ describe("@/components/molecules/RoomLessonCard", () => { keyDrag: false, dragInProgress: false, }; - const wrapper = getWrapper({ ...lessonObject, role }); + const wrapper = getWrapper({ ...lessonObject, userRole }); const chipElement = wrapper.findAll(".chip-value"); expect(chipElement).toHaveLength(0); @@ -410,16 +410,16 @@ describe("@/components/molecules/RoomLessonCard", () => { keyDrag: false, dragInProgress: false, }; - const wrapper = getWrapper({ ...lessonObject, role }); + const wrapper = getWrapper({ ...lessonObject, userRole }); const chipElement = wrapper.findAll(".chip-value"); expect(chipElement).toHaveLength(0); }); }); describe("students", () => { - const role = "student"; + const userRole = "student"; it("should have no action button", () => { - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); const actionButtons = wrapper.findAll(".action-button"); expect(actionButtons).toHaveLength(0); @@ -428,9 +428,9 @@ describe("@/components/molecules/RoomLessonCard", () => { }); describe("keypress events", () => { - const role = "teacher"; + const userRole = "teacher"; it("should call 'handleClick' event when 'enter' key is pressed", async () => { - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); Object.defineProperty(window, "location", { set: jest.fn(), @@ -446,7 +446,11 @@ describe("@/components/molecules/RoomLessonCard", () => { describe("when keydrag is true", () => { it("should call 'onKeyPress' event when 'up, down, space' keys are pressed", async () => { - const wrapper = getWrapper({ ...baseTestProps, keyDrag: true, role }); + const wrapper = getWrapper({ + ...baseTestProps, + keyDrag: true, + userRole, + }); await wrapper.trigger("keydown.up"); @@ -469,7 +473,7 @@ describe("@/components/molecules/RoomLessonCard", () => { }); it("should emit 'tab-pressed' event when 'tab' key is pressed", async () => { - const wrapper = getWrapper({ ...baseTestProps, role }); + const wrapper = getWrapper({ ...baseTestProps, userRole }); await wrapper.trigger("keydown.tab"); diff --git a/src/components/molecules/RoomLessonCard.vue b/src/components/molecules/RoomLessonCard.vue index aea03e2f82..4624bc797d 100644 --- a/src/components/molecules/RoomLessonCard.vue +++ b/src/components/molecules/RoomLessonCard.vue @@ -22,7 +22,7 @@
@@ -52,7 +52,7 @@ this.postLesson(), + action: () => this.publishLesson(), name: this.$t("common.action.publish"), }); } } - if (this.role === Roles.Student) { + if (this.userRole === Roles.Student) { // if action is needed for the students add actions like above } return roleBasedActions; @@ -146,7 +146,7 @@ export default { [Roles.Student]: [], }; - if (this.role === Roles.Teacher) { + if (this.userRole === Roles.Teacher) { roleBasedMoreActions[Roles.Teacher].push({ icon: this.icons.mdiPencilOutline, action: () => @@ -169,7 +169,7 @@ export default { if (!this.isHidden) { roleBasedMoreActions[Roles.Teacher].push({ icon: this.icons.mdiUndoVariant, - action: () => this.revertPublishedCard(), + action: () => this.unPublishCard(), name: this.$t("pages.room.cards.label.revert"), dataTestId: "content-card-lesson-menu-revert", }); @@ -192,7 +192,7 @@ export default { }); } - if (this.role === Roles.Student) { + if (this.userRole === Roles.Student) { // if more action is needed for the students add actions like above } @@ -261,14 +261,14 @@ export default { redirectAction(value) { window.location = value; }, - postLesson() { - this.$emit("post-lesson"); + publishLesson() { + this.$emit("update-visibility", true); }, copyCard() { this.$emit("copy-lesson"); }, - revertPublishedCard() { - this.$emit("revert-lesson"); + unPublishCard() { + this.$emit("update-visibility", false); }, onKeyPress(e) { switch (e.keyCode) { diff --git a/src/components/molecules/RoomTaskCard.unit.ts b/src/components/molecules/RoomTaskCard.unit.ts index f8262c81e2..fb7fb61f3d 100644 --- a/src/components/molecules/RoomTaskCard.unit.ts +++ b/src/components/molecules/RoomTaskCard.unit.ts @@ -234,9 +234,9 @@ describe("@/components/molecules/RoomTaskCard", () => { }); describe("common behaviors and actions", () => { - const role = "teacher"; + const userRole = "teacher"; it("should have correct props", () => { - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); expect(wrapper.vm.task).toStrictEqual(testProps.task); expect(wrapper.vm.ariaLabel).toStrictEqual(testProps.ariaLabel); @@ -249,7 +249,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); const locationSpy = jest.spyOn(window, "location", "set"); - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); const taskCard = wrapper.find(".task-card"); await taskCard.trigger("click"); @@ -264,7 +264,11 @@ describe("@/components/molecules/RoomTaskCard", () => { }); const locationSpy = jest.spyOn(window, "location", "set"); - const wrapper = getWrapper({ ...testProps, role, dragInProgress: true }); + const wrapper = getWrapper({ + ...testProps, + userRole, + dragInProgress: true, + }); const taskCard = wrapper.find(".task-card"); await taskCard.trigger("click"); @@ -273,7 +277,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should have correct combined title for published task with due date ", () => { - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); const tagline = wrapper.find("[data-testid='tagline']"); expect(tagline.element.textContent).toContain( @@ -282,7 +286,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should have correct combined title for published task with no due date ", () => { - const wrapper = getWrapper({ ...noDueTestProps, role }); + const wrapper = getWrapper({ ...noDueTestProps, userRole }); const tagline = wrapper.find("[data-testid='tagline']"); expect(tagline.element.textContent).toContain( @@ -291,7 +295,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should have correct combined title for planned task", () => { - const wrapper = getWrapper({ ...plannedTestProps, role }); + const wrapper = getWrapper({ ...plannedTestProps, userRole }); const tagline = wrapper.find("[data-testid='tagline']"); expect(tagline.element.textContent).toContain( @@ -300,7 +304,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should have correct combined title for draft", () => { - const wrapper = getWrapper({ ...draftTestProps, role }); + const wrapper = getWrapper({ ...draftTestProps, userRole }); const tagline = wrapper.find("[data-testid='tagline']"); expect(tagline.element.textContent).toContain( @@ -309,7 +313,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should show or hide description area", async () => { - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); const descElement = wrapper.findAll(".text-description"); expect(descElement.length).toStrictEqual(0); @@ -322,15 +326,15 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should use hidden UI only for unfinished task draft cards and task planned cards", async () => { - const taskDraftWrapper = getWrapper({ ...draftTestProps, role }); + const taskDraftWrapper = getWrapper({ ...draftTestProps, userRole }); const taskDraftCard = taskDraftWrapper.find(".task-card"); expect(taskDraftCard.element.className).toContain("task-hidden"); - const plannedTaskWrapper = getWrapper({ ...plannedTestProps, role }); + const plannedTaskWrapper = getWrapper({ ...plannedTestProps, userRole }); const plannedCard = plannedTaskWrapper.find(".task-card"); expect(plannedCard.element.className).toContain("task-hidden"); - const regularTaskWrapper = getWrapper({ ...testProps, role }); + const regularTaskWrapper = getWrapper({ ...testProps, userRole }); const taskCard = regularTaskWrapper.find(".task-card"); expect(taskCard.element.className).not.toContain("task-hidden"); }); @@ -338,27 +342,27 @@ describe("@/components/molecules/RoomTaskCard", () => { describe("user role based behaviors and actions", () => { describe("teachers", () => { - const role = "teacher"; + const userRole = "teacher"; it("should not have submitted and graded section if task is a draft or finished or planned", () => { - const draftWrapper = getWrapper({ ...draftTestProps, role }); + const draftWrapper = getWrapper({ ...draftTestProps, userRole }); const draftSubmitSection = draftWrapper.findAll(".v-chip"); expect(draftSubmitSection).toHaveLength(0); - const finishedWrapper = getWrapper({ ...finishedTestProps, role }); + const finishedWrapper = getWrapper({ ...finishedTestProps, userRole }); const finsihedSubmitSection = finishedWrapper.findAll(".v-chip"); expect(finsihedSubmitSection).toHaveLength(0); - const plannedWrapper = getWrapper({ ...plannedTestProps, role }); + const plannedWrapper = getWrapper({ ...plannedTestProps, userRole }); const plannedSubmitSection = plannedWrapper.findAll(".v-chip"); expect(plannedSubmitSection).toHaveLength(0); }); it("should have submitted and graded section if task is not a draft and not finished", () => { - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); const submitSection = wrapper.findAllComponents(".v-chip"); expect(submitSection).toHaveLength(2); @@ -367,7 +371,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should have one 'finish' action button if task is not a draft and not finished", () => { - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); const actionButtons = wrapper.findAllComponents(".action-button"); expect(actionButtons).toHaveLength(1); @@ -377,7 +381,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should have one 'post' action button if task is a draft", () => { - const wrapper = getWrapper({ ...draftTestProps, role }); + const wrapper = getWrapper({ ...draftTestProps, userRole }); const actionButtons = wrapper.findAllComponents(".action-button"); expect(actionButtons).toHaveLength(1); @@ -387,7 +391,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should have one 'post' action button if task is planned", () => { - const wrapper = getWrapper({ ...plannedTestProps, role }); + const wrapper = getWrapper({ ...plannedTestProps, userRole }); const actionButtons = wrapper.findAllComponents(".action-button"); expect(actionButtons).toHaveLength(1); @@ -397,7 +401,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should have no action button if task is finished", () => { - const wrapper = getWrapper({ ...finishedTestProps, role }); + const wrapper = getWrapper({ ...finishedTestProps, userRole }); const actionButtons = wrapper.findAll(".action-button"); expect(actionButtons).toHaveLength(0); @@ -405,7 +409,7 @@ describe("@/components/molecules/RoomTaskCard", () => { it("should trigger the 'redirectAction' method when 'more action' edit button is clicked", async () => { const redirectAction = jest.fn(); - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); wrapper.vm.redirectAction = redirectAction; const threeDotButton = wrapper.find(".three-dot-button"); @@ -422,10 +426,10 @@ describe("@/components/molecules/RoomTaskCard", () => { ); }); - it("should trigger the 'revertPublishedCard' method when 'more action' revert button is clicked", async () => { - const revertPublishedCardMock = jest.fn(); - const wrapper = getWrapper({ ...testProps, role }); - wrapper.vm.revertPublishedCard = revertPublishedCardMock; + it("should trigger the 'unPublishCard' method when 'more action' unpublish button is clicked", async () => { + const unPublishCardMock = jest.fn(); + const wrapper = getWrapper({ ...testProps, userRole }); + wrapper.vm.unPublishCard = unPublishCardMock; const threeDotButton = wrapper.find(".three-dot-button"); await threeDotButton.trigger("click"); @@ -435,12 +439,12 @@ describe("@/components/molecules/RoomTaskCard", () => { ); await moreActionButton.trigger("click"); - expect(revertPublishedCardMock).toHaveBeenCalled(); + expect(unPublishCardMock).toHaveBeenCalled(); }); it("should trigger the 'restoreCard' method when 'more action' restore button is clicked", async () => { const restoreCardMock = jest.fn(); - const wrapper = getWrapper({ ...finishedTestProps, role }); + const wrapper = getWrapper({ ...finishedTestProps, userRole }); wrapper.vm.restoreCard = restoreCardMock; const threeDotButton = wrapper.find(".three-dot-button"); @@ -455,7 +459,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should emit 'delete-task' when 'more menu' delete action button clicked'", async () => { - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); const threeDotButton = wrapper.find(".three-dot-button"); await threeDotButton.trigger("click"); @@ -468,9 +472,9 @@ describe("@/components/molecules/RoomTaskCard", () => { expect(emitted).toHaveLength(1); }); - it("should trigger the 'publishCard' method when 'Post' button is clicked on a draft", async () => { + it("should trigger the 'publishCard' method when 'Publish' button is clicked on a draft", async () => { const publishCardMock = jest.fn(); - const wrapper = getWrapper({ ...draftTestProps, role }); + const wrapper = getWrapper({ ...draftTestProps, userRole }); wrapper.vm.publishCard = publishCardMock; const actionButton = wrapper.findComponent( @@ -481,9 +485,9 @@ describe("@/components/molecules/RoomTaskCard", () => { expect(publishCardMock).toHaveBeenCalled(); }); - it("should trigger the 'publishCard' method when 'Post' button is clicked on a planned task", async () => { + it("should trigger the 'publishCard' method when 'Publish' button is clicked on a planned task", async () => { const publishCardMock = jest.fn(); - const wrapper = getWrapper({ ...plannedTestProps, role }); + const wrapper = getWrapper({ ...plannedTestProps, userRole }); wrapper.vm.publishCard = publishCardMock; const actionButton = wrapper.find( @@ -496,7 +500,7 @@ describe("@/components/molecules/RoomTaskCard", () => { it("should trigger the 'finishCard' method when 'Finish' button is clicked", async () => { const finishCardMock = jest.fn(); - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); wrapper.vm.finishCard = finishCardMock; const actionButton = wrapper.find( @@ -508,7 +512,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should overdue chip is visible if the task overdued", async () => { - const wrapper = getWrapper({ ...overdueTestProps, role }); + const wrapper = getWrapper({ ...overdueTestProps, userRole }); const overrdueElement = wrapper.find(".overdue"); expect(overrdueElement.element.innerHTML).toContain( "pages.room.taskCard.teacher.label.overdue" @@ -539,7 +543,7 @@ describe("@/components/molecules/RoomTaskCard", () => { description: "some description here", }, }; - const wrapper = getWrapper({ ...localProps, role }); + const wrapper = getWrapper({ ...localProps, userRole }); const { vm } = wrapper; expect(vm.isPlanned).toBe(false); }); @@ -569,7 +573,7 @@ describe("@/components/molecules/RoomTaskCard", () => { description: "some description here", }, }; - const wrapper = getWrapper({ ...localProps, role }); + const wrapper = getWrapper({ ...localProps, userRole }); const { vm } = wrapper; expect(vm.isPlanned).toBe(true); }); @@ -581,7 +585,7 @@ describe("@/components/molecules/RoomTaskCard", () => { FEATURE_COPY_SERVICE_ENABLED: true, } as Envs); const copyCard = jest.fn(); - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); wrapper.vm.copyCard = copyCard; const threeDotButton = wrapper.find(".three-dot-button"); @@ -601,7 +605,7 @@ describe("@/components/molecules/RoomTaskCard", () => { envConfigModule.setEnvs({ FEATURE_COPY_SERVICE_ENABLED: false, } as Envs); - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); const threeDotButton = wrapper.find(".three-dot-button"); await threeDotButton.trigger("click"); @@ -617,9 +621,9 @@ describe("@/components/molecules/RoomTaskCard", () => { }); describe("students", () => { - const role = "student"; + const userRole = "student"; it("should have no button if task is finished", async () => { - const wrapper = getWrapper({ ...studentFinishedTestProps, role }); + const wrapper = getWrapper({ ...studentFinishedTestProps, userRole }); const actionButtons = wrapper.findAll(".action-button"); expect(actionButtons).toHaveLength(0); @@ -627,7 +631,7 @@ describe("@/components/molecules/RoomTaskCard", () => { it("should have finish button if task is not marked as finished", async () => { const finishCardMock = jest.fn(); - const wrapper = getWrapper({ ...studentTestProps, role }); + const wrapper = getWrapper({ ...studentTestProps, userRole }); wrapper.vm.finishCard = finishCardMock; const actionButton = wrapper.findComponent( `[data-testid="room-detail-task-action-done"]` @@ -643,7 +647,7 @@ describe("@/components/molecules/RoomTaskCard", () => { it("should trigger the 'restoreCard' method when 'more action' restore button is clicked", async () => { const restoreCardMock = jest.fn(); - const wrapper = getWrapper({ ...studentFinishedTestProps, role }); + const wrapper = getWrapper({ ...studentFinishedTestProps, userRole }); wrapper.vm.restoreCard = restoreCardMock; const threeDotButton = wrapper.find(".three-dot-button"); @@ -683,7 +687,7 @@ describe("@/components/molecules/RoomTaskCard", () => { description: "some description here", }, }; - const wrapper = getWrapper({ ...localProps, role }); + const wrapper = getWrapper({ ...localProps, userRole }); expect(wrapper.find("[data-test-id='dueDateHintLabel']").exists()).toBe( true @@ -713,7 +717,7 @@ describe("@/components/molecules/RoomTaskCard", () => { description: "some description here", }, }; - const wrapper = getWrapper({ ...localProps, role }); + const wrapper = getWrapper({ ...localProps, userRole }); const chips = wrapper.find(".overdue"); expect(chips.element.textContent).toContain( @@ -744,7 +748,7 @@ describe("@/components/molecules/RoomTaskCard", () => { description: "some description here", }, }; - const wrapper = getWrapper({ ...localProps, role }); + const wrapper = getWrapper({ ...localProps, userRole }); const chips = wrapper.find(".submitted"); expect(chips.element.textContent).toContain( @@ -775,7 +779,7 @@ describe("@/components/molecules/RoomTaskCard", () => { description: "some description here", }, }; - const wrapper = getWrapper({ ...localProps, role }); + const wrapper = getWrapper({ ...localProps, userRole }); const chips = wrapper.find(".graded"); expect(chips.element.textContent).toContain( @@ -784,7 +788,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should have no chips if task is finished", () => { - const finishedWrapper = getWrapper({ ...finishedTestProps, role }); + const finishedWrapper = getWrapper({ ...finishedTestProps, userRole }); const chips = finishedWrapper.findAll(".v-chip"); expect(chips).toHaveLength(0); @@ -797,9 +801,9 @@ describe("@/components/molecules/RoomTaskCard", () => { }); describe("keypress events", () => { - const role = "teacher"; + const userRole = "teacher"; it("should call 'handleClick' event when 'enter' key is pressed", async () => { - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); Object.defineProperty(window, "location", { set: jest.fn(), @@ -814,7 +818,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should call 'onKeyPress' event when 'up, down, space' keys are pressed", async () => { - const wrapper = getWrapper({ ...testProps, keyDrag: true, role }); + const wrapper = getWrapper({ ...testProps, keyDrag: true, userRole }); await wrapper.trigger("keydown.up"); @@ -838,7 +842,7 @@ describe("@/components/molecules/RoomTaskCard", () => { }); it("should emit 'tab-pressed' event when 'tab' key is pressed", async () => { - const wrapper = getWrapper({ ...testProps, role }); + const wrapper = getWrapper({ ...testProps, userRole }); await wrapper.trigger("keydown", { key: "tab" }); diff --git a/src/components/molecules/RoomTaskCard.vue b/src/components/molecules/RoomTaskCard.vue index b0df57ee7b..ca8600dc4b 100644 --- a/src/components/molecules/RoomTaskCard.vue +++ b/src/components/molecules/RoomTaskCard.vue @@ -23,7 +23,7 @@
@@ -46,7 +46,7 @@ >
({}), }, - role: { type: String, required: true }, + userRole: { type: String, required: true }, ariaLabel: { type: String, default: "", @@ -187,7 +187,7 @@ export default { [Roles.Student]: [], }; - if (this.role === Roles.Teacher) { + if (this.userRole === Roles.Teacher) { if (this.isPlanned || (this.isDraft && !this.isFinished)) { roleBasedActions[Roles.Teacher].push({ action: () => this.publishCard(), @@ -204,7 +204,7 @@ export default { } } - if (this.role === Roles.Student) { + if (this.userRole === Roles.Student) { if (!this.isFinished) { roleBasedActions[Roles.Student].push({ action: () => this.finishCard(), @@ -222,7 +222,7 @@ export default { [Roles.Student]: [], }; - if (this.role === Roles.Teacher) { + if (this.userRole === Roles.Teacher) { roleBasedChips[Roles.Teacher].push({ name: `${this.task.status.submitted}/${ this.task.status.maxSubmissions @@ -246,7 +246,7 @@ export default { } } - if (this.role === Roles.Student) { + if (this.userRole === Roles.Student) { if (this.isSubmittedNotGraded) { roleBasedChips[Roles.Student].push({ icon: "$taskDone", @@ -288,7 +288,7 @@ export default { [Roles.Student]: [], }; - if (this.role === Roles.Teacher) { + if (this.userRole === Roles.Teacher) { roleBasedMoreActions[Roles.Teacher].push({ icon: this.icons.mdiPencilOutline, action: () => @@ -320,7 +320,7 @@ export default { if (!this.isDraft && !this.isFinished) { roleBasedMoreActions[Roles.Teacher].push({ icon: this.icons.mdiUndoVariant, - action: () => this.revertPublishedCard(), + action: () => this.unPublishCard(), name: this.$t("pages.room.cards.label.revert"), dataTestId: "content-card-task-menu-revert", }); @@ -334,7 +334,7 @@ export default { }); } - if (this.role === Roles.Student) { + if (this.userRole === Roles.Student) { // if more action is needed for the students add actions like above } @@ -389,10 +389,10 @@ export default { window.location = value; }, publishCard() { - this.$emit("post-task"); + this.$emit("update-visibility", true); }, - revertPublishedCard() { - this.$emit("revert-task"); + unPublishCard() { + this.$emit("update-visibility", false); }, finishCard() { this.$emit("finish-task"); diff --git a/src/components/templates/RoomDashboard.unit.ts b/src/components/templates/RoomDashboard.unit.ts index c1d00efa35..dd0f830377 100644 --- a/src/components/templates/RoomDashboard.unit.ts +++ b/src/components/templates/RoomDashboard.unit.ts @@ -100,7 +100,7 @@ const mockData = { content: { id: "column-board-id", title: "title", - published: false, + isVisible: false, createdAt: "2023-05-31T15:34:59.276Z", updatedAt: "2023-05-31T15:34:59.276Z", }, @@ -503,9 +503,11 @@ describe("@/components/templates/RoomDashboard.vue", () => { roomModule.finishTask = finishTaskMock; taskCard.vm.$emit("finish-task"); + expect(finishTaskMock).toHaveBeenCalled(); expect(finishTaskMock.mock.calls[0][0].action).toStrictEqual("finish"); }); + it("should call restoreTask action", async () => { const finishTaskMock = jest.fn(); const wrapper = getWrapper({ @@ -516,6 +518,7 @@ describe("@/components/templates/RoomDashboard.vue", () => { roomModule.finishTask = finishTaskMock; taskCard.vm.$emit("restore-task"); + expect(finishTaskMock).toHaveBeenCalled(); expect(finishTaskMock.mock.calls[0][0].action).toStrictEqual("restore"); }); @@ -532,6 +535,7 @@ describe("@/components/templates/RoomDashboard.vue", () => { roomModule.finishTask = finishTaskMock; taskCard.vm.$emit("finish-task"); + expect(finishTaskMock).toHaveBeenCalled(); expect(finishTaskMock.mock.calls[0][0].action).toStrictEqual("finish"); }); @@ -552,6 +556,23 @@ describe("@/components/templates/RoomDashboard.vue", () => { }); }); + describe("Publishing and unpublishing a board", () => { + it("should call publishBoard action", async () => { + const publishCardMock = jest.fn(); + const wrapper = getWrapper({ + roomDataObject: mockData, + role: "teacher", + }); + const boardCard = wrapper.findComponent({ name: "room-board-card" }); + roomModule.publishCard = publishCardMock; + + boardCard.vm.$emit("update-visibility", true); + + expect(publishCardMock).toHaveBeenCalled(); + expect(publishCardMock.mock.calls[0][0].visibility).toStrictEqual(true); + }); + }); + describe("CopyTask Process", () => { beforeEach(() => { envConfigModule.setEnvs({ FEATURE_COPY_SERVICE_ENABLED: true } as Envs); diff --git a/src/components/templates/RoomDashboard.vue b/src/components/templates/RoomDashboard.vue index db8e52ec44..f4ece6dc48 100644 --- a/src/components/templates/RoomDashboard.vue +++ b/src/components/templates/RoomDashboard.vue @@ -19,7 +19,7 @@ - -
- -
@@ -264,11 +272,8 @@ export default { } }, methods: { - async postDraftElement(elementId) { - await roomModule.publishCard({ elementId, visibility: true }); - }, - async revertPublishedElement(elementId) { - await roomModule.publishCard({ elementId, visibility: false }); + async updateCardVisibility(elementId, visibility) { + await roomModule.publishCard({ elementId, visibility }); }, async onSort(items) { const idList = {}; @@ -355,6 +360,11 @@ export default { courseId: this.roomData.roomId, }); }, + boardCardIsVisibleToStudent(card) { + const isBoardCard = card.type === this.cardTypes.ColumnBoard; + const isVisibleToStudent = card.content.published; + return isBoardCard && isVisibleToStudent; + }, }, }; diff --git a/src/components/ui-board/BoardMenu.vue b/src/components/ui-board/BoardMenu.vue index ff008755f1..ce2a701685 100644 --- a/src/components/ui-board/BoardMenu.vue +++ b/src/components/ui-board/BoardMenu.vue @@ -3,7 +3,7 @@