From 64858352de9982370ee5115f022971d3d06cf066 Mon Sep 17 00:00:00 2001 From: virgilchiriac <17074330+virgilchiriac@users.noreply.github.com> Date: Fri, 16 Aug 2024 09:14:20 +0200 Subject: [PATCH 1/8] BC-7345 - upgrade node (#3346) --- .github/workflows/test.yml | 6 +++++- .npmrc | 1 + .nvmrc | 2 +- Dockerfile | 2 +- package-lock.json | 11 +++++++---- package.json | 4 ++-- 6 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 .npmrc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0e52199b5f..0989349ac2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ permissions: contents: read env: - node: 18 + node: 20 jobs: unit: runs-on: ubuntu-latest @@ -49,6 +49,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: setup node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.node }} - name: npm ci run: npm ci --prefer-offline --no-audit - name: npm run lint diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..b6f27f1359 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.nvmrc b/.nvmrc index 3c032078a4..209e3ef4b6 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18 +20 diff --git a/Dockerfile b/Dockerfile index 0b0daa5972..8036526997 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # build stage -FROM docker.io/node:18-bullseye AS build-stage +FROM docker.io/node:20 AS build-stage ## add libraries needed for installing canvas npm package RUN apt update && apt install -y g++ libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev; diff --git a/package-lock.json b/package-lock.json index 60d6c90f7a..9246ee6d0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "dependencies": { "@braintree/sanitize-url": "^6.0.4", "@ckeditor/ckeditor5-vue": "^5.1.0", - "@hpi-schul-cloud/ckeditor": "^1.0.0", + "@hpi-schul-cloud/ckeditor": "^1.1.0", "@lumieducation/h5p-webcomponents": "^9.2.2", "@vuelidate/core": "^2.0.3", "@vuelidate/validators": "^2.0.4", @@ -88,7 +88,7 @@ "webpack-plugin-vuetify": "^3.0.3" }, "engines": { - "node": "18", + "node": "20", "npm": ">=9" } }, @@ -1835,10 +1835,13 @@ "license": "MIT" }, "node_modules/@hpi-schul-cloud/ckeditor": { - "version": "1.0.0", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@hpi-schul-cloud/ckeditor/-/ckeditor-1.1.0.tgz", + "integrity": "sha512-pcmF/JtrCB9K4uXQdKiFaziPEKYbv1E9M4HJJANBXem+FFLMHWbBkmdJQxR1LRizLc4FqAvlzmKQq5pHdlng8A==", "license": "AGPL-3.0", "engines": { - "node": "18" + "node": "20", + "npm": ">=10" } }, "node_modules/@humanwhocodes/config-array": { diff --git a/package.json b/package.json index 97dcebb441..ed761f443a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "@braintree/sanitize-url": "^6.0.4", "@ckeditor/ckeditor5-vue": "^5.1.0", - "@hpi-schul-cloud/ckeditor": "^1.0.0", + "@hpi-schul-cloud/ckeditor": "^1.1.0", "@lumieducation/h5p-webcomponents": "^9.2.2", "@vuelidate/core": "^2.0.3", "@vuelidate/validators": "^2.0.4", @@ -102,7 +102,7 @@ } }, "engines": { - "node": "18", + "node": "20", "npm": ">=9" } } From 5b87a95f64439edffabdca0851411470d48e22eb Mon Sep 17 00:00:00 2001 From: virgilchiriac <17074330+virgilchiriac@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:36:42 +0200 Subject: [PATCH 2/8] BC-7826 - make logo link to dashboard (#3359) --- src/modules/ui/layout/CloudLogo.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/ui/layout/CloudLogo.vue b/src/modules/ui/layout/CloudLogo.vue index 0a02469d81..1d7021b149 100644 --- a/src/modules/ui/layout/CloudLogo.vue +++ b/src/modules/ui/layout/CloudLogo.vue @@ -1,5 +1,5 @@ From b4e65f69f167d98fb708f57e5d5ee0095b098a98 Mon Sep 17 00:00:00 2001 From: Murat Merdoglu <64781656+muratmerdoglu-dp@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:50:02 +0200 Subject: [PATCH 3/8] BC-7808 - Fix unexpected cloned card element (#3353) BC-7808 - implement key changing to force the component to re-rendering --- src/modules/data/board/Board.store.unit.ts | 1 - .../boardActions/boardSocketApi.composable.ts | 11 +++++-- .../boardSocketApi.composable.unit.ts | 14 ++++++++- .../board/fixSamePositionDnD.composable.ts | 29 +++++++++++++++++++ .../fixSamePositionDnD.composable.unit.ts | 16 ++++++++++ src/modules/data/board/index.ts | 2 ++ .../feature/board/board/BoardColumn.unit.ts | 19 +++++++++++- .../feature/board/board/BoardColumn.vue | 13 +++++++-- 8 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 src/modules/data/board/fixSamePositionDnD.composable.ts create mode 100644 src/modules/data/board/fixSamePositionDnD.composable.unit.ts diff --git a/src/modules/data/board/Board.store.unit.ts b/src/modules/data/board/Board.store.unit.ts index 696d4a4921..079bab5af0 100644 --- a/src/modules/data/board/Board.store.unit.ts +++ b/src/modules/data/board/Board.store.unit.ts @@ -661,7 +661,6 @@ describe("BoardStore", () => { expect(secondColumnCardsAfterMove?.map((card) => card.cardId)).toEqual([ secondCardId, ]); - expect(firstColumnCardsAfterMove?.map((card) => card.cardId)).toEqual([ firstCardId, thirdCardId, diff --git a/src/modules/data/board/boardActions/boardSocketApi.composable.ts b/src/modules/data/board/boardActions/boardSocketApi.composable.ts index 16eac25a8a..5deaced929 100644 --- a/src/modules/data/board/boardActions/boardSocketApi.composable.ts +++ b/src/modules/data/board/boardActions/boardSocketApi.composable.ts @@ -1,6 +1,6 @@ import * as BoardActions from "./boardActions"; import * as CardActions from "../cardActions/cardActions"; -import { useSocketConnection } from "@data-board"; +import { useSocketConnection, useForceRender } from "@data-board"; import { useBoardStore } from "../Board.store"; import { CreateCardRequestPayload, @@ -9,6 +9,7 @@ import { DisconnectSocketRequestPayload, FetchBoardRequestPayload, MoveCardRequestPayload, + MoveCardSuccessPayload, MoveColumnRequestPayload, UpdateBoardTitleRequestPayload, UpdateBoardVisibilityRequestPayload, @@ -95,7 +96,8 @@ export const useBoardSocketApi = () => { ...successActions, ...failureActions, ...ariaLiveNotifications, - on(BoardActions.disconnectSocket, disconnectSocketRequest) + on(BoardActions.disconnectSocket, disconnectSocketRequest), + on(BoardActions.moveCardSuccess, setRenderKeyAfterMoveCard) ); }; @@ -171,6 +173,11 @@ export const useBoardSocketApi = () => { emitOnSocket("update-board-visibility-request", payload); }; + const setRenderKeyAfterMoveCard = (payload: MoveCardSuccessPayload) => { + const { generateRenderKey } = useForceRender(payload.fromColumnId); + generateRenderKey(); + }; + const createCardFailure = () => notifySocketError("notCreated", "boardCard"); const createColumnFailure = () => notifySocketError("notCreated", "boardColumn"); diff --git a/src/modules/data/board/boardActions/boardSocketApi.composable.unit.ts b/src/modules/data/board/boardActions/boardSocketApi.composable.unit.ts index dfa5d562e1..0e1b672ba1 100644 --- a/src/modules/data/board/boardActions/boardSocketApi.composable.unit.ts +++ b/src/modules/data/board/boardActions/boardSocketApi.composable.unit.ts @@ -9,7 +9,11 @@ import { mockedPiniaStoreTyping, } from "@@/tests/test-utils"; import setupStores from "@@/tests/test-utils/setupStores"; -import { useBoardStore, useSocketConnection } from "@data-board"; +import { + useBoardStore, + useSocketConnection, + useForceRender, +} from "@data-board"; import { createMock, DeepMocked } from "@golevelup/ts-jest"; import { createTestingPinia } from "@pinia/testing"; import { useBoardNotifier, useSharedLastCreatedElement } from "@util-board"; @@ -36,6 +40,9 @@ import { useBoardSocketApi } from "./boardSocketApi.composable"; jest.mock("../socket/socket"); const mockedUseSocketConnection = jest.mocked(useSocketConnection); +jest.mock("../fixSamePositionDnD.composable"); +const mockedUseForceRender = jest.mocked(useForceRender); + jest.mock("./boardRestApi.composable"); const mockedUseBoardRestApi = jest.mocked(useBoardRestApi); @@ -59,6 +66,7 @@ describe("useBoardSocketApi", () => { let mockedSharedLastCreatedElementActions: DeepMocked< ReturnType >; + let mockedUseForceRenderHandler: ReturnType; beforeEach(() => { setActivePinia(createTestingPinia()); @@ -89,6 +97,9 @@ describe("useBoardSocketApi", () => { mockedSharedLastCreatedElement.mockReturnValue( mockedSharedLastCreatedElementActions ); + mockedUseForceRenderHandler = + createMock>(); + mockedUseForceRender.mockReturnValue(mockedUseForceRenderHandler); }); it("should be defined", () => { @@ -176,6 +187,7 @@ describe("useBoardSocketApi", () => { dispatch(BoardActions.moveCardSuccess(payload)); expect(boardStore.moveCardSuccess).toHaveBeenCalledWith(payload); + expect(mockedUseForceRenderHandler.generateRenderKey).toHaveBeenCalled(); }); it("should call moveColumnSuccess for corresponding action", () => { diff --git a/src/modules/data/board/fixSamePositionDnD.composable.ts b/src/modules/data/board/fixSamePositionDnD.composable.ts new file mode 100644 index 0000000000..18d43085f5 --- /dev/null +++ b/src/modules/data/board/fixSamePositionDnD.composable.ts @@ -0,0 +1,29 @@ +import { reactive } from "vue"; + +const renderKeyList = reactive>({}); + +const getMaxRenderKey = () => { + return Object.values(renderKeyList).reduce((a, b) => Math.max(a, b), 0); +}; + +/* + * This artificial generating renderKey forces a component to re-render by changing the element key. + * It is used to fix issue described on https://ticketsystem.dbildungscloud.de/browse/BC-7806 + * + * This solution is based on the article on https://michaelnthiessen.com/force-re-render/ + */ +export const useForceRender = (id: string) => { + const generateRenderKey = () => { + renderKeyList[id] = getMaxRenderKey() + 1; + }; + + const getRenderKey = (): number => { + if (!renderKeyList[id]) generateRenderKey(); + return renderKeyList[id]; + }; + + return { + generateRenderKey, + getRenderKey, + }; +}; diff --git a/src/modules/data/board/fixSamePositionDnD.composable.unit.ts b/src/modules/data/board/fixSamePositionDnD.composable.unit.ts new file mode 100644 index 0000000000..ed9ea1e6c7 --- /dev/null +++ b/src/modules/data/board/fixSamePositionDnD.composable.unit.ts @@ -0,0 +1,16 @@ +import { useForceRender } from "./fixSamePositionDnD.composable"; + +describe("fixSamePositionDnD", () => { + it("should set render key to the list", () => { + const { getRenderKey } = useForceRender("test-id-#1"); + const renderKey = getRenderKey(); + expect(renderKey).toBe(1); + }); + + it('should increase the render key when "generateRenderKey" is called', () => { + const { generateRenderKey, getRenderKey } = useForceRender("test-id-#1"); + expect(getRenderKey()).toBe(1); + generateRenderKey(); + expect(getRenderKey()).toBe(2); + }); +}); diff --git a/src/modules/data/board/index.ts b/src/modules/data/board/index.ts index 860ce2ee41..a2a0c8d797 100644 --- a/src/modules/data/board/index.ts +++ b/src/modules/data/board/index.ts @@ -9,6 +9,7 @@ import * as cardActions from "./cardActions/cardActions"; import { useSocketConnection } from "./socket/socket"; import { useCardStore } from "./Card.store"; import { useBoardInactivity } from "./boardInactivity.composable"; +import { useForceRender } from "./fixSamePositionDnD.composable"; export { boardActions, @@ -20,6 +21,7 @@ export { useCardStore, useContentElementState, useEditMode, + useForceRender, useSharedEditMode, useSharedBoardPageInformation, useSocketConnection, diff --git a/src/modules/feature/board/board/BoardColumn.unit.ts b/src/modules/feature/board/board/BoardColumn.unit.ts index 41639ce20a..0e6e310316 100644 --- a/src/modules/feature/board/board/BoardColumn.unit.ts +++ b/src/modules/feature/board/board/BoardColumn.unit.ts @@ -4,7 +4,11 @@ import { envsFactory, } from "@@/tests/test-utils/factory"; import { shallowMount } from "@vue/test-utils"; -import { useBoardPermissions, useBoardStore } from "@data-board"; +import { + useBoardPermissions, + useBoardStore, + useForceRender, +} from "@data-board"; import { BoardPermissionChecks, defaultPermissions, @@ -32,8 +36,12 @@ const mockedUserPermissions = jest.mocked(useBoardPermissions); jest.mock("@util-board"); const mockedUseBoardNotifier = jest.mocked(useBoardNotifier); +jest.mock("@data-board/fixSamePositionDnD.composable"); +const mockedUseForceRender = jest.mocked(useForceRender); + describe("BoardColumn", () => { let mockedBoardNotifierCalls: DeepMocked>; + let mockedUseForceRenderHandler: ReturnType; beforeEach(() => { setupStores({ envConfigModule: EnvConfigModule }); @@ -45,6 +53,10 @@ describe("BoardColumn", () => { mockedBoardNotifierCalls = createMock>(); mockedUseBoardNotifier.mockReturnValue(mockedBoardNotifierCalls); + + mockedUseForceRenderHandler = + createMock>(); + mockedUseForceRender.mockReturnValue(mockedUseForceRenderHandler); }); const setup = (options?: { @@ -93,6 +105,11 @@ describe("BoardColumn", () => { const { wrapper } = setup(); expect(wrapper.findComponent(BoardColumnVue).exists()).toBe(true); }); + + it("should trigger 'getRenderKey' method", () => { + setup(); + expect(mockedUseForceRenderHandler.getRenderKey).toHaveBeenCalled(); + }); }); describe("when a card moved ", () => { diff --git a/src/modules/feature/board/board/BoardColumn.vue b/src/modules/feature/board/board/BoardColumn.vue index fffefb5868..3b809a076e 100644 --- a/src/modules/feature/board/board/BoardColumn.vue +++ b/src/modules/feature/board/board/BoardColumn.vue @@ -1,5 +1,5 @@ diff --git a/src/modules/ui/board/BoardMenuActionDelete.vue b/src/modules/ui/board/BoardMenuActionDelete.vue index 19e954740d..478e2b2ba1 100644 --- a/src/modules/ui/board/BoardMenuActionDelete.vue +++ b/src/modules/ui/board/BoardMenuActionDelete.vue @@ -26,23 +26,23 @@ const emit = defineEmits(["click"]); const scope = injectStrict(MENU_SCOPE); const { askDeleteConfirmation } = useDeleteConfirmationDialog(); -const getLanguageKeyTypeName = (scope: string) => { - switch (scope) { - case "column": - return "components.boardColumn"; - case "card": - return "components.boardCard"; - case "element": - return "components.boardElement"; - default: - return "components.board"; - } +const languageKeyForScopeType: Record = { + board: "components.board", + column: "components.boardColumn", + card: "components.boardCard", + collaborativeTextEditorElement: + "components.cardElement.collaborativeTextEditorElement", + drawingElement: "components.cardElement.drawingElement", + externalToolElement: "components.cardElement.externalToolElement", + fileElement: "components.cardElement.fileElement", + linkElement: "components.cardElement.LinkElement", + submissionElement: "components.cardElement.submissionElement", }; const onClick = (): void => { const promise = askDeleteConfirmation( props.name, - getLanguageKeyTypeName(scope) + languageKeyForScopeType[scope] ); emit("click", promise); diff --git a/src/modules/ui/board/board-menu-scope.ts b/src/modules/ui/board/board-menu-scope.ts index 6290c635a8..bda0b49fbe 100644 --- a/src/modules/ui/board/board-menu-scope.ts +++ b/src/modules/ui/board/board-menu-scope.ts @@ -1 +1,10 @@ -export type BoardMenuScope = "element" | "card" | "column" | "board"; +export type BoardMenuScope = + | "card" + | "column" + | "board" + | "collaborativeTextEditorElement" + | "drawingElement" + | "externalToolElement" + | "fileElement" + | "linkElement" + | "submissionElement"; From 85a4120306b4a2f72df68c9d486e87d3dd98379b Mon Sep 17 00:00:00 2001 From: hoeppner-dataport <106819770+hoeppner-dataport@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:47:47 +0200 Subject: [PATCH 8/8] BC-7830 - board loadtests merge (#3369) Problems identified during loadtests * fix: avoid fetchCard of newly created card * fix: richtext live updating --- src/modules/data/board/Board.store.ts | 4 +++ src/modules/data/board/Board.store.unit.ts | 32 +++++++++++++++---- src/modules/data/board/Card.store.ts | 8 +++++ src/modules/data/board/Card.store.unit.ts | 21 ++++++++++++ .../RichTextContentElement.vue | 2 +- .../feature/board/board/BoardColumn.unit.ts | 13 ++++++-- src/modules/feature/board/card/CardHost.vue | 4 ++- 7 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/modules/data/board/Board.store.ts b/src/modules/data/board/Board.store.ts index 92ca20ddf1..5e0a96fa11 100644 --- a/src/modules/data/board/Board.store.ts +++ b/src/modules/data/board/Board.store.ts @@ -6,6 +6,7 @@ import { useBoardSocketApi } from "./boardActions/boardSocketApi.composable"; import { useBoardFocusHandler } from "./BoardFocusHandler.composable"; import { useSharedEditMode } from "./EditMode.composable"; import { envConfigModule } from "@/store"; +import { useCardStore } from "./Card.store"; import { CreateCardRequestPayload, CreateCardSuccessPayload, @@ -30,6 +31,7 @@ import { import { DeleteCardSuccessPayload } from "./cardActions/cardActionPayload"; export const useBoardStore = defineStore("boardStore", () => { + const cardStore = useCardStore(); const board = ref(undefined); const isLoading = ref(false); const { setFocus } = useBoardFocusHandler(); @@ -94,6 +96,8 @@ export const useBoardStore = defineStore("boardStore", () => { const { newCard } = payload; + cardStore.createCardSuccess(payload); + const columnIndex = board.value.columns.findIndex( (column) => column.id === payload.columnId ); diff --git a/src/modules/data/board/Board.store.unit.ts b/src/modules/data/board/Board.store.unit.ts index 079bab5af0..d7c2d8a26b 100644 --- a/src/modules/data/board/Board.store.unit.ts +++ b/src/modules/data/board/Board.store.unit.ts @@ -7,22 +7,20 @@ import { envsFactory, } from "@@/tests/test-utils/factory"; import { createMock, DeepMocked } from "@golevelup/ts-jest"; -import { useBoardNotifier } from "@util-board"; +import { useBoardNotifier, useSharedLastCreatedElement } from "@util-board"; import { createPinia, setActivePinia } from "pinia"; -import { ref } from "vue"; +import { computed, ref } from "vue"; import { useBoardStore } from "./Board.store"; import { useSharedEditMode } from "./EditMode.composable"; import { envConfigModule } from "@/store"; import EnvConfigModule from "@/store/env-config"; import { cardResponseFactory } from "@@/tests/test-utils/factory/cardResponseFactory"; import setupStores from "@@/tests/test-utils/setupStores"; -import { useSocketConnection } from "@data-board"; -import { useI18n } from "vue-i18n"; +import { useCardStore, useSocketConnection } from "@data-board"; import { useBoardRestApi } from "./boardActions/boardRestApi.composable"; import { useBoardSocketApi } from "./boardActions/boardSocketApi.composable"; - -jest.mock("vue-i18n"); -(useI18n as jest.Mock).mockReturnValue({ t: (key: string) => key }); +import { mockedPiniaStoreTyping } from "@@/tests/test-utils"; +import { useCardSocketApi } from "./cardActions/cardSocketApi.composable"; jest.mock("./boardActions/boardSocketApi.composable"); const mockedUseBoardSocketApi = jest.mocked(useBoardSocketApi); @@ -35,6 +33,9 @@ const mockedSharedEditMode = jest.mocked(useSharedEditMode); jest.mock("@util-board"); const mockedUseBoardNotifier = jest.mocked(useBoardNotifier); +const mockUseSharedLastCreatedElement = jest.mocked( + useSharedLastCreatedElement +); jest.mock("@/components/error-handling/ErrorHandler.composable"); const mockedUseErrorHandler = jest.mocked(useErrorHandler); @@ -42,6 +43,9 @@ const mockedUseErrorHandler = jest.mocked(useErrorHandler); jest.mock("@data-board/socket/socket"); const mockedUseSocketConnection = jest.mocked(useSocketConnection); +jest.mock("./cardActions/cardSocketApi.composable"); +const mockedUseCardSocketApi = jest.mocked(useCardSocketApi); + describe("BoardStore", () => { let mockedBoardNotifierCalls: DeepMocked>; let mockedErrorHandlerCalls: DeepMocked>; @@ -50,6 +54,9 @@ describe("BoardStore", () => { >; let mockedSocketApiActions: DeepMocked>; let mockedBoardRestApiActions: DeepMocked>; + let mockedCardSocketApiActions: DeepMocked< + ReturnType + >; let setEditModeId: jest.Mock; beforeEach(() => { @@ -74,11 +81,20 @@ describe("BoardStore", () => { createMock>(); mockedUseBoardRestApi.mockReturnValue(mockedBoardRestApiActions); + mockedCardSocketApiActions = + createMock>(); + mockedUseCardSocketApi.mockReturnValue(mockedCardSocketApiActions); + setEditModeId = jest.fn(); mockedSharedEditMode.mockReturnValue({ setEditModeId, editModeId: ref(undefined), }); + + mockUseSharedLastCreatedElement.mockReturnValue({ + lastCreatedElementId: computed(() => "element-id"), + resetLastCreatedElementId: jest.fn(), + }); }); const setup = (options?: { createBoard?: boolean; socketFlag?: boolean }) => { @@ -107,6 +123,8 @@ describe("BoardStore", () => { boardStore.board = board; } + mockedPiniaStoreTyping(useCardStore); + return { boardStore, board, firstColumn, secondColumn, cards }; }; diff --git a/src/modules/data/board/Card.store.ts b/src/modules/data/board/Card.store.ts index 55a928f395..32bb60395d 100644 --- a/src/modules/data/board/Card.store.ts +++ b/src/modules/data/board/Card.store.ts @@ -4,6 +4,7 @@ import { envConfigModule } from "@/store"; import { useBoardFocusHandler } from "./BoardFocusHandler.composable"; import { CardResponse, ContentElementType } from "@/serverApi/v3"; +import { CreateCardSuccessPayload } from "./boardActions/boardActionPayload"; import { CreateElementSuccessPayload, DeleteCardSuccessPayload, @@ -48,6 +49,12 @@ export const useCardStore = defineStore("cardStore", () => { return cards.value[cardId]; }; + const createCardSuccess = (payload: CreateCardSuccessPayload) => { + if (payload.newCard) { + cards.value[payload.newCard.id] = payload.newCard; + } + }; + const updateCardTitleRequest = socketOrRest.updateCardTitleRequest; const updateCardTitleSuccess = async ( @@ -184,6 +191,7 @@ export const useCardStore = defineStore("cardStore", () => { }; return { + createCardSuccess, createElementRequest, createElementSuccess, deleteElementRequest, diff --git a/src/modules/data/board/Card.store.unit.ts b/src/modules/data/board/Card.store.unit.ts index 143a93112f..d9591728a4 100644 --- a/src/modules/data/board/Card.store.unit.ts +++ b/src/modules/data/board/Card.store.unit.ts @@ -161,6 +161,27 @@ describe("CardStore", () => { }); }); + describe("createCardSuccess", () => { + describe("when card is provided", () => { + it("should add the card to the store", async () => { + const { cardStore } = setup(); + + const newCardId = "idNewCard"; + const newCard = cardResponseFactory.build({ id: newCardId }); + await cardStore.createCardSuccess({ + newCard, + columnId: "any-column-id", + isOwnAction: true, + }); + + expect(cardStore.cards[newCardId]).toBeDefined(); + expect(cardStore.cards[newCardId]).toEqual( + expect.objectContaining({ id: newCardId, elements: [] }) + ); + }); + }); + }); + describe("deleteCardRequest", () => { it("should call socket Api if feature flag is enabled", () => { const { cardStore, cardId } = setup(true); diff --git a/src/modules/feature/board-text-element/RichTextContentElement.vue b/src/modules/feature/board-text-element/RichTextContentElement.vue index c596c60447..2c697eedde 100644 --- a/src/modules/feature/board-text-element/RichTextContentElement.vue +++ b/src/modules/feature/board-text-element/RichTextContentElement.vue @@ -3,7 +3,7 @@ "element-id"), + resetLastCreatedElementId: jest.fn(), +}); + describe("BoardColumn", () => { let mockedBoardNotifierCalls: DeepMocked>; let mockedUseForceRenderHandler: ReturnType; diff --git a/src/modules/feature/board/card/CardHost.vue b/src/modules/feature/board/card/CardHost.vue index ae7218efa4..bca6ebc900 100644 --- a/src/modules/feature/board/card/CardHost.vue +++ b/src/modules/feature/board/card/CardHost.vue @@ -231,7 +231,9 @@ export default defineComponent({ }); onMounted(async () => { - await cardStore.fetchCardRequest({ cardIds: [cardId.value] }); + if (card.value === undefined) { + await cardStore.fetchCardRequest({ cardIds: [cardId.value] }); + } }); return {