diff --git a/package-lock.json b/package-lock.json index 475ad48429..03bde2c9f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@vuelidate/validators": "^2.0.4", "@vueuse/components": "^10.11.0", "@vueuse/core": "^10.11.0", + "@vueuse/integrations": "^12.0.0", "axios": "^1.7.4", "cross-env": "^7.0.3", "dayjs": "^1.11.10", @@ -4483,53 +4484,53 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.11.tgz", - "integrity": "sha512-PwAdxs7/9Hc3ieBO12tXzmTD+Ln4qhT/56S+8DvrrZ4kLDn4Z/AMUr8tXJD0axiJBS0RKIoNaR0yMuQB9v9Udg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", "license": "MIT", "dependencies": { "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.11", + "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.11.tgz", - "integrity": "sha512-pyGf8zdbDDRkBrEzf8p7BQlMKNNF5Fk/Cf/fQ6PiUz9at4OaUfyXW0dGJTo2Vl1f5U9jSLCNf0EZJEogLXoeew==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.11", - "@vue/shared": "3.5.11" + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.11.tgz", - "integrity": "sha512-gsbBtT4N9ANXXepprle+X9YLg2htQk1sqH/qGJ/EApl+dgpUBdTv3yP7YlR535uHZY3n6XaR0/bKo0BgwwDniw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", "license": "MIT", "dependencies": { "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.11", - "@vue/compiler-dom": "3.5.11", - "@vue/compiler-ssr": "3.5.11", - "@vue/shared": "3.5.11", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", - "postcss": "^8.4.47", + "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.11.tgz", - "integrity": "sha512-P4+GPjOuC2aFTk1Z4WANvEhyOykcvEd5bIj2KVNGKGfM745LaXGr++5njpdBTzVz5pZifdlR1kpYSJJpIlSePA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.11", - "@vue/shared": "3.5.11" + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/devtools-api": { @@ -4565,53 +4566,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.11.tgz", - "integrity": "sha512-Nqo5VZEn8MJWlCce8XoyVqHZbd5P2NH+yuAaFzuNSR96I+y1cnuUiq7xfSG+kyvLSiWmaHTKP1r3OZY4mMD50w==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.11" + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.11.tgz", - "integrity": "sha512-7PsxFGqwfDhfhh0OcDWBG1DaIQIVOLgkwA5q6MtkPiDFjp5gohVnJEahSktwSFLq7R5PtxDKy6WKURVN1UDbzA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.11", - "@vue/shared": "3.5.11" + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.11.tgz", - "integrity": "sha512-GNghjecT6IrGf0UhuYmpgaOlN7kxzQBhxWEn08c/SQDxv1yy4IXI1bn81JgEpQ4IXjRxWtPyI8x0/7TF5rPfYQ==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.11", - "@vue/runtime-core": "3.5.11", - "@vue/shared": "3.5.11", + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.11.tgz", - "integrity": "sha512-cVOwYBxR7Wb1B1FoxYvtjJD8X/9E5nlH4VSkJy2uMA1MzYNdzAAB//l8nrmN9py/4aP+3NjWukf9PZ3TeWULaA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.11", - "@vue/shared": "3.5.11" + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { - "vue": "3.5.11" + "vue": "3.5.13" } }, "node_modules/@vue/shared": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.11.tgz", - "integrity": "sha512-W8GgysJVnFo81FthhzurdRAWP/byq3q2qIw70e0JWblzVhjgOMiC2GyovXrZTFQJnFVryYaKGP3Tc9vYzYm6PQ==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "license": "MIT" }, "node_modules/@vue/test-utils": { @@ -4790,6 +4791,108 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@vueuse/integrations": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.0.0.tgz", + "integrity": "sha512-M16fkVp+i4je75I7uvifMbJKHFrjx2+0LuHEH9++iPJ11zc4SRy5NdRN0z2NR+a54eQ5Gs2Ds7pby5ST96zxCA==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.0.0", + "@vueuse/shared": "12.0.0", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations/node_modules/@vueuse/core": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.0.0.tgz", + "integrity": "sha512-C12RukhXiJCbx4MGhjmd/gH52TjJsc3G0E0kQj/kb19H3Nt6n1CA4DRWuTdWWcaFRdlTe0npWDS942mvacvNBw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "12.0.0", + "@vueuse/shared": "12.0.0", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations/node_modules/@vueuse/metadata": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.0.0.tgz", + "integrity": "sha512-Yzimd1D3sjxTDOlF05HekU5aSGdKjxhuhRFHA7gDWLn57PRbBIh+SF5NmjhJ0WRgF3my7T8LBucyxdFJjIfRJQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations/node_modules/@vueuse/shared": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.0.0.tgz", + "integrity": "sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==", + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@vueuse/metadata": { "version": "10.11.1", "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", @@ -8618,6 +8721,17 @@ "dev": true, "license": "ISC" }, + "node_modules/focus-trap": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.2.tgz", + "integrity": "sha512-9FhUxK1hVju2+AiQIDJ5Dd//9R2n2RAfJ0qfhF4IHGHgcoEUTMpbTeG/zbEuwaiYXfuAH6XE0/aCyxDdRM+W5w==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tabbable": "^6.2.0" + } + }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", @@ -13654,9 +13768,9 @@ "license": "MIT" }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { @@ -13825,9 +13939,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -13845,7 +13959,7 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -15452,6 +15566,14 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -16412,16 +16534,16 @@ } }, "node_modules/vue": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.11.tgz", - "integrity": "sha512-/8Wurrd9J3lb72FTQS7gRMNQD4nztTtKPmuDuPuhqXmmpD6+skVjAeahNpVzsuky6Sy9gy7wn8UadqPtt9SQIg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.11", - "@vue/compiler-sfc": "3.5.11", - "@vue/runtime-dom": "3.5.11", - "@vue/server-renderer": "3.5.11", - "@vue/shared": "3.5.11" + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" diff --git a/package.json b/package.json index 3569a7adeb..c50ba90ee3 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@vuelidate/validators": "^2.0.4", "@vueuse/components": "^10.11.0", "@vueuse/core": "^10.11.0", + "@vueuse/integrations": "^12.0.0", "axios": "^1.7.4", "cross-env": "^7.0.3", "dayjs": "^1.11.10", diff --git a/src/modules/feature/room/RoomMembers/AddMembers.unit.ts b/src/modules/feature/room/RoomMembers/AddMembers.unit.ts index 78a77f9695..a965da19c8 100644 --- a/src/modules/feature/room/RoomMembers/AddMembers.unit.ts +++ b/src/modules/feature/room/RoomMembers/AddMembers.unit.ts @@ -3,15 +3,16 @@ import { createTestingVuetify, } from "@@/tests/test-utils/setup"; import AddMembers from "./AddMembers.vue"; -import { RoleName, SchoolForExternalInviteResponse } from "@/serverApi/v3"; +import { RoleName } from "@/serverApi/v3"; import { AUTH_MODULE_KEY } from "@/utils/inject"; import { authModule } from "@/store"; -import { nextTick } from "vue"; import { roomMemberListFactory, roomMemberSchoolResponseFactory, } from "@@/tests/test-utils"; -import { RoomMember } from "@data-room"; +import { VueWrapper } from "@vue/test-utils"; +import { VAutocomplete } from "vuetify/lib/components/index.mjs"; +import { useFocusTrap } from "@vueuse/integrations/useFocusTrap"; jest.mock("@/store/store-accessor", () => { return { @@ -22,12 +23,31 @@ jest.mock("@/store/store-accessor", () => { }; }); -const mockPotentialMembers = roomMemberListFactory.buildList(3); -const roomMembersSchools = roomMemberSchoolResponseFactory.buildList(3); - +jest.mock("@vueuse/integrations/useFocusTrap", () => { + return { + ...jest.requireActual("@vueuse/integrations/useFocusTrap"), + useFocusTrap: jest.fn(), + }; +}); describe("AddMembers", () => { + let wrapper: VueWrapper>; + let pauseMock: jest.Mock; + let unpauseMock: jest.Mock; + + beforeEach(() => { + pauseMock = jest.fn(); + unpauseMock = jest.fn(); + (useFocusTrap as jest.Mock).mockReturnValue({ + pause: pauseMock, + unpause: unpauseMock, + }); + }); + const setup = () => { - const wrapper = mount(AddMembers, { + const potentialRoomMembers = roomMemberListFactory.buildList(3); + const roomMembersSchools = roomMemberSchoolResponseFactory.buildList(3); + wrapper = mount(AddMembers, { + attachTo: document.body, global: { plugins: [createTestingVuetify(), createTestingI18n()], provide: { @@ -35,169 +55,285 @@ describe("AddMembers", () => { }, }, props: { - memberList: mockPotentialMembers, + memberList: potentialRoomMembers, schools: roomMembersSchools, }, }); - const wrapperVM = wrapper.vm as unknown as { - memberList: RoomMember[]; - preSelectedRole: RoleName; - selectedUsers: RoomMember[]; - schoolList: SchoolForExternalInviteResponse[]; - roles: { id: string; name: string }[]; + return { + wrapper, + potentialRoomMembers, + roomMembersSchools, }; - - return { wrapper, wrapperVM }; }; + afterEach(() => { + wrapper.unmount(); // necessary due focus trap + }); + describe("when component is mounted", () => { it("should render component", () => { - const { wrapper, wrapperVM } = setup(); + const { wrapper, potentialRoomMembers, roomMembersSchools } = setup(); expect(wrapper.exists()).toBe(true); - expect(wrapper.findComponent(AddMembers)).toBeTruthy(); - expect(wrapperVM.memberList).toStrictEqual(mockPotentialMembers); - expect(wrapperVM.schoolList).toStrictEqual(roomMembersSchools); - expect(wrapperVM.schoolList).toHaveLength(3); + expect(wrapper.props()).toEqual({ + memberList: potentialRoomMembers, + schools: roomMembersSchools, + }); }); - describe("AutoComplete components", () => { - it("should render Autocomplete components", () => { + describe("Autocomplete components", () => { + it("should render autocomplete components", () => { const { wrapper } = setup(); - const autoCompleteComponents = wrapper.findAllComponents({ - name: "v-autocomplete", - }); + const autoCompleteComponents = wrapper.findAllComponents(VAutocomplete); expect(autoCompleteComponents).toHaveLength(3); }); it("should have proper props for autoCompleteSchool component", () => { - const { wrapper, wrapperVM } = setup(); - const schoolComponent = wrapper.findComponent({ - name: "v-autocomplete", + const { wrapper, roomMembersSchools } = setup(); + const schoolComponent = wrapper.getComponent({ ref: "autoCompleteSchool", }); - expect(schoolComponent).toBeTruthy(); expect(schoolComponent.props("items")).toStrictEqual( - wrapperVM.schoolList + roomMembersSchools ); expect(schoolComponent.props("modelValue")).toBe( - wrapperVM.schoolList[0].id + roomMembersSchools[0].id ); }); - it("should have proper props for autoCompleteRole component", () => { - const { wrapper, wrapperVM } = setup(); - const roleComponent = wrapper.findComponent({ - name: "v-autocomplete", + const { wrapper } = setup(); + + const roles = [ + { id: RoleName.Roomeditor, name: "common.labels.teacher" }, + ]; + + const roleComponent = wrapper.getComponent({ ref: "autoCompleteRole", }); - expect(roleComponent).toBeTruthy(); - expect(roleComponent.props("items")).toStrictEqual(wrapperVM.roles); - expect(roleComponent.props("modelValue")).toBe(wrapperVM.roles[0].id); + expect(roleComponent.props("items")).toStrictEqual(roles); + expect(roleComponent.props("modelValue")).toBe(roles[0].id); }); it("should have proper props for autoCompleteUsers component", () => { - const { wrapper, wrapperVM } = setup(); - const userComponent = wrapper.findComponent({ - name: "v-autocomplete", + const { wrapper, potentialRoomMembers } = setup(); + const userComponent = wrapper.getComponent({ ref: "autoCompleteUsers", }); - expect(userComponent).toBeTruthy(); expect(userComponent.props("items")).toStrictEqual( - wrapperVM.memberList + potentialRoomMembers ); expect(userComponent.props("modelValue")).toHaveLength(0); }); }); }); + describe("when school is changed", () => { + it("should emit 'update:role'", async () => { + const { wrapper, roomMembersSchools } = setup(); + const selectedSchool = roomMembersSchools[1].id; + const schoolComponent = wrapper.getComponent({ + ref: "autoCompleteSchool", + }); + + await schoolComponent.setValue(selectedSchool); + + expect(wrapper.emitted("update:role")).toHaveLength(1); + expect(wrapper.emitted("update:role")![0]).toStrictEqual([ + { role: RoleName.Roomeditor, schoolId: selectedSchool }, + ]); + }); + + it("should set the role to room editor", async () => { + const { wrapper } = setup(); + const schoolComponent = wrapper.getComponent({ + ref: "autoCompleteSchool", + }); + + await schoolComponent.setValue("schoolId"); + + const roleComponent = wrapper.getComponent({ + ref: "autoCompleteRole", + }); + + expect(roleComponent.props("modelValue")).toBe(RoleName.Roomeditor); + }); + + it("should reset selectedUsers", async () => { + const { wrapper } = setup(); + const schoolComponent = wrapper.getComponent({ + ref: "autoCompleteSchool", + }); + + const userComponent = wrapper.getComponent({ + ref: "autoCompleteUsers", + }); + + await schoolComponent.setValue("schoolId"); + + expect(userComponent.props("modelValue")).toEqual([]); + }); + }); + describe("when userRole is changed", () => { it("should emit the userRole", async () => { - const { wrapper } = setup(); - const roleComponent = wrapper.findComponent({ - name: "v-autocomplete", + const { wrapper, roomMembersSchools } = setup(); + const selectedRole = RoleName.Roomeditor; + const roleComponent = wrapper.getComponent({ ref: "autoCompleteRole", }); - expect(roleComponent).toBeTruthy(); - await roleComponent.vm.$emit("update:modelValue", RoleName.Roomviewer); - await nextTick(); + await roleComponent.setValue(selectedRole); + expect(wrapper.emitted("update:role")).toHaveLength(1); expect(wrapper.emitted("update:role")![0]).toStrictEqual([ - { role: RoleName.Roomviewer, schoolId: roomMembersSchools[0].id }, + { role: selectedRole, schoolId: roomMembersSchools[0].id }, ]); }); + + it("should reset selectedUsers", async () => { + const { wrapper } = setup(); + const roleComponent = wrapper.getComponent({ + ref: "autoCompleteRole", + }); + + const userComponent = wrapper.getComponent({ + ref: "autoCompleteUsers", + }); + + await roleComponent.setValue(RoleName.Roomeditor); + + expect(wrapper.emitted()).toHaveProperty("update:role"); + expect(userComponent.props("modelValue")).toEqual([]); + }); }); describe("when user(s) selected", () => { it("should add user to selectedUsers", async () => { - const { wrapper, wrapperVM } = setup(); - const userComponent = wrapper.findComponent({ - name: "v-autocomplete", + const { wrapper, potentialRoomMembers } = setup(); + const userComponent = wrapper.getComponent({ ref: "autoCompleteUsers", }); - expect(userComponent).toBeTruthy(); - await userComponent.vm.$emit("update:modelValue", [ - mockPotentialMembers[0].userId, - mockPotentialMembers[1].userId, + await userComponent.setValue([ + potentialRoomMembers[0].userId, + potentialRoomMembers[1].userId, ]); - await nextTick(); - expect(wrapperVM.selectedUsers).toHaveLength(2); + + expect(userComponent.props("modelValue")).toHaveLength(2); expect(userComponent.props("modelValue")).toStrictEqual([ - mockPotentialMembers[0].userId, - mockPotentialMembers[1].userId, + potentialRoomMembers[0].userId, + potentialRoomMembers[1].userId, ]); }); }); describe("when add button clicked", () => { it("should emit the selectedUsers", async () => { - const { wrapper, wrapperVM } = setup(); - const userComponent = wrapper.findComponent({ - name: "v-autocomplete", + const { wrapper, potentialRoomMembers } = setup(); + const userComponent = wrapper.getComponent({ ref: "autoCompleteUsers", }); - expect(userComponent).toBeTruthy(); - await userComponent.vm.$emit("update:modelValue", [ - mockPotentialMembers[0].userId, - mockPotentialMembers[1].userId, - ]); - await nextTick(); + const selectedUsers = [ + potentialRoomMembers[0].userId, + potentialRoomMembers[1].userId, + ]; + userComponent.setValue(selectedUsers); - const addButton = wrapper.findComponent({ - name: "v-btn", + const addButton = wrapper.getComponent({ ref: "addButton", }); - expect(addButton).toBeTruthy(); await addButton.trigger("click"); - await nextTick(); + expect(wrapper.emitted("add:members")).toHaveLength(1); - expect(wrapper.emitted("add:members")![0]).toStrictEqual([ - wrapperVM.selectedUsers, - ]); + expect(wrapper.emitted("add:members")![0]).toStrictEqual([selectedUsers]); expect(wrapper.emitted("close")).toHaveLength(1); }); + + it("should emit 'close'", async () => { + const { wrapper, potentialRoomMembers } = setup(); + const userComponent = wrapper.getComponent({ + ref: "autoCompleteUsers", + }); + + const selectedUsers = [ + potentialRoomMembers[0].userId, + potentialRoomMembers[1].userId, + ]; + userComponent.setValue(selectedUsers); + + const addButton = wrapper.getComponent({ + ref: "addButton", + }); + await addButton.trigger("click"); + + expect(wrapper.emitted()).toHaveProperty("close"); + }); }); describe("when cancel button clicked", () => { it("should emit the selectedUsers", async () => { const { wrapper } = setup(); - const cancelButton = wrapper.findComponent({ - name: "v-btn", + const cancelButton = wrapper.getComponent({ ref: "cancelButton", }); - expect(cancelButton).toBeTruthy(); + await cancelButton.trigger("click"); - await nextTick(); - expect(wrapper.emitted("close")).toHaveLength(1); + + expect(wrapper.emitted()).toHaveProperty("close"); + }); + }); + + describe("focus trap", () => { + it("should pause focus trap when any autocomplete menu is open", async () => { + const { wrapper } = setup(); + const schoolComponent = wrapper.getComponent({ + ref: "autoCompleteSchool", + }); + + schoolComponent.vm.menu = true; + + expect(pauseMock).toHaveBeenCalledTimes(1); + }); + + it("should unpause focus trap when all autocomplete menus are closed", async () => { + const { wrapper } = setup(); + const schoolComponent = wrapper.getComponent({ + ref: "autoCompleteSchool", + }); + + schoolComponent.vm.menu = true; + expect(pauseMock).toHaveBeenCalledTimes(1); + + schoolComponent.vm.menu = false; + expect(unpauseMock).toHaveBeenCalled(); + }); + + it("should not unpause focus trap when a autocomplete is closed while another one is opened", async () => { + // this happens when user switches between autocomplete components for brief moment both are treated as open + const { wrapper } = setup(); + const schoolComponent = wrapper.getComponent({ + ref: "autoCompleteSchool", + }); + + const roleComponent = wrapper.getComponent({ + ref: "autoCompleteRole", + }); + + schoolComponent.vm.menu = true; + roleComponent.vm.menu = true; + + expect(pauseMock).toHaveBeenCalled(); + expect(unpauseMock).not.toHaveBeenCalled(); + + schoolComponent.vm.menu = false; + expect(unpauseMock).not.toHaveBeenCalled(); }); }); }); diff --git a/src/modules/feature/room/RoomMembers/AddMembers.vue b/src/modules/feature/room/RoomMembers/AddMembers.vue index 3a70ed3e98..a8ab6f64db 100644 --- a/src/modules/feature/room/RoomMembers/AddMembers.vue +++ b/src/modules/feature/room/RoomMembers/AddMembers.vue @@ -1,5 +1,5 @@