diff --git a/src/__mocks__/router.js b/src/__mocks__/router.js
index f62163e43a8..d2b86effaa8 100644
--- a/src/__mocks__/router.js
+++ b/src/__mocks__/router.js
@@ -2,51 +2,9 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import VueRouter from 'vue-router'
+import { createTalkRouter } from '../router/router.ts'
-const Stub = {
- name: 'Stub',
- template: '
',
-}
+const router = createTalkRouter()
+router.addRoute({ path: '/', name: 'none', redirect: '/apps/spreed', component: { template: '' } })
-export default new VueRouter({
- linkActiveClass: 'active',
- routes: [
- {
- path: '/apps/spreed',
- name: 'root',
- component: Stub,
- props: true,
- },
- {
- path: '/apps/spreed/not-found',
- name: 'notfound',
- component: Stub,
- props: true,
- },
- {
- path: '/apps/spreed/forbidden',
- name: 'forbidden',
- component: Stub,
- props: true,
- },
- {
- path: '/apps/spreed/duplicate-session',
- name: 'duplicatesession',
- component: Stub,
- props: true,
- },
- {
- path: '/call/:token',
- name: 'conversation',
- component: Stub,
- props: true,
- },
- {
- path: '/call/:token/recording',
- name: 'recording',
- component: Stub,
- props: true,
- },
- ],
-})
+export default router
diff --git a/src/components/AvatarWrapper/AvatarWrapper.spec.js b/src/components/AvatarWrapper/AvatarWrapper.spec.js
index 1a552625941..74b7ce0c448 100644
--- a/src/components/AvatarWrapper/AvatarWrapper.spec.js
+++ b/src/components/AvatarWrapper/AvatarWrapper.spec.js
@@ -83,7 +83,7 @@ describe('AvatarWrapper.vue', () => {
expect(avatar.props('displayName')).toBe(USER_NAME)
expect(avatar.props('hideStatus')).toBe(false)
expect(avatar.props('verboseStatus')).toBe(true)
- expect(avatar.props('preloadedUserStatus')).toBe(PRELOADED_USER_STATUS)
+ expect(avatar.props('preloadedUserStatus')).toStrictEqual(PRELOADED_USER_STATUS)
expect(avatar.props('size')).toBe(AVATAR.SIZE.DEFAULT)
})
})
diff --git a/src/components/CallView/shared/VideoBottomBar.spec.js b/src/components/CallView/shared/VideoBottomBar.spec.js
index 5049f73e497..3584b4bf659 100644
--- a/src/components/CallView/shared/VideoBottomBar.spec.js
+++ b/src/components/CallView/shared/VideoBottomBar.spec.js
@@ -5,16 +5,16 @@
import { emit } from '@nextcloud/event-bus'
import { t } from '@nextcloud/l10n'
-import { shallowMount } from '@vue/test-utils'
+import { mount } from '@vue/test-utils'
import { cloneDeep } from 'lodash'
import { createPinia, setActivePinia } from 'pinia'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { createStore } from 'vuex'
import NcButton from '@nextcloud/vue/components/NcButton'
import IconAlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
-import IconHandBackLeftOutline from 'vue-material-design-icons/HandBackLeftOutline.vue'
+import IconHandBackLeft from 'vue-material-design-icons/HandBackLeft.vue'
+import IconVideo from 'vue-material-design-icons/Video.vue'
import IconVideoOffOutline from 'vue-material-design-icons/VideoOffOutline.vue'
-import IconVideoOutline from 'vue-material-design-icons/VideoOutline.vue'
import VideoBottomBar from './VideoBottomBar.vue'
import { CONVERSATION, PARTICIPANT } from '../../../constants.ts'
import storeConfig from '../../../store/storeConfig.js'
@@ -93,82 +93,60 @@ describe('VideoBottomBar.vue', () => {
vi.clearAllMocks()
})
+ /**
+ * Shared function to mount component
+ */
+ function mountVideoBottomBar(props) {
+ return mount(VideoBottomBar, {
+ global: {
+ plugins: [store],
+ },
+ props,
+ })
+ }
+
describe('unit tests', () => {
describe('render component', () => {
test('component renders properly', async () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
+ const wrapper = mountVideoBottomBar(componentProps)
expect(wrapper.exists()).toBeTruthy()
expect(wrapper.classes('wrapper')).toBeDefined()
})
test('component has class "wrapper--big" for main view', async () => {
componentProps.isBig = true
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
+ const wrapper = mountVideoBottomBar(componentProps)
expect(wrapper.exists()).toBeTruthy()
expect(wrapper.classes('wrapper--big')).toBeDefined()
})
test('component renders all indicators by default', async () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const indicators = wrapper.findAllComponents(NcButton)
expect(indicators).toHaveLength(3)
})
test('component does not render indicators for Screen.vue component', async () => {
componentProps.isScreen = true
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const indicators = wrapper.findAllComponents(NcButton)
expect(indicators).toHaveLength(0)
})
test('component does not show indicators after video overlay is off', async () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
componentProps.showVideoOverlay = false
await wrapper.setProps(cloneDeep(componentProps))
const indicators = wrapper.findAllComponents(NcButton)
- indicators.wrappers.forEach((indicator) => {
+ indicators.forEach((indicator) => {
expect(indicator.isVisible()).toBeFalsy()
})
})
test('component does not render anything when used in sidebar', async () => {
componentProps.isSidebar = true
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const participantName = wrapper.find('.participant-name')
expect(participantName.exists()).toBeFalsy()
@@ -179,13 +157,7 @@ describe('VideoBottomBar.vue', () => {
describe('render participant name', () => {
test('name is shown by default', () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const participantName = wrapper.find('.participant-name')
expect(participantName.isVisible()).toBeTruthy()
expect(participantName.text()).toBe(PARTICIPANT_NAME)
@@ -193,13 +165,7 @@ describe('VideoBottomBar.vue', () => {
test('name is not shown if all checks are falsy', () => {
componentProps.showVideoOverlay = false
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const participantName = wrapper.find('.participant-name')
expect(participantName.isVisible()).toBeFalsy()
})
@@ -209,21 +175,16 @@ describe('VideoBottomBar.vue', () => {
describe('connection failed indicator', () => {
test('indicator is not shown by default, other indicators are visible', () => {
componentProps.model.attributes.raisedHand.state = true
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
+ const wrapper = mountVideoBottomBar(componentProps)
const iceFailedIndicator = wrapper.findComponent(IconAlertCircleOutline)
expect(iceFailedIndicator.exists()).toBeFalsy()
- const raiseHandIndicator = wrapper.findComponent(IconHandBackLeftOutline)
+ const raiseHandIndicator = wrapper.findComponent(IconHandBackLeft)
expect(raiseHandIndicator.exists()).toBeTruthy()
const indicators = wrapper.findAllComponents(NcButton)
- indicators.wrappers.forEach((indicator) => {
+ indicators.forEach((indicator) => {
expect(indicator.isVisible()).toBeTruthy()
})
})
@@ -231,21 +192,16 @@ describe('VideoBottomBar.vue', () => {
test('indicator is shown when model prop is true, other indicators are hidden', () => {
componentProps.model.attributes.raisedHand.state = true
componentProps.model.attributes.connectionState = ConnectionState.FAILED_NO_RESTART
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
+ const wrapper = mountVideoBottomBar(componentProps)
const iceFailedIndicator = wrapper.findComponent(IconAlertCircleOutline)
expect(iceFailedIndicator.exists()).toBeTruthy()
- const raiseHandIndicator = wrapper.findComponent(IconHandBackLeftOutline)
+ const raiseHandIndicator = wrapper.findComponent(IconHandBackLeft)
expect(raiseHandIndicator.exists()).toBeFalsy()
const indicators = wrapper.findAllComponents(NcButton)
- indicators.wrappers.forEach((indicator) => {
+ indicators.forEach((indicator) => {
expect(indicator.isVisible()).toBeFalsy()
})
})
@@ -253,27 +209,17 @@ describe('VideoBottomBar.vue', () => {
describe('raise hand indicator', () => {
test('indicator is not shown by default', () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
+ const wrapper = mountVideoBottomBar(componentProps)
- const raiseHandIndicator = wrapper.findComponent(IconHandBackLeftOutline)
+ const raiseHandIndicator = wrapper.findComponent(IconHandBackLeft)
expect(raiseHandIndicator.exists()).toBeFalsy()
})
test('indicator is shown when model prop is true', () => {
componentProps.model.attributes.raisedHand.state = true
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
+ const wrapper = mountVideoBottomBar(componentProps)
- const raiseHandIndicator = wrapper.findComponent(IconHandBackLeftOutline)
+ const raiseHandIndicator = wrapper.findComponent(IconHandBackLeft)
expect(raiseHandIndicator.exists()).toBeTruthy()
})
})
@@ -282,37 +228,20 @@ describe('VideoBottomBar.vue', () => {
describe('render buttons', () => {
describe('audio indicator', () => {
test('button is rendered properly', () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
+ const wrapper = mountVideoBottomBar(componentProps)
const audioIndicator = findNcButton(wrapper, audioIndicatorAriaLabels)
expect(audioIndicator.exists()).toBeTruthy()
})
test('button is visible for moderators when audio is available', () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const audioIndicator = findNcButton(wrapper, audioIndicatorAriaLabels)
expect(audioIndicator.isVisible()).toBeTruthy()
})
test('button is not rendered for non-moderators when audio is available', () => {
conversationProps.participantType = PARTICIPANT.TYPE.USER
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const audioIndicator = findNcButton(wrapper, audioIndicatorAriaLabels)
expect(audioIndicator.exists()).toBeFalsy()
})
@@ -320,52 +249,26 @@ describe('VideoBottomBar.vue', () => {
test('button is visible for everyone when audio is unavailable', () => {
conversationProps.participantType = PARTICIPANT.TYPE.USER
componentProps.model.attributes.audioAvailable = false
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const audioIndicator = findNcButton(wrapper, audioIndicatorAriaLabels)
expect(audioIndicator.isVisible()).toBeTruthy()
})
test('button is enabled for moderators', () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const audioIndicator = findNcButton(wrapper, audioIndicatorAriaLabels)
- expect(audioIndicator.attributes('disabled')).toBeFalsy()
+ expect(audioIndicator.attributes('disabled')).toBeUndefined()
})
test('button is disabled when audio is unavailable', () => {
componentProps.model.attributes.audioAvailable = false
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
+ const wrapper = mountVideoBottomBar(componentProps)
const audioIndicator = findNcButton(wrapper, audioIndicatorAriaLabels)
- expect(audioIndicator.attributes('disabled')).toBeTruthy()
+ expect(audioIndicator.attributes('disabled')).toBeDefined()
})
test('method is called after click', async () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- stubs: {
- NcButton,
- },
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const audioIndicator = findNcButton(wrapper, audioIndicatorAriaLabels)
await audioIndicator.trigger('click')
@@ -375,86 +278,39 @@ describe('VideoBottomBar.vue', () => {
describe('video indicator', () => {
test('button is rendered properly', () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
+ const wrapper = mountVideoBottomBar(componentProps)
const videoIndicator = findNcButton(wrapper, videoIndicatorAriaLabels)
expect(videoIndicator.exists()).toBeTruthy()
})
test('button is visible when video is available', () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const videoIndicator = findNcButton(wrapper, videoIndicatorAriaLabels)
expect(videoIndicator.isVisible()).toBeTruthy()
})
test('button is not rendered when video is unavailable', () => {
componentProps.model.attributes.videoAvailable = false
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const videoIndicator = findNcButton(wrapper, videoIndicatorAriaLabels)
expect(videoIndicator.exists()).toBeFalsy()
})
test('button shows proper icon if video is enabled', () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- stubs: {
- NcButton,
- },
- },
-
- props: componentProps,
- })
-
- const videoOnIcon = wrapper.findComponent(IconVideoOutline)
+ const wrapper = mountVideoBottomBar(componentProps)
+ const videoOnIcon = wrapper.findComponent(IconVideo)
expect(videoOnIcon.exists()).toBeTruthy()
})
test('button shows proper icon if video is blocked', () => {
componentProps.sharedData.remoteVideoBlocker.isVideoEnabled.mockReturnValue(false)
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- stubs: {
- NcButton,
- },
- },
-
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const videoOffIcon = wrapper.findComponent(IconVideoOffOutline)
expect(videoOffIcon.exists()).toBeTruthy()
})
test('method is called after click', async () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- stubs: {
- NcButton,
- },
- },
-
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const videoIndicator = findNcButton(wrapper, videoIndicatorAriaLabels)
await videoIndicator.trigger('click')
@@ -465,54 +321,26 @@ describe('VideoBottomBar.vue', () => {
describe('screen sharing indicator', () => {
test('button is rendered properly', () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const screenSharingIndicator = findNcButton(wrapper, screenSharingAriaLabel)
expect(screenSharingIndicator.exists()).toBeTruthy()
})
test('button is visible when screen is available', () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const screenSharingIndicator = findNcButton(wrapper, screenSharingAriaLabel)
expect(screenSharingIndicator.isVisible()).toBeTruthy()
})
test('button is not rendered when screen is unavailable', () => {
componentProps.model.attributes.screen = false
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const screenSharingIndicator = findNcButton(wrapper, screenSharingAriaLabel)
expect(screenSharingIndicator.exists()).toBeFalsy()
})
test('component emits peer id after click', async () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- stubs: {
- NcButton,
- },
- },
-
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const screenSharingIndicator = findNcButton(wrapper, screenSharingAriaLabel)
await screenSharingIndicator.trigger('click')
@@ -522,24 +350,14 @@ describe('VideoBottomBar.vue', () => {
describe('following button', () => {
test('button is not rendered for participants by default', () => {
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
+ const wrapper = mountVideoBottomBar(componentProps)
const followingButton = findNcButton(wrapper, followingButtonAriaLabel)
expect(followingButton.exists()).toBeFalsy()
})
test('button is not rendered for main speaker by default', () => {
componentProps.isBig = true
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
+ const wrapper = mountVideoBottomBar(componentProps)
const followingButton = findNcButton(wrapper, followingButtonAriaLabel)
expect(followingButton.exists()).toBeFalsy()
})
@@ -547,13 +365,7 @@ describe('VideoBottomBar.vue', () => {
test('button is rendered when source is selected', () => {
callViewStore.setSelectedVideoPeerId(PEER_ID)
componentProps.isBig = true
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- },
- props: componentProps,
- })
-
+ const wrapper = mountVideoBottomBar(componentProps)
const followingButton = findNcButton(wrapper, followingButtonAriaLabel)
expect(followingButton.exists()).toBeTruthy()
})
@@ -565,16 +377,7 @@ describe('VideoBottomBar.vue', () => {
expect(callViewStore.presentationStarted).toBeTruthy()
componentProps.isBig = true
- const wrapper = shallowMount(VideoBottomBar, {
- global: {
- plugins: [store],
- stubs: {
- NcButton,
- },
- },
-
- props: componentProps,
- })
+ const wrapper = mountVideoBottomBar(componentProps)
const followingButton = findNcButton(wrapper, followingButtonAriaLabel)
await followingButton.trigger('click')
diff --git a/src/components/CallView/shared/VideoVue.spec.js b/src/components/CallView/shared/VideoVue.spec.js
index 79f8c7c1197..809354a929b 100644
--- a/src/components/CallView/shared/VideoVue.spec.js
+++ b/src/components/CallView/shared/VideoVue.spec.js
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import { shallowMount } from '@vue/test-utils'
+import { mount } from '@vue/test-utils'
import { cloneDeep } from 'lodash'
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, test, vi } from 'vitest'
@@ -97,7 +97,7 @@ describe('VideoVue.vue', () => {
* async).
*/
function setupWrapper() {
- wrapper = shallowMount(VideoVue, {
+ wrapper = mount(VideoVue, {
global: { plugins: [store] },
props: {
model: callParticipantModel,
diff --git a/src/components/LeftSidebar/ConversationsList/Conversation.spec.js b/src/components/LeftSidebar/ConversationsList/Conversation.spec.js
index 121768f24ca..6b234c99208 100644
--- a/src/components/LeftSidebar/ConversationsList/Conversation.spec.js
+++ b/src/components/LeftSidebar/ConversationsList/Conversation.spec.js
@@ -4,26 +4,29 @@
*/
import { showError, showSuccess } from '@nextcloud/dialogs'
-import { flushPromises, mount, shallowMount } from '@vue/test-utils'
+import { flushPromises, mount } from '@vue/test-utils'
import { cloneDeep } from 'lodash'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { createStore } from 'vuex'
-import NcActionButton from '@nextcloud/vue/components/NcActionButton'
import NcButton from '@nextcloud/vue/components/NcButton'
+import NcDialog from '@nextcloud/vue/components/NcDialog'
import NcListItem from '@nextcloud/vue/components/NcListItem'
+import IconFileOutline from 'vue-material-design-icons/FileOutline.vue'
+import ConversationIcon from '../../ConversationIcon.vue'
import Conversation from './Conversation.vue'
import router from '../../../__mocks__/router.js'
import { ATTENDEE, CONVERSATION, PARTICIPANT } from '../../../constants.ts'
import { leaveConversation } from '../../../services/participantsService.js'
import storeConfig from '../../../store/storeConfig.js'
-import { findNcButton } from '../../../test-helpers.js'
+import { findNcActionButton, findNcButton } from '../../../test-helpers.js'
vi.mock('../../../services/participantsService', () => ({
leaveConversation: vi.fn(),
}))
-// TODO fix after RouterLinkStub can support slots https://github.com/vuejs/vue-test-utils/issues/1803
-const RouterLinkStub = true
+const ComponentStub = {
+ template: '
',
+}
describe('Conversation.vue', () => {
const TOKEN = 'XXTOKENXX'
@@ -32,6 +35,26 @@ describe('Conversation.vue', () => {
let item
let messagesMock
+ /**
+ * Shared function to mount component
+ */
+ function mountConversation(isSearchResult = false) {
+ return mount(Conversation, {
+ global: {
+ plugins: [router, store],
+ stubs: {
+ NcModal: ComponentStub,
+ NcPopover: ComponentStub,
+ },
+ },
+
+ props: {
+ isSearchResult,
+ item,
+ },
+ })
+ }
+
beforeEach(() => {
testStoreConfig = cloneDeep(storeConfig)
messagesMock = vi.fn().mockReturnValue({})
@@ -73,24 +96,13 @@ describe('Conversation.vue', () => {
})
test('renders conversation entry', () => {
- const wrapper = mount(Conversation, {
- global: {
- plugins: [store],
- stubs: {
- RouterLink: RouterLinkStub,
- },
- },
- props: {
- isSearchResult: false,
- item,
- },
- })
+ const wrapper = mountConversation(false)
- const el = wrapper.findComponent({ name: 'NcListItem' })
+ const el = wrapper.findComponent(NcListItem)
expect(el.exists()).toBe(true)
expect(el.props('name')).toBe('conversation one')
- const icon = el.findComponent({ name: 'ConversationIcon' })
+ const icon = el.findComponent(ConversationIcon)
expect(icon.props('item')).toStrictEqual(item)
expect(icon.props('hideFavorite')).toStrictEqual(false)
expect(icon.props('hideCall')).toStrictEqual(false)
@@ -102,106 +114,93 @@ describe('Conversation.vue', () => {
* @param {string} expectedText Expected subname of the conversation item
* @param {boolean} isSearchResult Whether or not the item is a search result (has no … menu)
*/
- function testConversationLabel(item, expectedText, isSearchResult = false) {
- const wrapper = shallowMount(Conversation, {
- global: {
- plugins: [store],
- },
- props: {
- isSearchResult,
- item,
- },
- })
+ async function testConversationLabel(item, expectedText, isSearchResult = false) {
+ const wrapper = mountConversation(isSearchResult)
+ await flushPromises()
const el = wrapper.find('.conversation__subname')
+ expect(el.exists()).toBeTruthy()
expect(el.text()).toMatch(expectedText)
return wrapper
}
- test('display joining conversation message when not joined yet', () => {
+ test('display joining conversation message when not joined yet', async () => {
item.actorId = null
- testConversationLabel(item, 'Joining conversation …')
+ await testConversationLabel(item, 'Joining conversation …')
})
- test('displays nothing when there is no last chat message', () => {
+ test('displays nothing when there is no last chat message', async () => {
delete item.lastMessage
- testConversationLabel(item, 'No messages')
+ await testConversationLabel(item, 'No messages')
})
describe('author name', () => {
- test('displays last chat message with shortened author name', () => {
- testConversationLabel(item, /^Alice:\s+hello$/)
+ // items are padded from each other visually
+ test('displays last chat message with shortened author name', async () => {
+ await testConversationLabel(item, 'Alice:hello')
})
- test('displays last chat message with author name if no space in name', () => {
+ test('displays last chat message with author name if no space in name', async () => {
item.lastMessage.actorDisplayName = 'Bob'
- testConversationLabel(item, /^Bob:\s+hello$/)
+ await testConversationLabel(item, 'Bob:hello')
})
- test('displays own last chat message with "You" as author', () => {
+ test('displays own last chat message with "You" as author', async () => {
item.lastMessage.actorId = 'actor-id-1'
- testConversationLabel(item, /^You:\s+hello$/)
+ await testConversationLabel(item, 'You:hello')
})
- test('displays last system message without author', () => {
+ test('displays last system message without author', async () => {
item.lastMessage.message = 'Alice has joined the call'
item.lastMessage.systemMessage = 'call_joined'
- testConversationLabel(item, 'Alice has joined the call')
+ await testConversationLabel(item, 'Alice has joined the call')
})
- test('displays last message without author in one to one conversations', () => {
+ test('displays last message without author in one to one conversations', async () => {
item.type = CONVERSATION.TYPE.ONE_TO_ONE
- testConversationLabel(item, 'hello')
+ await testConversationLabel(item, 'hello')
})
- test('displays own last message with "You" author in one to one conversations', () => {
+ test('displays own last message with "You" author in one to one conversations', async () => {
item.type = CONVERSATION.TYPE.ONE_TO_ONE
item.lastMessage.actorId = 'actor-id-1'
- testConversationLabel(item, /^You:\s+hello$/)
+ await testConversationLabel(item, 'You:hello')
})
- test('displays last guest message with default author when none set', () => {
+ test('displays last guest message with default author when none set', async () => {
item.type = CONVERSATION.TYPE.PUBLIC
item.lastMessage.actorDisplayName = ''
item.lastMessage.actorType = ATTENDEE.ACTOR_TYPE.GUESTS
- testConversationLabel(item, /^Guest:\s+hello$/)
+ await testConversationLabel(item, 'Guest:hello')
})
- test('displays description for search results', () => {
+ test('displays description for search results', async () => {
// search results have no actor id
item.actorId = null
item.description = 'This is a description'
- testConversationLabel(item, 'This is a description', true)
+ await testConversationLabel(item, 'This is a description', true)
})
})
- test('replaces placeholders in rich object of last message', () => {
+ test('replaces placeholders in rich object of last message', async () => {
item.lastMessage.message = '{file}'
item.lastMessage.messageParameters = {
file: {
name: 'filename.jpg',
},
}
- const wrapper = testConversationLabel(item, /^Alice:\s+filename.jpg$/)
- expect(wrapper.findComponent({ name: 'FileIcon' }).exists()).toBeTruthy()
+ const wrapper = await testConversationLabel(item, 'Alice:filename.jpg')
+ expect(wrapper.findComponent(IconFileOutline).exists()).toBeTruthy()
})
test('hides subname for sensitive conversations', () => {
item.isSensitive = true
- const wrapper = shallowMount(Conversation, {
- global: {
- plugins: [store],
- },
- props: {
- isSearchResult: false,
- item,
- },
- })
+ const wrapper = mountConversation(false)
const el = wrapper.find('.conversation__subname')
expect(el.exists()).toBe(false)
@@ -216,20 +215,9 @@ describe('Conversation.vue', () => {
* @param {boolean} expectedHighlighted Whether or not the unread counter is highlighted with primary color
*/
function testCounter(item, expectedCounterText, expectedOutlined, expectedHighlighted) {
- const wrapper = mount(Conversation, {
- global: {
- plugins: [store],
- stubs: {
- RouterLink: RouterLinkStub,
- },
- },
- props: {
- isSearchResult: false,
- item,
- },
- })
+ const wrapper = mountConversation(false)
- const el = wrapper.findComponent({ name: 'NcListItem' })
+ const el = wrapper.findComponent(NcListItem)
expect(el.exists()).toBe(true)
expect(el.props('counterNumber')).toBe(expectedCounterText)
@@ -268,95 +256,38 @@ describe('Conversation.vue', () => {
})
test('does not render counter when no unread messages', () => {
- const wrapper = mount(Conversation, {
- global: {
- plugins: [store],
- stubs: {
- RouterLink: RouterLinkStub,
- },
- },
- props: {
- isSearchResult: false,
- item,
- },
- })
+ const wrapper = mountConversation(false)
- const el = wrapper.findComponent({ name: 'NcListItem' })
+ const el = wrapper.findComponent(NcListItem)
expect(el.exists()).toBe(true)
expect(el.vm.$slots.counter).not.toBeDefined()
})
})
- describe('actions (real router)', () => {
+ describe('actions and routing', () => {
test('change route on click event', async () => {
- const wrapper = mount(Conversation, {
- global: {
- plugins: [router, store],
- stubs: {
- NcListItem,
- },
- },
- props: {
- isSearchResult: false,
- item,
- },
- })
+ await router.isReady()
+ const wrapper = mountConversation(false)
- const el = wrapper.findComponent({ name: 'NcListItem' })
+ const el = wrapper.findComponent(NcListItem)
expect(el.exists()).toBe(true)
await el.find('a').trigger('click')
+ await flushPromises()
expect(wrapper.vm.$route.name).toBe('conversation')
expect(wrapper.vm.$route.params).toStrictEqual({ token: TOKEN })
})
- })
-
- describe('actions (mock router)', () => {
- let $router
-
- beforeEach(() => {
- $router = { push: vi.fn() }
- })
-
- /**
- * @param {object} wrapper Parent element to search the text in
- * @param {string} text Text to find within the wrapper
- */
- function findNcActionButton(wrapper, text) {
- const actionButtons = wrapper.findAllComponents(NcActionButton)
- const items = actionButtons.filter((actionButton) => {
- return actionButton.text() === text
- })
- if (!items.exists()) {
- return items
- }
- return items.at(0)
- }
/**
* @param {string} actionName The name of the action to shallow
*/
function shallowMountAndGetAction(actionName) {
store = createStore(testStoreConfig)
- const wrapper = shallowMount(Conversation, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- },
- mocks: {
- $router,
- },
- },
- props: {
- isSearchResult: false,
- item,
- },
- })
+ const wrapper = mountConversation(false)
- const el = wrapper.findComponent({ name: 'NcListItem' })
+ const el = wrapper.findComponent(NcListItem)
expect(el.exists()).toBe(true)
return findNcActionButton(el, actionName)
@@ -367,23 +298,8 @@ describe('Conversation.vue', () => {
* @param {number} buttonsAmount The amount of buttons to be shown in dialog
*/
async function shallowMountAndOpenDialog(actionName, buttonsAmount) {
- const wrapper = shallowMount(Conversation, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- NcButton,
- },
- mocks: {
- $router,
- },
- },
- props: {
- isSearchResult: false,
- item,
- },
- })
- const el = wrapper.findComponent({ name: 'NcListItem' })
+ const wrapper = mountConversation(false)
+ const el = wrapper.findComponent(NcListItem)
const action = findNcActionButton(el, actionName)
expect(action.exists()).toBeTruthy()
@@ -392,10 +308,9 @@ describe('Conversation.vue', () => {
await action.find('button').trigger('click')
// Assert 1
- const dialog = wrapper.findComponent({ name: 'NcDialog' })
+ const dialog = wrapper.findComponent(NcDialog)
expect(dialog.exists).toBeTruthy()
- const buttons = dialog.findAllComponents({ name: 'NcButton' })
- expect(buttons.exists()).toBeTruthy()
+ const buttons = dialog.findAllComponents(NcButton)
expect(buttons).toHaveLength(buttonsAmount)
return dialog
@@ -418,7 +333,7 @@ describe('Conversation.vue', () => {
// Act: click on the 'confirm' button
await findNcButton(dialog, 'Yes').find('button').trigger('click')
-
+ await flushPromises()
// Assert
expect(actionHandler).toHaveBeenCalledWith(expect.anything(), { token: TOKEN })
})
@@ -474,6 +389,7 @@ describe('Conversation.vue', () => {
let actionHandler
beforeEach(() => {
+ vi.spyOn(router, 'push')
actionHandler = vi.fn().mockResolvedValueOnce()
testStoreConfig.modules.conversationsStore.actions.deleteConversationFromServer = actionHandler
store = createStore(testStoreConfig)
@@ -485,10 +401,11 @@ describe('Conversation.vue', () => {
// Act: click on the 'confirm' button
await findNcButton(dialog, 'Yes').find('button').trigger('click')
+ await flushPromises()
// Assert
expect(actionHandler).toHaveBeenCalledWith(expect.anything(), { token: TOKEN })
- expect($router.push).not.toHaveBeenCalled()
+ expect(router.push).not.toHaveBeenCalled()
})
test('does not delete conversation when not confirmed', async () => {
@@ -497,10 +414,11 @@ describe('Conversation.vue', () => {
// Act: click on the 'decline' button
await findNcButton(dialog, 'No').find('button').trigger('click')
+ await flushPromises()
// Assert
expect(actionHandler).not.toHaveBeenCalled()
- expect($router.push).not.toHaveBeenCalled()
+ expect(router.push).not.toHaveBeenCalled()
})
test('hides "delete conversation" action when not allowed', async () => {
@@ -514,19 +432,7 @@ describe('Conversation.vue', () => {
test('copies link conversation', async () => {
store = createStore(testStoreConfig)
const copyTextMock = vi.fn().mockResolvedValueOnce()
- const wrapper = shallowMount(Conversation, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- },
- },
-
- props: {
- isSearchResult: false,
- item,
- },
- })
+ const wrapper = mountConversation(false)
Object.assign(navigator, {
clipboard: {
@@ -534,7 +440,7 @@ describe('Conversation.vue', () => {
},
})
- const el = wrapper.findComponent({ name: 'NcListItem' })
+ const el = wrapper.findComponent(NcListItem)
expect(el.exists()).toBe(true)
const action = findNcActionButton(el, 'Copy link')
@@ -552,21 +458,9 @@ describe('Conversation.vue', () => {
testStoreConfig.modules.conversationsStore.actions.toggleFavorite = toggleFavoriteAction
store = createStore(testStoreConfig)
- const wrapper = shallowMount(Conversation, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- },
- },
+ const wrapper = mountConversation(false)
- props: {
- isSearchResult: false,
- item,
- },
- })
-
- const el = wrapper.findComponent({ name: 'NcListItem' })
+ const el = wrapper.findComponent(NcListItem)
expect(el.exists()).toBe(true)
const action = findNcActionButton(el, 'Add to favorites')
@@ -586,21 +480,9 @@ describe('Conversation.vue', () => {
item.isFavorite = true
store = createStore(testStoreConfig)
- const wrapper = shallowMount(Conversation, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- },
- },
+ const wrapper = mountConversation(false)
- props: {
- isSearchResult: false,
- item,
- },
- })
-
- const el = wrapper.findComponent({ name: 'NcListItem' })
+ const el = wrapper.findComponent(NcListItem)
expect(el.exists()).toBe(true)
const action = findNcActionButton(el, 'Remove from favorites')
@@ -637,26 +519,11 @@ describe('Conversation.vue', () => {
})
test('does not show all actions for search result (open conversations)', () => {
store = createStore(testStoreConfig)
- const wrapper = shallowMount(Conversation, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- },
- },
+ const wrapper = mountConversation(true)
- props: {
- isSearchResult: true,
- item,
- },
- })
-
- const el = wrapper.findComponent({ name: 'NcListItem' })
+ const el = wrapper.findComponent(NcListItem)
expect(el.exists()).toBe(true)
- const actionButtons = wrapper.findAllComponents(NcActionButton)
- expect(actionButtons.exists()).toBe(true)
-
// Join conversation and Copy link actions are intended
expect(findNcActionButton(el, 'Join conversation').exists()).toBe(true)
expect(findNcActionButton(el, 'Copy link').exists()).toBe(true)
diff --git a/src/components/LeftSidebar/LeftSidebar.spec.js b/src/components/LeftSidebar/LeftSidebar.spec.js
index 4287b0cbe05..5846f048b0a 100644
--- a/src/components/LeftSidebar/LeftSidebar.spec.js
+++ b/src/components/LeftSidebar/LeftSidebar.spec.js
@@ -12,6 +12,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { createStore } from 'vuex'
import LeftSidebar from './LeftSidebar.vue'
import router from '../../__mocks__/router.js'
+import { localCapabilities } from '../../services/CapabilitiesManager.ts'
import { searchListedConversations } from '../../services/conversationsService.ts'
import { autocompleteQuery } from '../../services/coreService.ts'
import { EventBus } from '../../services/EventBus.ts'
@@ -27,22 +28,6 @@ vi.mock('../../services/coreService', () => ({
autocompleteQuery: vi.fn(),
}))
-// Test actions with 'can-create' config
-let mockCanCreateConversations = true
-vi.mock('../../services/CapabilitiesManager', async () => {
- const CapabilitiesManager = await vi.importActual('../../services/CapabilitiesManager')
- return {
- ...CapabilitiesManager,
- getTalkConfig: vi.fn((...args) => {
- if (args[0] === 'local' && args[1] === 'conversations' && args[2] === 'can-create') {
- return mockCanCreateConversations
- } else {
- return CapabilitiesManager.getTalkConfig(...args)
- }
- }),
- }
-})
-
// short-circuit debounce
vi.mock('debounce', () => ({
default: vi.fn().mockImplementation((fn) => fn),
@@ -59,6 +44,9 @@ describe('LeftSidebar.vue', () => {
const SEARCH_TERM = 'search'
+ const ComponentStub = {
+ template: '
',
+ }
const RecycleScrollerStub = {
props: {
items: Array,
@@ -69,7 +57,13 @@ describe('LeftSidebar.vue', () => {
`,
}
- const mountComponent = () => {
+ const HAS_APP_NAVIGATION_KEY = Symbol.for('NcContent:setHasAppNavigation')
+ const NC_ACTIONS_CLOSE_MENU = Symbol.for('NcActions:closeMenu')
+
+ /**
+ * Shared function to mount component
+ */
+ function mountComponent() {
return mount(LeftSidebar, {
global: {
plugins: [router, store],
@@ -77,13 +71,14 @@ describe('LeftSidebar.vue', () => {
// to prevent user status fetching
NcAvatar: true,
// to prevent complex dialog logic
- NcActions: true,
- NcModal: true,
+ NcActions: ComponentStub,
+ NcModal: ComponentStub,
RecycleScroller: RecycleScrollerStub,
},
- },
- provide: {
- 'NcContent:setHasAppNavigation': () => {},
+ provide: {
+ [HAS_APP_NAVIGATION_KEY]: () => {},
+ [NC_ACTIONS_CLOSE_MENU]: () => {},
+ },
},
})
}
@@ -122,7 +117,7 @@ describe('LeftSidebar.vue', () => {
})
afterEach(() => {
- mockCanCreateConversations = true
+ localCapabilities.spreed.config.conversations['can-create'] = true
vi.clearAllMocks()
})
@@ -184,9 +179,7 @@ describe('LeftSidebar.vue', () => {
expect(conversationListItems.at(0).text()).toStrictEqual(normalConversationsList[0].displayName)
expect(conversationListItems.at(1).text()).toStrictEqual(normalConversationsList[1].displayName)
- expect(conversationsReceivedEvent).toHaveBeenCalledWith({
- singleConversation: false,
- })
+ expect(conversationsReceivedEvent).toHaveBeenCalled()
})
test('re-fetches conversations every 30 seconds', async () => {
@@ -422,7 +415,6 @@ describe('LeftSidebar.vue', () => {
)
const itemsListNames = prepareExpectedResults(usersResults, groupsResults, circlesResults, listedResults, 'Other sources')
const itemsList = wrapper.findAll('.vue-recycle-scroller-STUB-item')
- expect(itemsList.exists()).toBeTruthy()
expect(itemsList).toHaveLength(itemsListNames.length)
itemsListNames.forEach((name, index) => {
expect(itemsList.at(index).text()).toStrictEqual(name)
@@ -430,7 +422,7 @@ describe('LeftSidebar.vue', () => {
})
test('only shows user search results when cannot create conversations', async () => {
- mockCanCreateConversations = false
+ localCapabilities.spreed.config.conversations['can-create'] = false
const wrapper = await testSearch(
SEARCH_TERM,
@@ -443,7 +435,6 @@ describe('LeftSidebar.vue', () => {
const itemsListNames = prepareExpectedResults(usersResults, groupsResults, circlesResults, listedResults, 'Groups and teams', true, false)
const itemsList = wrapper.findAll('.vue-recycle-scroller-STUB-item')
- expect(itemsList.exists()).toBeTruthy()
expect(itemsList).toHaveLength(itemsListNames.length)
expect(itemsListNames.filter((item) => ['Groups', 'Teams', 'Federated users', SEARCH_TERM].includes(item)).length).toBe(0)
itemsListNames.forEach((name, index) => {
@@ -463,7 +454,6 @@ describe('LeftSidebar.vue', () => {
const itemsListNames = prepareExpectedResults(usersResults, groupsResults, circlesResults, listedResults, 'Other sources', false, true)
const itemsList = wrapper.findAll('.vue-recycle-scroller-STUB-item')
- expect(itemsList.exists()).toBeTruthy()
expect(itemsList).toHaveLength(itemsListNames.length)
expect(itemsListNames.filter((item) => ['Teams'].includes(item)).length).toBe(0)
itemsListNames.forEach((name, index) => {
@@ -484,7 +474,6 @@ describe('LeftSidebar.vue', () => {
const wrapper = await testSearch(searchTerm, possibleResults, listedResults, loadStateSettingsOverride)
const captionsEls = wrapper.findAll('.caption')
- expect(captionsEls.exists()).toBeTruthy()
if (listedResults.length > 0) {
expect(captionsEls.length).toBeGreaterThan(2)
expect(captionsEls.at(0).text()).toBe('Conversations')
@@ -601,7 +590,7 @@ describe('LeftSidebar.vue', () => {
expect(newConversationbutton.exists()).toBeTruthy()
})
test('does not show new conversation button if user cannot start conversations', () => {
- mockCanCreateConversations = false
+ localCapabilities.spreed.config.conversations['can-create'] = false
const wrapper = mountComponent()
const newConversationbutton = findNcActionButton(wrapper, 'Create a new conversation')
diff --git a/src/components/MessagesList/MessagesGroup/Message/Message.spec.js b/src/components/MessagesList/MessagesGroup/Message/Message.spec.js
index c49968be284..f45abc79f2b 100644
--- a/src/components/MessagesList/MessagesGroup/Message/Message.spec.js
+++ b/src/components/MessagesList/MessagesGroup/Message/Message.spec.js
@@ -3,15 +3,18 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import { flushPromises, shallowMount } from '@vue/test-utils'
+import { flushPromises, mount } from '@vue/test-utils'
import { cloneDeep } from 'lodash'
import { createPinia, setActivePinia } from 'pinia'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { createStore } from 'vuex'
+import NcActions from '@nextcloud/vue/components/NcActions'
import NcButton from '@nextcloud/vue/components/NcButton'
+import NcRichText from '@nextcloud/vue/components/NcRichText'
import IconCheck from 'vue-material-design-icons/Check.vue'
import IconCheckAll from 'vue-material-design-icons/CheckAll.vue'
import Quote from '../../../Quote.vue'
+import CallButton from '../../../TopBar/CallButton.vue'
import Message from './Message.vue'
import MessageButtonsBar from './MessageButtonsBar/MessageButtonsBar.vue'
import DeckCard from './MessagePart/DeckCard.vue'
@@ -19,7 +22,7 @@ import DefaultParameter from './MessagePart/DefaultParameter.vue'
import FilePreview from './MessagePart/FilePreview.vue'
import Location from './MessagePart/Location.vue'
import Mention from './MessagePart/Mention.vue'
-import MessageBody from './MessagePart/MessageBody.vue'
+import router from '../../../../__mocks__/router.js'
import * as useIsInCallModule from '../../../../composables/useIsInCall.js'
import { ATTENDEE, CONVERSATION, MESSAGE, PARTICIPANT } from '../../../../constants.ts'
import { EventBus } from '../../../../services/EventBus.ts'
@@ -27,19 +30,6 @@ import storeConfig from '../../../../store/storeConfig.js'
import { useActorStore } from '../../../../stores/actor.ts'
import { useTokenStore } from '../../../../stores/token.ts'
-// needed because of https://github.com/vuejs/vue-test-utils/issues/1507
-const RichTextStub = {
- props: {
- text: {
- type: String,
- },
- arguments: {
- type: Object,
- },
- },
- template: '',
-}
-
describe('Message.vue', () => {
const TOKEN = 'XXTOKENXX'
let testStoreConfig
@@ -100,44 +90,42 @@ describe('Message.vue', () => {
vi.clearAllMocks()
})
+ /**
+ * Shared function to mount component
+ */
+ function mountMessage(props) {
+ return mount(Message, {
+ global: {
+ plugins: [router, store],
+ provide: injected,
+ stubs: {
+ Location: true,
+ },
+ },
+ props,
+ })
+ }
+
describe('message rendering', () => {
beforeEach(() => {
store = createStore(testStoreConfig)
})
test('renders rich text message', async () => {
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
- const message = wrapper.findComponent({ name: 'NcRichText' })
- expect(message.attributes('text')).toBe('test message')
+ const message = wrapper.findComponent(NcRichText)
+ expect(message.text()).toBe('test message')
})
test('renders emoji as single plain text', async () => {
messageProps.isSingleEmoji = true
messageProps.message.message = '🌧️'
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
- const message = wrapper.findComponent({ name: 'NcRichText' })
+ const message = wrapper.findComponent(NcRichText)
expect(message.exists()).toBeTruthy()
- expect(message.attributes('text')).toBe('🌧️')
+ expect(message.text()).toBe('🌧️')
})
describe('call button', () => {
@@ -162,21 +150,12 @@ describe('Message.vue', () => {
messageProps.message.message = 'message two'
conversationProps.hasCall = true
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
- const richText = wrapper.findComponent({ name: 'NcRichText' })
- expect(richText.attributes('text')).toBe('message two')
+ const richText = wrapper.findComponent(NcRichText)
+ expect(richText.text()).toBe('message two')
- const callButton = wrapper.findComponent({ name: 'CallButton' })
+ const callButton = wrapper.findComponent(CallButton)
expect(callButton.exists()).toBe(true)
})
@@ -186,18 +165,9 @@ describe('Message.vue', () => {
messageProps.message.message = 'message one'
conversationProps.hasCall = true
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
- const callButton = wrapper.findComponent({ name: 'CallButton' })
+ const callButton = wrapper.findComponent(CallButton)
expect(callButton.exists()).toBe(false)
})
@@ -207,18 +177,9 @@ describe('Message.vue', () => {
messageProps.message.message = 'message two'
conversationProps.hasCall = false
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
- const callButton = wrapper.findComponent({ name: 'CallButton' })
+ const callButton = wrapper.findComponent(CallButton)
expect(callButton.exists()).toBe(false)
})
@@ -230,18 +191,9 @@ describe('Message.vue', () => {
vi.spyOn(useIsInCallModule, 'useIsInCall').mockReturnValue(() => true)
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
- const callButton = wrapper.findComponent({ name: 'CallButton' })
+ const callButton = wrapper.findComponent(CallButton)
expect(callButton.exists()).toBe(false)
})
})
@@ -251,32 +203,14 @@ describe('Message.vue', () => {
messageProps.message.message = 'message deleted'
conversationProps.hasCall = true
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
- const richText = wrapper.findComponent({ name: 'NcRichText' })
- expect(richText.attributes('text')).toBe('message deleted')
+ const richText = wrapper.findComponent(NcRichText)
+ expect(richText.text()).toBe('message deleted')
})
test('renders date', () => {
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
const date = wrapper.find('.date')
expect(date.exists()).toBe(true)
@@ -300,16 +234,7 @@ describe('Message.vue', () => {
testStoreConfig.modules.messagesStore.getters.message = vi.fn(() => messageGetterMock)
store = createStore(testStoreConfig)
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
const quote = wrapper.findComponent(Quote)
expect(quote.exists()).toBeTruthy()
@@ -326,21 +251,11 @@ describe('Message.vue', () => {
messageProps.message.message = message
messageProps.message.messageParameters = messageParameters
store.dispatch('processMessage', { token: TOKEN, message: messageProps.message })
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- RichText: RichTextStub,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
- const messageEl = wrapper.findComponent({ name: 'NcRichText' })
+ const messageEl = wrapper.findComponent(NcRichText)
// note: indices as object keys are on purpose
- expect(messageEl.props('arguments')).toMatchObject(expectedRichParameters)
+ expect(Object.keys(messageEl.props('arguments'))).toMatchObject(Object.keys(expectedRichParameters))
return messageEl
}
@@ -358,6 +273,7 @@ describe('Message.vue', () => {
},
'mention-call1': {
id: 'some_call',
+ name: 'Some call',
type: 'call',
},
}
@@ -389,8 +305,11 @@ describe('Message.vue', () => {
type: 'user',
},
file: {
- path: 'some/path',
+ id: '123',
+ path: 'Talk/some-path.txt',
+ name: 'some-path.txt',
type: 'file',
+ mimetype: 'txt/plain',
},
}
renderRichObject(
@@ -418,8 +337,11 @@ describe('Message.vue', () => {
type: 'user',
},
file: {
- path: 'some/path',
+ id: '123',
+ path: 'Talk/some-path.txt',
+ name: 'some-path.txt',
type: 'file',
+ mimetype: 'txt/plain',
},
}
const messageEl = renderRichObject(
@@ -448,6 +370,11 @@ describe('Message.vue', () => {
type: 'user',
},
'deck-card': {
+ id: '123',
+ name: 'Card name',
+ boardname: 'Board name',
+ stackname: 'Stack name',
+ link: 'https://example.com/some/deck/card/url',
metadata: '{id:123}',
type: 'deck-card',
},
@@ -471,6 +398,10 @@ describe('Message.vue', () => {
test('renders geo locations', () => {
const params = {
'geo-location': {
+ id: '123',
+ name: 'Location name',
+ latitude: 12.345678,
+ longitude: 98.765432,
metadata: '{id:123}',
type: 'geo-location',
},
@@ -495,6 +426,8 @@ describe('Message.vue', () => {
type: 'user',
},
unknown: {
+ id: '123',
+ name: 'Unknown name',
path: 'some/path',
type: 'unknown',
},
@@ -516,62 +449,11 @@ describe('Message.vue', () => {
})
})
- test('displays unread message marker that marks the message seen when visible', () => {
- getVisualLastReadMessageIdMock.mockReturnValue(123)
- messageProps.nextMessageId = 333
- const IntersectionObserver = vi.fn()
-
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- directives: {
- IntersectionObserver,
- },
- props: messageProps,
- provide: injected,
- })
-
- const marker = wrapper.find('.message-unread-marker')
- expect(marker.exists()).toBe(true)
-
- expect(IntersectionObserver).toHaveBeenCalled()
- const directiveValue = IntersectionObserver.mock.calls[0][1]
-
- expect(wrapper.vm.seen).toEqual(false)
-
- directiveValue.value([{ isIntersecting: false }])
- expect(wrapper.vm.seen).toEqual(false)
-
- directiveValue.value([{ isIntersecting: true }])
- expect(wrapper.vm.seen).toEqual(true)
-
- // stays true if it was visible once
- directiveValue.value([{ isIntersecting: false }])
- expect(wrapper.vm.seen).toEqual(true)
- })
-
test('does not display read marker on the very last message', () => {
messageProps.lastReadMessageId = 123
messageProps.nextMessageId = null // last message
- const IntersectionObserver = vi.fn()
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- directives: {
- IntersectionObserver,
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
const marker = wrapper.find('.message-unread-marker')
expect(marker.exists()).toBe(false)
@@ -586,16 +468,7 @@ describe('Message.vue', () => {
test('does not render actions for system messages are available', async () => {
messageProps.message.systemMessage = 'this is a system message'
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
await wrapper.find('.message').trigger('mouseover')
expect(wrapper.findComponent(MessageButtonsBar).exists()).toBe(false)
@@ -604,16 +477,7 @@ describe('Message.vue', () => {
test('does not render actions for temporary messages', async () => {
messageProps.message.timestamp = 0
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
await wrapper.find('.message').trigger('mouseover')
expect(wrapper.findComponent(MessageButtonsBar).exists()).toBe(false)
@@ -622,16 +486,7 @@ describe('Message.vue', () => {
test('does not render actions for deleted messages', async () => {
messageProps.message.messageType = MESSAGE.TYPE.COMMENT_DELETED
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
await wrapper.find('.message').trigger('mouseover')
expect(wrapper.findComponent(MessageButtonsBar).exists()).toBe(false)
@@ -639,17 +494,7 @@ describe('Message.vue', () => {
test('Buttons bar is rendered on mouse over', async () => {
messageProps.message.sendingFailure = 'timeout'
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- MessageButtonsBar,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
// Initial state
expect(wrapper.findComponent(MessageButtonsBar).exists()).toBe(false)
@@ -659,7 +504,7 @@ describe('Message.vue', () => {
expect(wrapper.findComponent(MessageButtonsBar).exists()).toBe(true)
// Actions are rendered with MessageButtonsBar
- expect(wrapper.findComponent({ name: 'NcActions' }).exists()).toBe(true)
+ expect(wrapper.findComponent(NcActions).exists()).toBe(true)
// Mouseleave
await wrapper.find('.message').trigger('mouseleave')
@@ -677,17 +522,7 @@ describe('Message.vue', () => {
// need to mock the date to be within 6h
vi.useFakeTimers().setSystemTime(new Date('2020-05-07T10:00:00'))
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- MessageButtonsBar,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
// Hover the messages in order to render the MessageButtonsBar component
await wrapper.find('.message').trigger('mouseover')
@@ -722,16 +557,7 @@ describe('Message.vue', () => {
test('lets user retry sending a timed out message', async () => {
messageProps.message.sendingFailure = 'timeout'
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
await wrapper.find('.message-body').trigger('mouseover')
expect(wrapper.findComponent(MessageButtonsBar).exists()).toBe(true)
@@ -754,34 +580,16 @@ describe('Message.vue', () => {
test('displays the message already with a spinner while sending it', () => {
messageProps.message.timestamp = 0
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
- const message = wrapper.findComponent({ name: 'NcRichText' })
- expect(message.attributes('text')).toBe('test message')
+ const wrapper = mountMessage(messageProps)
+ const message = wrapper.findComponent(NcRichText)
+ expect(message.text()).toBe('test message')
expect(wrapper.find('.icon-loading-small').exists()).toBe(true)
})
test('displays icon when message was read by everyone', () => {
conversationProps.lastCommonReadMessage = 123
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
expect(wrapper.findComponent(IconCheck).exists()).toBe(false)
expect(wrapper.findComponent(IconCheckAll).exists()).toBe(true)
@@ -789,16 +597,7 @@ describe('Message.vue', () => {
test('displays sent icon when own message was sent', () => {
conversationProps.lastCommonReadMessage = 0
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
expect(wrapper.findComponent(IconCheck).exists()).toBe(true)
expect(wrapper.findComponent(IconCheckAll).exists()).toBe(false)
@@ -808,16 +607,7 @@ describe('Message.vue', () => {
conversationProps.lastCommonReadMessage = 123
messageProps.message.actorId = 'user-id-2'
messageProps.message.actorType = ATTENDEE.ACTOR_TYPE.USERS
- const wrapper = shallowMount(Message, {
- global: {
- plugins: [store],
- stubs: {
- MessageBody,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessage(messageProps)
expect(wrapper.findComponent(IconCheck).exists()).toBe(false)
expect(wrapper.findComponent(IconCheckAll).exists()).toBe(false)
diff --git a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.spec.js b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.spec.js
index 8f3515733f7..724322a05f5 100644
--- a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.spec.js
+++ b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.spec.js
@@ -3,15 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import { shallowMount } from '@vue/test-utils'
+import { mount } from '@vue/test-utils'
import { cloneDeep } from 'lodash'
import { createPinia, setActivePinia } from 'pinia'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { computed } from 'vue'
import { createStore } from 'vuex'
-import NcActionButton from '@nextcloud/vue/components/NcActionButton'
-import NcButton from '@nextcloud/vue/components/NcButton'
import MessageButtonsBar from './../MessageButtonsBar/MessageButtonsBar.vue'
+import router from '../../../../../__mocks__/router.js'
import * as useMessageInfoModule from '../../../../../composables/useMessageInfo.ts'
import { ATTENDEE, CONVERSATION, MESSAGE, PARTICIPANT } from '../../../../../constants.ts'
import storeConfig from '../../../../../store/storeConfig.js'
@@ -29,12 +28,18 @@ describe('MessageButtonsBar.vue', () => {
let conversationProps
let actorStore
let tokenStore
+ let useMessageInfoSpy
beforeEach(() => {
setActivePinia(createPinia())
actorStore = useActorStore()
tokenStore = useTokenStore()
+ injected = {
+ getMessagesListScroller: vi.fn(),
+ }
+ useMessageInfoSpy = vi.spyOn(useMessageInfoModule, 'useMessageInfo')
+
conversationProps = {
token: TOKEN,
lastCommonReadMessage: 0,
@@ -84,38 +89,32 @@ describe('MessageButtonsBar.vue', () => {
vi.clearAllMocks()
})
- describe('actions', () => {
- let useMessageInfoSpy
-
- beforeEach(() => {
- store = createStore(testStoreConfig)
-
- injected = {
- getMessagesListScroller: vi.fn(),
- }
-
- useMessageInfoSpy = vi.spyOn(useMessageInfoModule, 'useMessageInfo')
- })
-
- afterEach(() => {
- useMessageInfoSpy.mockRestore()
+ const ComponentStub = {
+ template: '
',
+ }
+
+ /**
+ * Shared function to mount component
+ */
+ function mountMessageButtonsBar(props) {
+ return mount(MessageButtonsBar, {
+ global: {
+ plugins: [router, store],
+ stubs: {
+ NcPopover: ComponentStub,
+ },
+ provide: injected,
+ },
+ props,
})
+ }
+ describe('actions', () => {
describe('reply action', () => {
test('replies to message', async () => {
store = createStore(testStoreConfig)
- const wrapper = shallowMount(MessageButtonsBar, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- NcButton,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessageButtonsBar(messageProps)
const replyButton = findNcButton(wrapper, 'Reply')
expect(replyButton.exists()).toBe(true)
@@ -129,17 +128,7 @@ describe('MessageButtonsBar.vue', () => {
messageProps.message.isReplyable = false
store = createStore(testStoreConfig)
- const wrapper = shallowMount(MessageButtonsBar, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- NcButton,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessageButtonsBar(messageProps)
const replyButton = findNcButton(wrapper, 'Reply')
expect(replyButton.exists()).toBe(false)
@@ -149,17 +138,7 @@ describe('MessageButtonsBar.vue', () => {
conversationProps.permissions = 0
store = createStore(testStoreConfig)
- const wrapper = shallowMount(MessageButtonsBar, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- NcButton,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessageButtonsBar(messageProps)
const replyButton = findNcButton(wrapper, 'Reply')
expect(replyButton.exists()).toBe(false)
@@ -168,28 +147,15 @@ describe('MessageButtonsBar.vue', () => {
describe('private reply action', () => {
test('creates a new conversation when replying to message privately', async () => {
- const routerPushMock = vi.fn().mockResolvedValue()
+ vi.spyOn(router, 'push')
+
const createOneToOneConversation = vi.fn()
testStoreConfig.modules.conversationsStore.actions.createOneToOneConversation = createOneToOneConversation
store = createStore(testStoreConfig)
messageProps.message.actorId = 'another-user'
- const wrapper = shallowMount(MessageButtonsBar, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- },
- mocks: {
- $router: {
- push: routerPushMock,
- },
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessageButtonsBar(messageProps)
const actionButton = findNcActionButton(wrapper, 'Reply privately')
expect(actionButton.exists()).toBe(true)
@@ -202,7 +168,7 @@ describe('MessageButtonsBar.vue', () => {
expect(createOneToOneConversation).toHaveBeenCalledWith(expect.anything(), 'another-user')
- expect(routerPushMock).toHaveBeenCalledWith({
+ expect(router.push).toHaveBeenCalledWith({
name: 'conversation',
params: {
token: 'new-token',
@@ -216,16 +182,7 @@ describe('MessageButtonsBar.vue', () => {
function testPrivateReplyActionVisible(visible) {
store = createStore(testStoreConfig)
- const wrapper = shallowMount(MessageButtonsBar, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessageButtonsBar(messageProps)
const actionButton = findNcActionButton(wrapper, 'Reply privately')
expect(actionButton.exists()).toBe(visible)
@@ -267,16 +224,7 @@ describe('MessageButtonsBar.vue', () => {
useMessageInfoSpy.mockReturnValue({
isDeleteable: computed(() => true),
})
- const wrapper = shallowMount(MessageButtonsBar, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessageButtonsBar(messageProps)
const actionButton = findNcActionButton(wrapper, 'Delete')
expect(actionButton.exists()).toBe(true)
@@ -292,16 +240,7 @@ describe('MessageButtonsBar.vue', () => {
* @param {boolean} visible Whether or not the delete action is visible
*/
function testDeleteMessageVisible(visible) {
- const wrapper = shallowMount(MessageButtonsBar, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessageButtonsBar(messageProps)
const actionButton = findNcActionButton(wrapper, 'Delete')
expect(actionButton.exists()).toBe(visible)
@@ -335,16 +274,7 @@ describe('MessageButtonsBar.vue', () => {
conversationProps.readOnly = CONVERSATION.STATE.READ_ONLY
messageProps.message.actorId = 'another-user'
- const wrapper = shallowMount(MessageButtonsBar, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessageButtonsBar(messageProps)
const actionButton = findNcActionButton(wrapper, 'Mark as unread')
expect(actionButton.exists()).toBe(true)
@@ -368,16 +298,7 @@ describe('MessageButtonsBar.vue', () => {
conversationProps.readOnly = CONVERSATION.STATE.READ_ONLY
messageProps.message.actorId = 'another-user'
- const wrapper = shallowMount(MessageButtonsBar, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessageButtonsBar(messageProps)
Object.assign(navigator, {
clipboard: {
@@ -404,16 +325,7 @@ describe('MessageButtonsBar.vue', () => {
actionsGetterMock.forEach((action) => integrationsStore.addMessageAction(action))
testStoreConfig.modules.messagesStore.getters.message = vi.fn(() => () => messageProps)
store = createStore(testStoreConfig)
- const wrapper = shallowMount(MessageButtonsBar, {
- global: {
- plugins: [store],
- stubs: {
- NcActionButton,
- },
- },
- props: messageProps,
- provide: injected,
- })
+ const wrapper = mountMessageButtonsBar(messageProps)
const actionButton = findNcActionButton(wrapper, 'first action')
expect(actionButton.exists()).toBeTruthy()
diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.spec.js b/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.spec.js
index 876352729e9..9cf58949c2f 100644
--- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.spec.js
+++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.spec.js
@@ -5,14 +5,17 @@
import { generateRemoteUrl, imagePath } from '@nextcloud/router'
import { getUploader } from '@nextcloud/upload'
-import { shallowMount } from '@vue/test-utils'
+import { mount } from '@vue/test-utils'
import { cloneDeep } from 'lodash'
import { createPinia, setActivePinia } from 'pinia'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { createStore } from 'vuex'
import NcButton from '@nextcloud/vue/components/NcButton'
+import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
+import NcProgressBar from '@nextcloud/vue/components/NcProgressBar'
import IconPlayCircleOutline from 'vue-material-design-icons/PlayCircleOutline.vue'
import FilePreview from './FilePreview.vue'
+import router from '../../../../../__mocks__/router.js'
import storeConfig from '../../../../../store/storeConfig.js'
import { useActorStore } from '../../../../../stores/actor.ts'
@@ -54,6 +57,18 @@ describe('FilePreview.vue', () => {
window.devicePixelRatio = oldPixelRatio
})
+ /**
+ * Shared function to mount component
+ */
+ function mountFilePreview() {
+ return mount(FilePreview, {
+ global: {
+ plugins: [router, store],
+ },
+ props,
+ })
+ }
+
/**
* @param {string} url Relative URL to parse (starting with / )
*/
@@ -63,10 +78,7 @@ describe('FilePreview.vue', () => {
describe('file preview rendering', () => {
test('renders file preview', async () => {
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
@@ -85,10 +97,7 @@ describe('FilePreview.vue', () => {
props.file.link = 'https://localhost/nc-webroot/s/xtokenx'
actorStore.userId = null
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
@@ -106,10 +115,7 @@ describe('FilePreview.vue', () => {
test('calculates preview size based on window pixel ratio', async () => {
window.devicePixelRatio = 1.5
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
@@ -121,16 +127,13 @@ describe('FilePreview.vue', () => {
test('renders small previews when requested', async () => {
props.smallPreview = true
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
expect(wrapper.element.tagName).toBe('A')
const imageUrl = parseRelativeUrl(wrapper.find('img').attributes('src'))
- expect(imageUrl.searchParams.get('y')).toBe('32')
+ expect(imageUrl.searchParams.get('y')).toBe('24')
})
describe('uploading', () => {
@@ -146,31 +149,28 @@ describe('FilePreview.vue', () => {
store = createStore(testStoreConfig)
})
- test.skip('renders progress bar while uploading', async () => {
- /* getUploader.mockImplementation(() => ({
+ test('renders progress bar while uploading', async () => {
+ getUploader.mockImplementation(() => ({
queue: [{
_source: path,
_uploaded: 85,
_size: 100,
}],
- })) */
+ }))
props.file.id = 'temp-123'
props.file.index = 'index-1'
props.file.uploadId = 1000
props.file.localUrl = 'blob:XYZ'
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
expect(wrapper.element.tagName).toBe('DIV')
expect(wrapper.find('img').attributes('src')).toBe('blob:XYZ')
- const progressEl = wrapper.findComponent({ name: 'NcProgressBar' })
+ const progressEl = wrapper.findComponent(NcProgressBar)
expect(progressEl.exists()).toBe(true)
expect(progressEl.props('value')).toBe(85)
@@ -179,22 +179,16 @@ describe('FilePreview.vue', () => {
})
test('renders spinner while loading', () => {
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
expect(wrapper.element.tagName).toBe('A')
- const spinner = wrapper.findComponent({ name: 'NcLoadingIcon' })
+ const spinner = wrapper.findComponent(NcLoadingIcon)
expect(spinner.exists()).toBe(true)
})
test('renders default mime icon on load error', async () => {
OC.MimeType.getIconUrl.mockReturnValueOnce(imagePath('core', 'image/jpeg'))
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('error')
@@ -207,10 +201,7 @@ describe('FilePreview.vue', () => {
props.file['preview-available'] = 'no'
OC.MimeType.getIconUrl.mockReturnValueOnce(imagePath('core', 'image/jpeg'))
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
@@ -230,10 +221,7 @@ describe('FilePreview.vue', () => {
test('directly renders small GIF files', async () => {
props.file.size = '128'
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
@@ -246,10 +234,7 @@ describe('FilePreview.vue', () => {
props.file.size = '128'
props.file.path = '/path/to/test %20.gif'
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
@@ -263,10 +248,7 @@ describe('FilePreview.vue', () => {
props.file.link = 'https://localhost/nc-webroot/s/xtokenx'
actorStore.userId = null
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
@@ -279,10 +261,7 @@ describe('FilePreview.vue', () => {
// 4 MB, bigger than max from capability (3 MB)
props.file.size = '4194304'
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
@@ -335,10 +314,7 @@ describe('FilePreview.vue', () => {
mimetypes: ['image/png', 'image/jpeg'],
}
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
@@ -368,10 +344,7 @@ describe('FilePreview.vue', () => {
}],
}
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
@@ -382,10 +355,7 @@ describe('FilePreview.vue', () => {
test('does not open viewer when clicking if viewer is not available', async () => {
delete OCA.Viewer
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
@@ -413,10 +383,7 @@ describe('FilePreview.vue', () => {
* @param {boolean} visible Whether or not the play button is visible
*/
async function testPlayButtonVisible(visible) {
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
@@ -453,10 +420,7 @@ describe('FilePreview.vue', () => {
})
test('does not render play icon for failed videos', async () => {
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('error')
@@ -485,16 +449,13 @@ describe('FilePreview.vue', () => {
props.isUploadEditor = true
})
test('emits event when clicking remove button when inside upload editor', async () => {
- const wrapper = shallowMount(FilePreview, {
- global: { plugins: [store] },
- props,
- })
+ const wrapper = mountFilePreview()
await wrapper.find('img').trigger('load')
expect(wrapper.element.tagName).toBe('DIV')
await wrapper.findComponent(NcButton).trigger('click')
- expect(wrapper.emitted()['remove-file']).toStrictEqual([['123']])
+ expect(wrapper.emitted().removeFile).toStrictEqual([['123']])
})
})
})
diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/Reactions.spec.js b/src/components/MessagesList/MessagesGroup/Message/MessagePart/Reactions.spec.js
index ec341224f59..4a3adc9e974 100644
--- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/Reactions.spec.js
+++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/Reactions.spec.js
@@ -4,7 +4,7 @@
*/
import { showError } from '@nextcloud/dialogs'
-import { shallowMount } from '@vue/test-utils'
+import { mount } from '@vue/test-utils'
import { cloneDeep } from 'lodash'
import { createPinia, setActivePinia } from 'pinia'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
@@ -13,7 +13,7 @@ import NcButton from '@nextcloud/vue/components/NcButton'
import NcEmojiPicker from '@nextcloud/vue/components/NcEmojiPicker'
import NcPopover from '@nextcloud/vue/components/NcPopover'
import Reactions from './Reactions.vue'
-import { ATTENDEE } from '../../../../../constants.ts'
+import router from '../../../../../__mocks__/router.js'
import {
addReactionToMessage,
getReactionsDetails,
@@ -103,20 +103,30 @@ describe('Reactions.vue', () => {
reactionsStore.resetReactions(token, messageId)
})
+ const ComponentTriggerStub = {
+ template: '
',
+ }
+
+ /**
+ * Shared function to mount component
+ */
+ function mountReactions(props) {
+ return mount(Reactions, {
+ global: {
+ plugins: [router, store],
+ stubs: {
+ NcEmojiPicker: ComponentTriggerStub,
+ NcPopover: ComponentTriggerStub,
+ },
+ },
+ props,
+ })
+ }
+
describe('reactions buttons', () => {
test('shows reaction buttons with count and emoji picker', async () => {
// Arrange
- const wrapper = shallowMount(Reactions, {
- global: {
- plugins: [store],
- stubs: {
- NcPopover,
- },
- },
- props: reactionsProps,
-
- })
-
+ const wrapper = mountReactions(reactionsProps)
// Assert
const reactionButtons = wrapper.findAllComponents(NcPopover)
expect(reactionButtons).toHaveLength(3)
@@ -131,16 +141,7 @@ describe('Reactions.vue', () => {
test('shows reaction buttons with count but without emoji picker when no react permission', () => {
// Arrange
reactionsProps.canReact = false
- const wrapper = shallowMount(Reactions, {
- global: {
- plugins: [store],
- stubs: {
- NcPopover,
- },
- },
- props: reactionsProps,
-
- })
+ const wrapper = mountReactions(reactionsProps)
const reactionButtons = wrapper.findAllComponents(NcButton)
const emojiPicker = wrapper.findAllComponents(NcEmojiPicker)
// Act
@@ -171,24 +172,13 @@ describe('Reactions.vue', () => {
})
testStoreConfig.modules.messagesStore.getters.message = () => messageMock
store = createStore(testStoreConfig)
- const wrapper = shallowMount(Reactions, {
- props: reactionsProps,
- global: {
- plugins: [store],
- stubs: {
- NcEmojiPicker,
- NcPopover,
- },
- },
-
- })
+ const wrapper = mountReactions(reactionsProps)
// Assert
const reactionButtons = wrapper.findAllComponents(NcPopover)
expect(reactionButtons).toHaveLength(0)
const emojiPicker = wrapper.findComponent(NcEmojiPicker)
expect(emojiPicker.exists()).toBeFalsy()
- expect(emojiPicker.vm).toBeUndefined()
})
test('dispatches store actions upon picking an emoji from the emojipicker', async () => {
@@ -196,19 +186,8 @@ describe('Reactions.vue', () => {
vi.spyOn(reactionsStore, 'addReactionToMessage')
vuexStore.dispatch('processMessage', { token, message })
- const wrapper = shallowMount(Reactions, {
- props: {
- ...reactionsProps,
- showControls: true,
- },
- global: {
- plugins: [store],
- stubs: {
- NcEmojiPicker,
- },
- },
-
- })
+ reactionsProps.showControls = true
+ const wrapper = mountReactions(reactionsProps)
const response = generateOCSResponse({ payload: Object.assign({}, reactionsStored, { '❤️': [{ actorDisplayName: 'user1', actorId: 'actorId1', actorType: 'users' }] }) })
addReactionToMessage.mockResolvedValue(response)
@@ -232,17 +211,8 @@ describe('Reactions.vue', () => {
vuexStore.dispatch('processMessage', { token, message })
- const wrapper = shallowMount(Reactions, {
- props: reactionsProps,
- global: {
- plugins: [store],
- stubs: {
- NcEmojiPicker,
- NcPopover,
- },
- },
+ const wrapper = mountReactions(reactionsProps)
- })
const addedReaction = {
...reactionsStored,
'🎄': [...reactionsStored['🎄'], { actorDisplayName: 'user3', actorId: 'admin', actorType: 'users' }],
@@ -282,16 +252,8 @@ describe('Reactions.vue', () => {
console.debug = vi.fn()
vi.spyOn(reactionsStore, 'fetchReactions')
- const wrapper = shallowMount(Reactions, {
- props: reactionsProps,
- global: {
- plugins: [store],
- stubs: {
- NcPopover,
- },
- },
+ const wrapper = mountReactions(reactionsProps)
- })
const response = generateOCSResponse({ payload: reactionsStored })
getReactionsDetails.mockResolvedValue(response)
diff --git a/src/components/MessagesList/MessagesList.spec.js b/src/components/MessagesList/MessagesList.spec.js
index cbca670b6c7..e7495e75f4b 100644
--- a/src/components/MessagesList/MessagesList.spec.js
+++ b/src/components/MessagesList/MessagesList.spec.js
@@ -3,21 +3,57 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import { shallowMount } from '@vue/test-utils'
+import { mount } from '@vue/test-utils'
import { cloneDeep } from 'lodash'
import { createPinia, setActivePinia } from 'pinia'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
-import { createStore } from 'vuex'
-import StaticDateTime from '../UIShared/StaticDateTime.vue'
+import { ref } from 'vue'
+import { createStore, useStore } from 'vuex'
+import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
+import LoadingPlaceholder from '../UIShared/LoadingPlaceholder.vue'
+import MessagesGroup from './MessagesGroup/MessagesGroup.vue'
+import MessagesSystemGroup from './MessagesGroup/MessagesSystemGroup.vue'
import MessagesList from './MessagesList.vue'
+import router from '../../__mocks__/router.js'
import { ATTENDEE, MESSAGE } from '../../constants.ts'
import storeConfig from '../../store/storeConfig.js'
+import { useChatStore } from '../../stores/chat.ts'
+
+vi.mock('vuex', async () => {
+ const vuex = await vi.importActual('vuex')
+ return {
+ ...vuex,
+ useStore: vi.fn(),
+ }
+})
+
+const contextMessageId = ref(0)
+const loadingOldMessages = ref(0)
+const loadingNewMessages = ref(0)
+const isInitialisingMessages = ref(true)
+const isChatBeginningReached = ref(0)
+const isChatEndReached = ref(0)
+
+vi.mock('../../composables/useGetMessages.ts', async () => ({
+ useGetMessages: vi.fn(() => ({
+ contextMessageId,
+ loadingOldMessages,
+ loadingNewMessages,
+ isInitialisingMessages,
+ isChatBeginningReached,
+ isChatEndReached,
+
+ getOldMessages: vi.fn(),
+ getNewMessages: vi.fn(),
+ })),
+}))
const fakeTimestamp = (value) => new Date(value).getTime() / 1000
describe('MessagesList.vue', () => {
const TOKEN = 'XXTOKENXX'
let store
+ let chatStore
let testStoreConfig
const getVisualLastReadMessageIdMock = vi.fn()
@@ -27,6 +63,9 @@ describe('MessagesList.vue', () => {
testStoreConfig.modules.messagesStore.getters.getVisualLastReadMessageId
= vi.fn().mockReturnValue(getVisualLastReadMessageIdMock)
store = createStore(testStoreConfig)
+ useStore.mockReturnValue(store)
+
+ chatStore = useChatStore()
// scrollTo isn't implemented in JSDOM
Element.prototype.scrollTo = () => {}
@@ -34,6 +73,13 @@ describe('MessagesList.vue', () => {
afterEach(() => {
vi.clearAllMocks()
+
+ contextMessageId.value = 0
+ loadingOldMessages.value = 0
+ loadingNewMessages.value = 0
+ isInitialisingMessages.value = true
+ isChatBeginningReached.value = 0
+ isChatEndReached.value = 0
})
const messagesGroup1 = [{
@@ -48,6 +94,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2024-05-01T12:05:00'),
isReplyable: true,
+ reactions: {},
}, {
id: 110,
token: TOKEN,
@@ -60,6 +107,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2024-05-01T12:06:00'),
isReplyable: true,
+ reactions: {},
}]
const messagesGroup1OldMessage = {
@@ -74,6 +122,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2024-05-01T12:04:00'),
isReplyable: true,
+ reactions: {},
}
const messagesGroup1WithOld = [messagesGroup1OldMessage].concat(messagesGroup1)
@@ -89,6 +138,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2024-05-01T12:30:00'),
isReplyable: true,
+ reactions: {},
}, {
id: 210,
token: TOKEN,
@@ -101,6 +151,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2024-05-01T12:31:00'),
isReplyable: true,
+ reactions: {},
}]
const messagesGroup2NewMessage = {
@@ -115,6 +166,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2024-05-01T12:32:00'),
isReplyable: true,
+ reactions: {},
}
const messagesGroup2WithNew = messagesGroup2.concat([messagesGroup2NewMessage])
@@ -130,27 +182,38 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: 0, // temporary
isReplyable: true,
+ reactions: {},
}]
+ function mountMessagesList() {
+ return mount(MessagesList, {
+ global: {
+ plugins: [router, store],
+ },
+ props: {
+ token: TOKEN,
+ isChatScrolledToBottom: true,
+ },
+ })
+ }
+
describe('message grouping', () => {
/**
* @param {Array} messagesGroups List of messages that should be grouped
*/
function testGrouped(...messagesGroups) {
- messagesGroups.flat().forEach((message) => store.commit('addMessage', { token: TOKEN, message }))
- const wrapper = shallowMount(MessagesList, {
- global: { plugins: [store] },
- props: {
- token: TOKEN,
- isChatScrolledToBottom: true,
- },
+ store.commit('addConversation', {
+ token: TOKEN,
+ hasCall: false,
})
+ messagesGroups.flat().forEach((message) => store.commit('addMessage', { token: TOKEN, message }))
+ chatStore.processChatBlocks(TOKEN, messagesGroups.flat())
+ isInitialisingMessages.value = false
- const groups = wrapper.findAll('.messages-group')
-
- expect(groups.exists()).toBeTruthy()
+ const wrapper = mountMessagesList()
- groups.wrappers.forEach((group, index) => {
+ const groups = wrapper.findAllComponents('li.wrapper')
+ groups.forEach((group, index) => {
expect(group.props('messages')).toStrictEqual(messagesGroups[index])
})
@@ -161,26 +224,18 @@ describe('MessagesList.vue', () => {
* @param {Array} messages List of messages that should not be grouped
*/
function testNotGrouped(messages) {
+ store.commit('addConversation', {
+ token: TOKEN,
+ hasCall: false,
+ })
messages.forEach((message) => store.commit('addMessage', { token: TOKEN, message }))
+ chatStore.processChatBlocks(TOKEN, messages)
+ isInitialisingMessages.value = false
- const wrapper = shallowMount(MessagesList, {
- global: {
- plugins: [store],
- stubs: {
- StaticDateTime,
- },
- },
- props: {
- token: TOKEN,
- isChatScrolledToBottom: true,
- },
- })
+ const wrapper = mountMessagesList()
const groups = wrapper.findAll('.messages-group')
-
- expect(groups.exists()).toBeTruthy()
-
- groups.wrappers.forEach((group, index) => {
+ groups.forEach((group, index) => {
expect(group.props('messages')).toStrictEqual([messages[index]])
})
@@ -215,6 +270,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2019-09-14T13:00:00'),
isReplyable: true,
+ reactions: {},
}, {
id: 110,
token: TOKEN,
@@ -227,6 +283,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2020-05-10T13:00:00'),
isReplyable: true,
+ reactions: {},
}, {
id: 'temp-120',
token: TOKEN,
@@ -239,6 +296,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: 0, // temporary, matches current date
isReplyable: true,
+ reactions: {},
}])
const dateSeparators = wrapper.findAll('.messages-date')
@@ -261,6 +319,7 @@ describe('MessagesList.vue', () => {
systemMessage: 'call_started',
timestamp: fakeTimestamp('2020-05-09T13:00:00'),
isReplyable: true,
+ reactions: {},
}, {
id: 110,
token: TOKEN,
@@ -273,6 +332,7 @@ describe('MessagesList.vue', () => {
systemMessage: 'call_ended',
timestamp: fakeTimestamp('2020-05-09T13:02:00'),
isReplyable: true,
+ reactions: {},
}])
})
@@ -289,6 +349,7 @@ describe('MessagesList.vue', () => {
systemMessage: 'call_started',
timestamp: fakeTimestamp('2020-05-09T13:00:00'),
isReplyable: true,
+ reactions: {},
}, {
id: 110,
token: TOKEN,
@@ -301,6 +362,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2020-05-09T13:02:00'),
isReplyable: true,
+ reactions: {},
}])
})
@@ -317,6 +379,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2020-05-09T13:00:00'),
isReplyable: true,
+ reactions: {},
}, {
id: 110,
token: TOKEN,
@@ -329,6 +392,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2020-05-09T13:02:00'),
isReplyable: true,
+ reactions: {},
}])
})
@@ -345,6 +409,7 @@ describe('MessagesList.vue', () => {
systemMessage: 'call_started',
timestamp: fakeTimestamp('2020-05-09T13:00:00'),
isReplyable: true,
+ reactions: {},
}, {
id: 110,
token: TOKEN,
@@ -357,6 +422,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2020-05-09T13:02:00'),
isReplyable: true,
+ reactions: {},
}])
})
@@ -373,6 +439,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2024-05-01T12:05:00'),
isReplyable: true,
+ reactions: {},
}, {
id: 110,
token: TOKEN,
@@ -389,6 +456,7 @@ describe('MessagesList.vue', () => {
systemMessage: '',
timestamp: fakeTimestamp('2024-05-01T12:06:00'),
isReplyable: true,
+ reactions: {},
}])
})
})
@@ -400,56 +468,45 @@ describe('MessagesList.vue', () => {
* @param {Array} messagesGroups initial messages groups
*/
function renderMessagesList(...messagesGroups) {
- messagesGroups.flat().forEach((message) => store.commit('addMessage', { token: TOKEN, message }))
- return shallowMount(MessagesList, {
- global: { plugins: [store] },
- props: {
- token: TOKEN,
- isChatScrolledToBottom: true,
- },
+ store.commit('addConversation', {
+ token: TOKEN,
+ hasCall: false,
})
+ messagesGroups.flat().forEach((message) => store.commit('addMessage', { token: TOKEN, message }))
+ chatStore.processChatBlocks(TOKEN, messagesGroups.flat())
+ isInitialisingMessages.value = false
+ return mountMessagesList()
}
test('renders a placeholder while loading', () => {
- const wrapper = shallowMount(MessagesList, {
- global: { plugins: [store] },
- props: {
- token: TOKEN,
- isChatScrolledToBottom: true,
- },
- })
+ const wrapper = mountMessagesList()
- const groups = wrapper.findAllComponents({ name: 'MessagesGroup' })
- expect(groups.exists()).toBe(false)
+ const groups = wrapper.findAllComponents(MessagesGroup)
+ expect(groups).toHaveLength(0)
- const placeholder = wrapper.findAllComponents({ name: 'LoadingPlaceholder' })
+ const placeholder = wrapper.findComponent(LoadingPlaceholder)
expect(placeholder.exists()).toBe(true)
})
test('renders an empty content after loading', () => {
store.commit('loadedMessagesOfConversation', { token: TOKEN })
- const wrapper = shallowMount(MessagesList, {
- global: { plugins: [store] },
- props: {
- token: TOKEN,
- isChatScrolledToBottom: true,
- },
- })
+ isInitialisingMessages.value = false
- const groups = wrapper.findAllComponents({ name: 'MessagesGroup' })
- expect(groups.exists()).toBe(false)
+ const wrapper = mountMessagesList()
- const placeholder = wrapper.findAllComponents({ name: 'NcEmptyContent' })
+ const groups = wrapper.findAllComponents(MessagesGroup)
+ expect(groups).toHaveLength(0)
+
+ const placeholder = wrapper.findComponent(NcEmptyContent)
expect(placeholder.exists()).toBe(true)
})
test('renders initial group of messages', () => {
// Act
const wrapper = renderMessagesList(messagesGroup1)
- const groups = wrapper.findAllComponents({ name: 'MessagesGroup' })
+ const groups = wrapper.findAllComponents(MessagesGroup)
// Assert: groups are rendered
- expect(groups.exists()).toBe(true)
expect(groups.at(0).props()).toMatchObject({
token: TOKEN,
messages: messagesGroup1,
@@ -464,10 +521,12 @@ describe('MessagesList.vue', () => {
// Act: add new group to the store
messagesGroup2.forEach((message) => store.commit('addMessage', { token: TOKEN, message }))
+ chatStore.processChatBlocks(TOKEN, messagesGroup2, { mergeBy: 100 })
+ isInitialisingMessages.value = false
await wrapper.vm.$nextTick()
// Assert: old group nextMessageId is updated, new group is added
- const groups = wrapper.findAllComponents({ name: 'MessagesGroup' })
+ const groups = wrapper.findAllComponents(MessagesGroup)
expect(groups.at(0).props()).toMatchObject({
token: TOKEN,
messages: messagesGroup1,
@@ -490,11 +549,13 @@ describe('MessagesList.vue', () => {
// Act: add new messages to the store
store.commit('addMessage', { token: TOKEN, message: messagesGroup1OldMessage })
store.commit('addMessage', { token: TOKEN, message: messagesGroup2NewMessage })
+ chatStore.processChatBlocks(TOKEN, [messagesGroup1OldMessage], { mergeBy: 100 })
+ chatStore.processChatBlocks(TOKEN, [messagesGroup2NewMessage], { mergeBy: 100 })
+ isInitialisingMessages.value = false
await wrapper.vm.$nextTick()
// Assert: both groups are updated
- const groups = wrapper.findAllComponents({ name: 'MessagesGroup' })
- expect(groups.exists()).toBe(true)
+ const groups = wrapper.findAllComponents(MessagesGroup)
expect(groups.length).toBe(2)
expect(groups.at(0).props()).toMatchObject({
token: TOKEN,
@@ -523,11 +584,11 @@ describe('MessagesList.vue', () => {
}
store.commit('deleteMessage', { token: TOKEN, id: messagesGroup3[0].id })
store.commit('addMessage', { token: TOKEN, message })
+ chatStore.processChatBlocks(TOKEN, [message], { mergeBy: 100 })
await wrapper.vm.$nextTick()
// Assert: old group nextMessageId is updated, new group is added
- const groups = wrapper.findAllComponents({ name: 'MessagesGroup' })
- expect(groups.exists()).toBe(true)
+ const groups = wrapper.findAllComponents(MessagesGroup)
expect(groups.length).toBe(2)
expect(groups.at(0).props()).toMatchObject({
token: TOKEN,
@@ -556,11 +617,12 @@ describe('MessagesList.vue', () => {
// Act: replace temporary message with returned from server
store.commit('deleteMessage', { token: TOKEN, id: 'temp-210' })
store.commit('addMessage', { token: TOKEN, message: messagesGroup2[1] })
+ chatStore.processChatBlocks(TOKEN, [messagesGroup2[1]], { mergeBy: 100 })
+
await wrapper.vm.$nextTick()
// Assert: old group nextMessageId is updated, new group is added
- const groups = wrapper.findAllComponents({ name: 'MessagesGroup' })
- expect(groups.exists()).toBe(true)
+ const groups = wrapper.findAllComponents(MessagesGroup)
expect(groups.length).toBe(2)
expect(groups.at(1).props()).toMatchObject({
@@ -588,15 +650,18 @@ describe('MessagesList.vue', () => {
systemMessage: 'history_cleared',
timestamp: fakeTimestamp('2024-05-01T13:00:00'),
isReplyable: false,
+ reactions: {},
}
store.commit('purgeMessagesStore', TOKEN)
store.commit('addMessage', { token: TOKEN, message })
+ chatStore.processChatBlocks(TOKEN, [message], { mergeBy: 100 })
+
await wrapper.vm.$nextTick()
// Assert: old messages are removed, system message is added
- const groups = wrapper.findAllComponents({ name: 'MessagesGroup' })
- expect(groups.exists()).toBe(false)
- const groupsSystem = wrapper.findAllComponents({ name: 'MessagesSystemGroup' })
+ const groups = wrapper.findAllComponents(MessagesGroup)
+ expect(groups).toHaveLength(0)
+ const groupsSystem = wrapper.findAllComponents(MessagesSystemGroup)
expect(groupsSystem.length).toBe(1)
expect(groupsSystem.at(0).props()).toMatchObject({
token: TOKEN,
@@ -615,7 +680,7 @@ describe('MessagesList.vue', () => {
store.commit('deleteMessage', { token: TOKEN, id: messagesGroup2NewMessage.id })
await wrapper.vm.$nextTick()
- const groups = wrapper.findAllComponents({ name: 'MessagesGroup' })
+ const groups = wrapper.findAllComponents(MessagesGroup)
expect(groups.length).toBe(2)
expect(groups.at(0).props()).toMatchObject({
token: TOKEN,
diff --git a/src/components/RightSidebar/Participants/Participant.spec.js b/src/components/RightSidebar/Participants/Participant.spec.js
index 0a6e1ba64e1..2ff7cd41131 100644
--- a/src/components/RightSidebar/Participants/Participant.spec.js
+++ b/src/components/RightSidebar/Participants/Participant.spec.js
@@ -3,30 +3,26 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import { flushPromises, shallowMount } from '@vue/test-utils'
+import { flushPromises, mount } from '@vue/test-utils'
import { cloneDeep } from 'lodash'
import { createPinia, setActivePinia } from 'pinia'
import { afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest'
import { createStore } from 'vuex'
-import NcActionButton from '@nextcloud/vue/components/NcActionButton'
-import NcActionText from '@nextcloud/vue/components/NcActionText'
-import NcButton from '@nextcloud/vue/components/NcButton'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import NcDialog from '@nextcloud/vue/components/NcDialog'
-import NcInputField from '@nextcloud/vue/components/NcInputField'
-import NcListItem from '@nextcloud/vue/components/NcListItem'
import NcTextArea from '@nextcloud/vue/components/NcTextArea'
-import IconHandBackLeftOutline from 'vue-material-design-icons/HandBackLeftOutline.vue'
+import IconHandBackLeft from 'vue-material-design-icons/HandBackLeft.vue'
import IconMicrophoneOutline from 'vue-material-design-icons/MicrophoneOutline.vue'
-import IconPhoneOutline from 'vue-material-design-icons/PhoneOutline.vue'
+import IconPhoneDialOutline from 'vue-material-design-icons/PhoneDialOutline.vue'
import IconVideoOutline from 'vue-material-design-icons/VideoOutline.vue'
import AvatarWrapper from '../../AvatarWrapper/AvatarWrapper.vue'
import Participant from './Participant.vue'
+import router from '../../../__mocks__/router.js'
import { ATTENDEE, PARTICIPANT, WEBINAR } from '../../../constants.ts'
import storeConfig from '../../../store/storeConfig.js'
import { useActorStore } from '../../../stores/actor.ts'
import { useTokenStore } from '../../../stores/token.ts'
-import { findNcActionButton, findNcButton } from '../../../test-helpers.js'
+import { findNcActionButton, findNcActionText, findNcButton } from '../../../test-helpers.js'
describe('Participant.vue', () => {
const TOKEN = 'XXTOKENXX'
@@ -76,43 +72,35 @@ describe('Participant.vue', () => {
testStoreConfig = cloneDeep(storeConfig)
testStoreConfig.modules.conversationsStore.getters.conversation = () => conversationGetterMock
store = createStore(testStoreConfig)
+
+ router.push({ name: 'conversation', params: { token: TOKEN } })
})
afterEach(() => {
vi.clearAllMocks()
})
+ const ComponentStub = {
+ template: '
',
+ }
+
/**
* @param {object} participant Participant with optional user status data
* @param {boolean} showUserStatus Whether or not the user status should be shown
*/
function mountParticipant(participant, showUserStatus = false) {
- return shallowMount(Participant, {
+ return mount(Participant, {
global: {
- plugins: [store],
+ plugins: [router, store],
stubs: {
- NcActionButton,
- NcButton,
- NcCheckboxRadioSwitch,
- NcDialog,
- NcInputField,
- NcListItem,
- NcTextArea,
+ NcModal: ComponentStub,
+ NcPopover: ComponentStub,
},
},
props: {
participant,
showUserStatus,
},
- mixins: [{
- // force tooltip display for testing
- methods: {
- forceEnableTooltips() {
- this.isUserNameTooltipVisible = true
- this.isStatusTooltipVisible = true
- },
- },
- }],
})
}
@@ -120,12 +108,12 @@ describe('Participant.vue', () => {
test('renders avatar', () => {
const wrapper = mountParticipant(participant)
const avatarEl = wrapper.findComponent(AvatarWrapper)
- expect(avatarEl.exists()).toBe(true)
+ expect(avatarEl.exists()).toBeTruthy()
expect(avatarEl.props('id')).toBe('alice-actor-id')
- expect(avatarEl.props('disableTooltip')).toBe(true)
- expect(avatarEl.props('disableMenu')).toBe(false)
- expect(avatarEl.props('showUserStatus')).toBe(false)
+ expect(avatarEl.props('disableTooltip')).toBeTruthy()
+ expect(avatarEl.props('disableMenu')).toBeFalsy()
+ expect(avatarEl.props('showUserStatus')).toBeFalsy()
expect(avatarEl.props('preloadedUserStatus')).toStrictEqual({
icon: '🌧️',
message: 'rainy',
@@ -133,15 +121,15 @@ describe('Participant.vue', () => {
})
expect(avatarEl.props('name')).toBe('Alice')
expect(avatarEl.props('source')).toBe(ATTENDEE.ACTOR_TYPE.USERS)
- expect(avatarEl.props('offline')).toBe(false)
+ expect(avatarEl.props('offline')).toBeFalsy()
})
test('renders avatar with enabled status', () => {
const wrapper = mountParticipant(participant, true)
const avatarEl = wrapper.findComponent(AvatarWrapper)
- expect(avatarEl.exists()).toBe(true)
+ expect(avatarEl.exists()).toBeTruthy()
- expect(avatarEl.props('showUserStatus')).toBe(true)
+ expect(avatarEl.props('showUserStatus')).toBeTruthy()
})
test('renders avatar with guest name when empty', () => {
@@ -149,7 +137,7 @@ describe('Participant.vue', () => {
participant.actorType = ATTENDEE.ACTOR_TYPE.GUESTS
const wrapper = mountParticipant(participant)
const avatarEl = wrapper.findComponent(AvatarWrapper)
- expect(avatarEl.exists()).toBe(true)
+ expect(avatarEl.exists()).toBeTruthy()
expect(avatarEl.props('name')).toBe('')
})
@@ -158,7 +146,7 @@ describe('Participant.vue', () => {
participant.displayName = ''
const wrapper = mountParticipant(participant, true)
const avatarEl = wrapper.findComponent(AvatarWrapper)
- expect(avatarEl.exists()).toBe(true)
+ expect(avatarEl.exists()).toBeTruthy()
expect(avatarEl.props('name')).toBe('')
})
@@ -167,15 +155,16 @@ describe('Participant.vue', () => {
participant.sessionIds = []
const wrapper = mountParticipant(participant, true)
const avatarEl = wrapper.findComponent(AvatarWrapper)
- expect(avatarEl.exists()).toBe(true)
+ expect(avatarEl.exists()).toBeTruthy()
- expect(avatarEl.props('offline')).toBe(true)
+ expect(avatarEl.props('offline')).toBeTruthy()
})
})
describe('user name', () => {
/**
* Check which text is currently rendered as a name
+ * (text in user badges has a padding to separate words visually)
* @param {object} participant participant object
* @param {RegExp} regexp regex pattern which expected to be rendered
*/
@@ -187,22 +176,22 @@ describe('Participant.vue', () => {
}
const testCases = [
- ['Alice', 'alice', ATTENDEE.ACTOR_TYPE.USERS, PARTICIPANT.TYPE.USER, /^Alice$/],
- ['Alice', 'guest-id', ATTENDEE.ACTOR_TYPE.GUESTS, PARTICIPANT.TYPE.GUEST, /^Alice\s+\(guest\)$/],
- ['Alice', 'guest-id', ATTENDEE.ACTOR_TYPE.EMAILS, PARTICIPANT.TYPE.GUEST, /^Alice\s+\(guest\)$/],
- ['', 'guest-id', ATTENDEE.ACTOR_TYPE.GUESTS, PARTICIPANT.TYPE.GUEST, /^Guest\s+\(guest\)$/],
- ['Alice', 'alice', ATTENDEE.ACTOR_TYPE.USERS, PARTICIPANT.TYPE.MODERATOR, /^Alice\s+\(moderator\)$/],
- ['Alice', 'guest-id', ATTENDEE.ACTOR_TYPE.GUESTS, PARTICIPANT.TYPE.GUEST_MODERATOR, /^Alice\s+\(moderator\)\s+\(guest\)$/],
- ['Bot', ATTENDEE.BRIDGE_BOT_ID, ATTENDEE.ACTOR_TYPE.USERS, PARTICIPANT.TYPE.USER, /^Bot\s+\(bot\)$/],
+ ['Alice', 'alice', ATTENDEE.ACTOR_TYPE.USERS, PARTICIPANT.TYPE.USER, 'Alice'],
+ ['Alice', 'guest-id', ATTENDEE.ACTOR_TYPE.GUESTS, PARTICIPANT.TYPE.GUEST, 'Alice(guest)'],
+ ['Alice', 'guest-id', ATTENDEE.ACTOR_TYPE.EMAILS, PARTICIPANT.TYPE.GUEST, 'Alice(guest)'],
+ ['', 'guest-id', ATTENDEE.ACTOR_TYPE.GUESTS, PARTICIPANT.TYPE.GUEST, 'Guest(guest)'],
+ ['Alice', 'alice', ATTENDEE.ACTOR_TYPE.USERS, PARTICIPANT.TYPE.MODERATOR, 'Alice(moderator)'],
+ ['Alice', 'guest-id', ATTENDEE.ACTOR_TYPE.GUESTS, PARTICIPANT.TYPE.GUEST_MODERATOR, 'Alice(moderator)(guest)'],
+ ['Bot', ATTENDEE.BRIDGE_BOT_ID, ATTENDEE.ACTOR_TYPE.USERS, PARTICIPANT.TYPE.USER, 'Bot(bot)'],
]
const testLobbyCases = [
- ['Alice', 'alice', ATTENDEE.ACTOR_TYPE.USERS, PARTICIPANT.TYPE.USER, /^Alice\s+\(in the lobby\)$/],
- ['Alice', 'guest-id', ATTENDEE.ACTOR_TYPE.GUESTS, PARTICIPANT.TYPE.GUEST, /^Alice\s+\(guest\)\s+\(in the lobby\)$/],
- ['Alice', 'guest-id', ATTENDEE.ACTOR_TYPE.EMAILS, PARTICIPANT.TYPE.GUEST, /^Alice\s+\(guest\)\s+\(in the lobby\)$/],
- ['', 'guest-id', ATTENDEE.ACTOR_TYPE.GUESTS, PARTICIPANT.TYPE.GUEST, /^Guest\s+\(guest\)\s+\(in the lobby\)$/],
- ['Alice', 'alice', ATTENDEE.ACTOR_TYPE.USERS, PARTICIPANT.TYPE.MODERATOR, /^Alice\s+\(moderator\)$/],
- ['Alice', 'guest-id', ATTENDEE.ACTOR_TYPE.GUESTS, PARTICIPANT.TYPE.GUEST_MODERATOR, /^Alice\s+\(moderator\)\s+\(guest\)$/],
+ ['Alice', 'alice', ATTENDEE.ACTOR_TYPE.USERS, PARTICIPANT.TYPE.USER, 'Alice(in the lobby)'],
+ ['Alice', 'guest-id', ATTENDEE.ACTOR_TYPE.GUESTS, PARTICIPANT.TYPE.GUEST, 'Alice(guest)(in the lobby)'],
+ ['Alice', 'guest-id', ATTENDEE.ACTOR_TYPE.EMAILS, PARTICIPANT.TYPE.GUEST, 'Alice(guest)(in the lobby)'],
+ ['', 'guest-id', ATTENDEE.ACTOR_TYPE.GUESTS, PARTICIPANT.TYPE.GUEST, 'Guest(guest)(in the lobby)'],
+ ['Alice', 'alice', ATTENDEE.ACTOR_TYPE.USERS, PARTICIPANT.TYPE.MODERATOR, 'Alice(moderator)'],
+ ['Alice', 'guest-id', ATTENDEE.ACTOR_TYPE.GUESTS, PARTICIPANT.TYPE.GUEST_MODERATOR, 'Alice(moderator)(guest)'],
]
it.each(testCases)(
@@ -281,7 +270,7 @@ describe('Participant.vue', () => {
describe('call icons', () => {
let getParticipantRaisedHandMock
- const components = [IconVideoOutline, IconPhoneOutline, IconMicrophoneOutline, IconHandBackLeftOutline]
+ const components = [IconVideoOutline, IconPhoneDialOutline, IconMicrophoneOutline, IconHandBackLeft]
/**
* Check which icons are currently rendered
@@ -324,13 +313,13 @@ describe('Participant.vue', () => {
})
test('renders phone call icon', async () => {
participant.inCall = PARTICIPANT.CALL_FLAG.WITH_PHONE
- checkStateIconsRendered(participant, IconPhoneOutline)
+ checkStateIconsRendered(participant, IconPhoneDialOutline)
})
test('renders hand raised icon', async () => {
participant.inCall = PARTICIPANT.CALL_FLAG.WITH_VIDEO
getParticipantRaisedHandMock = vi.fn().mockReturnValue({ state: true })
- checkStateIconsRendered(participant, IconHandBackLeftOutline)
+ checkStateIconsRendered(participant, IconHandBackLeft)
expect(getParticipantRaisedHandMock).toHaveBeenCalledWith(['session-id-alice'])
})
test('renders video call icon when joined with multiple', async () => {
@@ -356,7 +345,7 @@ describe('Participant.vue', () => {
async function testCanDemote() {
const wrapper = mountParticipant(participant)
const actionButton = findNcActionButton(wrapper, 'Demote from moderator')
- expect(actionButton.exists()).toBe(true)
+ expect(actionButton.exists()).toBeTruthy()
await actionButton.find('button').trigger('click')
@@ -372,7 +361,7 @@ describe('Participant.vue', () => {
async function testCannotDemote() {
const wrapper = mountParticipant(participant)
const actionButton = findNcActionButton(wrapper, 'Demote to moderator')
- expect(actionButton.exists()).toBe(false)
+ expect(actionButton.exists()).toBeFalsy()
}
test('allows a moderator to demote a moderator', async () => {
@@ -448,7 +437,7 @@ describe('Participant.vue', () => {
async function testCanPromote() {
const wrapper = mountParticipant(participant)
const actionButton = findNcActionButton(wrapper, 'Promote to moderator')
- expect(actionButton.exists()).toBe(true)
+ expect(actionButton.exists()).toBeTruthy()
await actionButton.find('button').trigger('click')
@@ -464,7 +453,7 @@ describe('Participant.vue', () => {
async function testCannotPromote() {
const wrapper = mountParticipant(participant)
const actionButton = findNcActionButton(wrapper, 'Promote to moderator')
- expect(actionButton.exists()).toBe(false)
+ expect(actionButton.exists()).toBeFalsy()
}
test('allows a moderator to promote a user to moderator', async () => {
@@ -541,7 +530,7 @@ describe('Participant.vue', () => {
participant.invitedActorId = 'alice@mail.com'
const wrapper = mountParticipant(participant)
const actionButton = findNcActionButton(wrapper, 'Resend invitation')
- expect(actionButton.exists()).toBe(true)
+ expect(actionButton.exists()).toBeTruthy()
await actionButton.find('button').trigger('click')
@@ -556,14 +545,14 @@ describe('Participant.vue', () => {
participant.actorType = ATTENDEE.ACTOR_TYPE.EMAILS
const wrapper = mountParticipant(participant)
const actionButton = findNcActionButton(wrapper, 'Resend invitation')
- expect(actionButton.exists()).toBe(false)
+ expect(actionButton.exists()).toBeFalsy()
})
test('does not display resend invitations action when not an email actor', async () => {
participant.actorType = ATTENDEE.ACTOR_TYPE.USERS
const wrapper = mountParticipant(participant)
const actionButton = findNcActionButton(wrapper, 'Resend invitation')
- expect(actionButton.exists()).toBe(false)
+ expect(actionButton.exists()).toBeFalsy()
})
})
describe('removing participant', () => {
@@ -582,7 +571,7 @@ describe('Participant.vue', () => {
async function testCanRemove(buttonText = 'Remove participant') {
const wrapper = mountParticipant(participant)
const actionButton = findNcActionButton(wrapper, buttonText)
- expect(actionButton.exists()).toBe(true)
+ expect(actionButton.exists()).toBeTruthy()
await actionButton.find('button').trigger('click')
@@ -590,6 +579,7 @@ describe('Participant.vue', () => {
expect(dialog.exists()).toBeTruthy()
const button = findNcButton(dialog, 'Remove')
+ expect(button.exists()).toBeTruthy()
await button.find('button').trigger('click')
expect(removeAction).toHaveBeenCalledWith(expect.anything(), {
@@ -606,7 +596,7 @@ describe('Participant.vue', () => {
async function testCannotRemove() {
const wrapper = mountParticipant(participant)
const actionButton = findNcActionButton(wrapper, 'Remove participant')
- expect(actionButton.exists()).toBe(false)
+ expect(actionButton.exists()).toBeFalsy()
}
/**
@@ -616,7 +606,7 @@ describe('Participant.vue', () => {
async function testCanBan(buttonText = 'Remove participant', internalNote = 'test note') {
const wrapper = mountParticipant(participant)
const actionButton = findNcActionButton(wrapper, buttonText)
- expect(actionButton.exists()).toBe(true)
+ expect(actionButton.exists()).toBeTruthy()
await actionButton.find('button').trigger('click')
@@ -648,7 +638,7 @@ describe('Participant.vue', () => {
async function testCannotBan(buttonText = 'Remove participant') {
const wrapper = mountParticipant(participant)
const actionButton = findNcActionButton(wrapper, buttonText)
- expect(actionButton.exists()).toBe(true)
+ expect(actionButton.exists()).toBeTruthy()
await actionButton.find('button').trigger('click')
@@ -753,13 +743,9 @@ describe('Participant.vue', () => {
*/
function testPinVisible() {
const wrapper = mountParticipant(participant)
- let actionTexts = wrapper.findAllComponents(NcActionText)
- actionTexts = actionTexts.filter((actionText) => {
- return actionText.props('name').includes('PIN')
- })
-
- expect(actionTexts.exists()).toBe(true)
- expect(actionTexts.at(0).text()).toBe('123 456 78')
+ const actionText = findNcActionText(wrapper, 'Dial-in PIN')
+ expect(actionText.exists()).toBeTruthy()
+ expect(actionText.text()).toContain('123 456 78')
}
test('allows moderators to see dial-in PIN when available', () => {
@@ -778,24 +764,16 @@ describe('Participant.vue', () => {
conversation.participantType = PARTICIPANT.TYPE.USER
participant.attendeePin = '12345678'
const wrapper = mountParticipant(participant)
- let actionTexts = wrapper.findAllComponents(NcActionText)
- actionTexts = actionTexts.filter((actionText) => {
- return actionText.props('title').includes('PIN')
- })
-
- expect(actionTexts.exists()).toBe(false)
+ const actionText = findNcActionText(wrapper, 'Dial-in PIN')
+ expect(actionText.exists()).toBeFalsy()
})
test('does not show PIN field when not set', () => {
conversation.participantType = PARTICIPANT.TYPE.MODERATOR
participant.attendeePin = ''
const wrapper = mountParticipant(participant)
- let actionTexts = wrapper.findAllComponents(NcActionText)
- actionTexts = actionTexts.filter((actionText) => {
- return actionText.props('title').includes('PIN')
- })
-
- expect(actionTexts.exists()).toBe(false)
+ const actionText = findNcActionText(wrapper, 'Dial-in PIN')
+ expect(actionText.exists()).toBeFalsy()
})
})
})
diff --git a/src/components/RoomSelector.spec.js b/src/components/RoomSelector.spec.js
index 83a1ed97e8a..6f438d38527 100644
--- a/src/components/RoomSelector.spec.js
+++ b/src/components/RoomSelector.spec.js
@@ -5,22 +5,37 @@
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
-import { flushPromises, shallowMount } from '@vue/test-utils'
+import { flushPromises, mount } from '@vue/test-utils'
+import { cloneDeep } from 'lodash'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import { createStore, useStore } from 'vuex'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcDialog from '@nextcloud/vue/components/NcDialog'
import ConversationSearchResult from './LeftSidebar/ConversationsList/ConversationSearchResult.vue'
import RoomSelector from './RoomSelector.vue'
+import router from '../__mocks__/router.js'
import { CONVERSATION } from '../constants.ts'
+import storeConfig from '../store/storeConfig.js'
import { useTokenStore } from '../stores/token.ts'
import { generateOCSResponse } from '../test-helpers.js'
+vi.mock('vuex', async () => {
+ const vuex = await vi.importActual('vuex')
+ return {
+ ...vuex,
+ useStore: vi.fn(),
+ }
+})
+
vi.mock('@nextcloud/axios', () => ({
default: {
get: vi.fn(),
},
}))
+const ComponentStub = {
+ template: '
',
+}
const ConversationsSearchListVirtualStub = {
props: {
conversations: Array,
@@ -41,7 +56,14 @@ describe('RoomSelector', () => {
let conversations
let tokenStore
+ let testStoreConfig
+ let store = null
+
beforeEach(() => {
+ testStoreConfig = cloneDeep(storeConfig)
+ store = createStore(testStoreConfig)
+ useStore.mockReturnValue(store)
+
tokenStore = useTokenStore()
tokenStore.token = 'current-token'
@@ -101,15 +123,15 @@ describe('RoomSelector', () => {
axios.get.mockResolvedValue(generateOCSResponse({ payload }))
- const wrapper = shallowMount(RoomSelector, {
+ const wrapper = mount(RoomSelector, {
global: {
+ plugins: [router],
stubs: {
ConversationsSearchListVirtual: ConversationsSearchListVirtualStub,
- ConversationSearchResult,
- NcDialog,
+ NcModal: ComponentStub,
},
},
- props: props,
+ props,
})
// need to wait for re-render, otherwise the list is not rendered yet
await flushPromises()
@@ -137,6 +159,7 @@ describe('RoomSelector', () => {
it('excludes current conversation if mounted inside of Talk', async () => {
// Arrange
+ await router.push({ name: 'conversation', params: { token: 'current-token' } })
const wrapper = await mountRoomSelector({ isPlugin: false })
expect(axios.get).toHaveBeenCalledWith(
generateOcsUrl('/apps/spreed/api/v4/room'),
@@ -226,7 +249,7 @@ describe('RoomSelector', () => {
await input.vm.$emit('update:modelValue', 'conversation')
// Act: click trailing button
- await input.vm.$emit('trailing-button-click')
+ await input.find('button').trigger('click')
// Assert
const list = wrapper.findAllComponents({ name: 'NcListItem' })
@@ -236,47 +259,29 @@ describe('RoomSelector', () => {
it('emits select event on select', async () => {
// Arrange
const wrapper = await mountRoomSelector()
- const eventHandler = vi.fn()
- wrapper.vm.$on('select', eventHandler)
// Act: click on second item, then click 'Select conversation'
- const list = wrapper.findComponent({ name: 'ConversationsSearchListVirtual' })
const items = wrapper.findAllComponents(ConversationSearchResult)
await items.at(1).vm.$emit('click', items.at(1).props('item'))
- expect(items.at(1).emitted('click')[0][0]).toMatchObject(conversations[0])
- expect(list.emitted('select')[0][0]).toMatchObject(conversations[0])
await wrapper.findComponent(NcButton).vm.$emit('click')
// Assert
- expect(eventHandler).toHaveBeenCalledWith(conversations[0])
+ const emitted = wrapper.emitted('select')
+ expect(emitted).toBeTruthy()
+ expect(emitted[0][0]).toMatchObject(conversations[0])
})
it('emits close event', async () => {
// Arrange
const wrapper = await mountRoomSelector()
- const eventHandler = vi.fn()
- wrapper.vm.$on('close', eventHandler)
-
- // Act: close dialog
- const dialog = wrapper.findComponent(NcDialog)
- await dialog.vm.$emit('update:open')
-
- // Assert
- expect(eventHandler).toHaveBeenCalled()
- })
-
- it('emits close event on $root as plugin', async () => {
- // Arrange
- const wrapper = await mountRoomSelector({ isPlugin: true })
- const eventHandler = vi.fn()
- wrapper.vm.$root.$on('close', eventHandler)
// Act: close dialog
const dialog = wrapper.findComponent(NcDialog)
await dialog.vm.$emit('update:open')
// Assert
- expect(eventHandler).toHaveBeenCalled()
+ const emitted = wrapper.emitted('close')
+ expect(emitted).toBeTruthy()
})
})
})
diff --git a/src/store/fileUploadStore.spec.js b/src/store/fileUploadStore.spec.js
index da7a0cdf4d1..b72f2c67584 100644
--- a/src/store/fileUploadStore.spec.js
+++ b/src/store/fileUploadStore.spec.js
@@ -8,6 +8,7 @@ import { getUploader } from '@nextcloud/upload'
import { cloneDeep } from 'lodash'
import { createPinia, setActivePinia } from 'pinia'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
+import { ref } from 'vue'
import { createStore } from 'vuex'
import { getDavClient } from '../services/DavClient.ts'
import { shareFile } from '../services/filesSharingServices.ts'
@@ -16,6 +17,11 @@ import { useActorStore } from '../stores/actor.ts'
import { findUniquePath } from '../utils/fileUpload.js'
import fileUploadStore from './fileUploadStore.js'
+const getThreadMock = ref(0)
+vi.mock('../composables/useGetThreadId.ts', () => ({
+ useGetThreadId: vi.fn(() => getThreadMock),
+}))
+
vi.mock('../services/DavClient.ts', () => ({
getDavClient: vi.fn(),
}))
@@ -72,7 +78,7 @@ describe('fileUploadStore', () => {
storeConfig.getters.getAttachmentFolder = vi.fn().mockReturnValue(() => '/Talk')
store = createStore(storeConfig)
getDavClient.mockReturnValue(client)
- // getUploader.mockReturnValue({ upload: uploadMock })
+ getUploader.mockReturnValue({ upload: uploadMock })
console.error = vi.fn()
})
@@ -338,7 +344,7 @@ describe('fileUploadStore', () => {
const uploads = store.getters.getInitialisedUploads('upload-id1')
expect(uploads).toHaveLength(1)
- expect(uploads[0][1].file).toBe(files[0])
+ expect(uploads[0][1].file).toStrictEqual(files[0])
})
test('discard an entire upload', async () => {
diff --git a/src/test-helpers.js b/src/test-helpers.js
index 4a6444d4855..97fb2f59160 100644
--- a/src/test-helpers.js
+++ b/src/test-helpers.js
@@ -4,21 +4,42 @@
*/
import { cloneDeep } from 'lodash'
+import NcActionButton from '@nextcloud/vue/components/NcActionButton'
+import NcActionText from '@nextcloud/vue/components/NcActionText'
+import NcButton from '@nextcloud/vue/components/NcButton'
+import NcListItem from '@nextcloud/vue/components/NcListItem'
// helpers
/**
*
* @param {import('@vue/test-utils').Wrapper} wrapper root wrapper to look for NcActionButton
* @param {string | Array} text or array of possible texts to look for NcButtons
- * @return {import('@vue/test-utils').Wrapper}
+ * @return {import('@vue/test-utils').Wrapper | import('@vue/test-utils').ErrorWrapper}
*/
function findNcActionButton(wrapper, text) {
- const actionButtons = wrapper.findAllComponents({ name: 'NcActionButton' })
+ const actionButtons = wrapper.findAllComponents(NcActionButton)
const items = (Array.isArray(text))
? actionButtons.filter((actionButton) => text.includes(actionButton.text()))
: actionButtons.filter((actionButton) => actionButton.text() === text)
- if (!items.exists()) {
- return items
+ if (!items.length) {
+ return wrapper.findComponent({ name: 'VTU__return-error-wrapper' }) // Returns ErrorWrapper
+ }
+ return items[0]
+}
+
+/**
+ *
+ * @param {import('@vue/test-utils').Wrapper} wrapper root wrapper to look for NcActionText
+ * @param {string | Array} text or array of possible texts to look for
+ * @return {import('@vue/test-utils').Wrapper | import('@vue/test-utils').ErrorWrapper}
+ */
+function findNcActionText(wrapper, text) {
+ const actionTexts = wrapper.findAllComponents(NcActionText)
+ const items = (Array.isArray(text))
+ ? actionTexts.filter((actionText) => text.includes(actionText.text()))
+ : actionTexts.filter((actionText) => actionText.text() === text || actionText.text().includes(text))
+ if (!items.length) {
+ return wrapper.findComponent({ name: 'VTU__return-error-wrapper' }) // Returns ErrorWrapper
}
return items[0]
}
@@ -30,12 +51,12 @@ function findNcActionButton(wrapper, text) {
* @return {import('@vue/test-utils').Wrapper}
*/
function findNcButton(wrapper, text) {
- const buttons = wrapper.findAllComponents({ name: 'NcButton' })
+ const buttons = wrapper.findAllComponents(NcButton)
const items = (Array.isArray(text))
? buttons.filter((button) => text.includes(button.text()) || text.includes(button.vm.ariaLabel))
: buttons.filter((button) => button.text() === text || button.vm.ariaLabel === text)
- if (!items.exists()) {
- return items
+ if (!items.length) {
+ return wrapper.findComponent({ name: 'VTU__return-error-wrapper' }) // Returns ErrorWrapper
}
return items[0]
}
@@ -47,7 +68,7 @@ function findNcButton(wrapper, text) {
* @return {import('@vue/test-utils').Wrapper}
*/
function findNcListItems(wrapper, text) {
- const listItems = wrapper.findAllComponents({ name: 'NcListItem' })
+ const listItems = wrapper.findAllComponents(NcListItem)
return (Array.isArray(text))
? listItems.filter((listItem) => text.includes(listItem.vm.name))
: listItems.filter((listItem) => listItem.vm.name === text)
@@ -114,6 +135,7 @@ function generateOCSErrorResponse({ headers = {}, payload = {}, status }) {
export {
findNcActionButton,
+ findNcActionText,
findNcButton,
findNcListItems,
generateOCSErrorResponse,
diff --git a/src/test-setup.js b/src/test-setup.js
index bac4d2683f9..f40355bbbef 100644
--- a/src/test-setup.js
+++ b/src/test-setup.js
@@ -26,6 +26,10 @@ vi.mock('@nextcloud/dialogs', () => ({
TOAST_PERMANENT_TIMEOUT: -1,
}))
+vi.mock('@nextcloud/files/dav', () => ({
+ defaultRemoteURL: () => 'https://nextcloud.local/remote.php/dav',
+}))
+
vi.mock('@nextcloud/initial-state', () => ({
loadState: vi.fn().mockImplementation((app, key, fallback) => {
return fallback
diff --git a/vitest.config.js b/vitest.config.js
index e77d263d086..5707916236b 100644
--- a/vitest.config.js
+++ b/vitest.config.js
@@ -12,12 +12,6 @@ export default defineConfig({
assetsInclude: ['**/*.tflite', '**/*.wasm'],
test: {
include: ['src/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
- exclude: [
- // TODO: migrate to Vue 3
- 'src/components/**',
- // FIXME: broken after Vue 3 migration
- 'src/store/fileUploadStore.spec.js',
- ],
server: {
deps: {
// Allow importing CSS from dependencies