diff --git a/webapp/channels/src/components/profile_popover/profile_popover_call_button_wrapper/index.test.tsx b/webapp/channels/src/components/profile_popover/profile_popover_call_button_wrapper/index.test.tsx index 2209b9419b..a06dcdf8b2 100644 --- a/webapp/channels/src/components/profile_popover/profile_popover_call_button_wrapper/index.test.tsx +++ b/webapp/channels/src/components/profile_popover/profile_popover_call_button_wrapper/index.test.tsx @@ -1,7 +1,14 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {isUserInCall} from './index'; +import {screen, render} from '@testing-library/react'; +import React from 'react'; +import {Provider} from 'react-redux'; +import configureStore from 'redux-mock-store'; + +import {TestHelper} from '../../../utils/test_helper'; + +import CallButton, {isUserInCall} from './index'; describe('isUserInCall', () => { test('missing state', () => { @@ -51,3 +58,385 @@ describe('isUserInCall', () => { } as any, 'userA', 'channelID')).toBe(true); }); }); + +describe('CallButton', () => { + const mockStore = configureStore(); + const userId = 'user1'; + const currentUserId = 'current_user'; + const dmChannelId = 'dm_channel_id'; + const baseProps = { + userId, + currentUserId, + fullname: 'Test User', + username: 'testuser', + }; + const PluginComponent = () => { + return ; + }; + + test('should not render when calls are disabled', () => { + const store = mockStore({ + 'plugins-com.mattermost.calls': { + enabled: false, + }, + entities: { + channels: { + channels: { + currentChannelId: dmChannelId, + channels: { + current_channel_id: TestHelper.getChannelMock({ + id: dmChannelId, + name: `${currentUserId}__${userId}`, + display_name: `${currentUserId}__${userId}`, + delete_at: 0, + type: 'D', + }), + }, + }, + }, + users: { + profiles: { + [currentUserId]: TestHelper.getUserMock({ + id: currentUserId, + roles: 'system_user', + }), + }, + }, + }, + plugins: { + plugins: { + 'com.mattermost.calls': { + id: 'com.mattermost.calls', + version: '1.0.0', + }, + }, + }, + }); + + render( + + + , + ); + + expect(screen.queryByTestId('startCallButton')).not.toBeInTheDocument(); + }); + + test('should render for admin even in test mode', () => { + const store = mockStore({ + 'plugins-com.mattermost.calls': { + enabled: true, + config: { + DefaultEnabled: false, + }, + }, + entities: { + channels: { + currentChannelId: dmChannelId, + channels: { + current_channel_id: TestHelper.getChannelMock({ + id: dmChannelId, + name: `${currentUserId}__${userId}`, + display_name: `${currentUserId}__${userId}`, + delete_at: 0, + type: 'D', + }), + }, + myMembers: { + [dmChannelId]: TestHelper.getChannelMembershipMock({channel_id: dmChannelId}), + }, + }, + users: { + currentUserId, + profiles: { + [currentUserId]: TestHelper.getUserMock({ + id: currentUserId, + roles: 'system_admin', + }), + }, + }, + }, + plugins: { + plugins: { + 'com.mattermost.calls': { + id: 'com.mattermost.calls', + version: '1.0.0', + }, + }, + components: { + CallButton: [{ + id: 'CallButton', + plugin_id: 'com.mattermost.calls', + button: PluginComponent, + }], + }, + }, + views: { + rhs: { + isSidebarOpen: false, + }, + }, + }); + + render( + + + , + ); + + expect(screen.getByLabelText('Start Call')).toBeInTheDocument(); + }); + + test('should render when calls are enabled and not in test mode', () => { + const store = mockStore({ + 'plugins-com.mattermost.calls': { + enabled: true, + config: { + DefaultEnabled: true, + }, + channels: {}, + sessions: {}, + }, + entities: { + channels: { + currentChannelId: dmChannelId, + channels: { + current_channel_id: TestHelper.getChannelMock({ + id: dmChannelId, + name: `${currentUserId}__${userId}`, + display_name: `${currentUserId}__${userId}`, + delete_at: 0, + type: 'D', + }), + }, + myMembers: { + [dmChannelId]: TestHelper.getChannelMembershipMock({channel_id: dmChannelId}), + }, + }, + users: { + currentUserId, + profiles: { + [currentUserId]: TestHelper.getUserMock({ + id: currentUserId, + roles: 'system_admin', + }), + }, + }, + }, + plugins: { + plugins: { + 'com.mattermost.calls': { + id: 'com.mattermost.calls', + version: '1.0.0', + }, + }, + components: { + CallButton: [{ + id: 'CallButton', + plugin_id: 'com.mattermost.calls', + button: PluginComponent, + }], + }, + }, + views: { + rhs: { + isSidebarOpen: false, + }, + }, + }); + + render( + + + , + ); + + expect(screen.getByLabelText('Start Call')).toBeInTheDocument(); + }); + + test('should render when channel is explicitly enabled regardless of test mode', () => { + const store = mockStore({ + 'plugins-com.mattermost.calls': { + enabled: true, + config: { + DefaultEnabled: false, + }, + channels: { + [dmChannelId]: {enabled: true}, + }, + sessions: {}, + }, + entities: { + channels: { + currentChannelId: dmChannelId, + channels: { + current_channel_id: TestHelper.getChannelMock({ + id: dmChannelId, + name: `${currentUserId}__${userId}`, + display_name: `${currentUserId}__${userId}`, + delete_at: 0, + type: 'D', + }), + }, + myMembers: { + [dmChannelId]: TestHelper.getChannelMembershipMock({channel_id: dmChannelId}), + }, + }, + users: { + currentUserId, + profiles: { + [currentUserId]: TestHelper.getUserMock({ + id: currentUserId, + roles: 'system_admin', + }), + }, + }, + }, + plugins: { + plugins: { + 'com.mattermost.calls': { + id: 'com.mattermost.calls', + version: '1.0.0', + }, + }, + components: { + CallButton: [{ + id: 'CallButton', + plugin_id: 'com.mattermost.calls', + button: PluginComponent, + }], + }, + }, + views: { + rhs: { + isSidebarOpen: false, + }, + }, + }); + + render( + + + , + ); + + expect(screen.getByLabelText('Start Call')).toBeInTheDocument(); + }); + + test('should not render when channel is explicitly disabled', () => { + const store = mockStore({ + 'plugins-com.mattermost.calls': { + enabled: true, + config: { + DefaultEnabled: true, + }, + channels: { + [dmChannelId]: {enabled: false}, + }, + sessions: {}, + }, + entities: { + channels: { + currentChannelId: dmChannelId, + channels: { + current_channel_id: TestHelper.getChannelMock({ + id: dmChannelId, + name: `${currentUserId}__${userId}`, + display_name: `${currentUserId}__${userId}`, + delete_at: 0, + type: 'D', + }), + }, + myMembers: { + [dmChannelId]: TestHelper.getChannelMembershipMock({channel_id: dmChannelId}), + }, + }, + users: { + profiles: { + [currentUserId]: TestHelper.getUserMock({ + id: currentUserId, + roles: 'system_admin', + }), + }, + }, + }, + plugins: { + plugins: { + 'com.mattermost.calls': { + id: 'com.mattermost.calls', + version: '1.0.0', + }, + }, + }, + }); + + render( + + + , + ); + + expect(screen.queryByLabelText('Start Call')).not.toBeInTheDocument(); + }); + + test('should disable button when there is an ongoing call', () => { + const store = mockStore({ + 'plugins-com.mattermost.calls': { + enabled: true, + config: { + DefaultEnabled: true, + }, + sessions: { + [dmChannelId]: { + session1: { + user_id: currentUserId, + }, + }, + }, + }, + entities: { + channels: { + currentChannelId: dmChannelId, + channels: { + current_channel_id: TestHelper.getChannelMock({ + id: dmChannelId, + name: `${currentUserId}__${userId}`, + display_name: `${currentUserId}__${userId}`, + delete_at: 0, + type: 'D', + }), + }, + myMembers: { + [dmChannelId]: TestHelper.getChannelMembershipMock({channel_id: dmChannelId}), + }, + }, + users: { + profiles: { + [currentUserId]: TestHelper.getUserMock({ + id: currentUserId, + roles: 'system_admin', + }), + }, + }, + }, + plugins: { + plugins: { + 'com.mattermost.calls': { + id: 'com.mattermost.calls', + version: '1.0.0', + }, + }, + }, + }); + + render( + + + , + ); + + const button = screen.getByLabelText('Call with Test User is ongoing'); + expect(button).toBeInTheDocument(); + expect(button).toBeDisabled(); + }); +}); diff --git a/webapp/channels/src/selectors/calls.ts b/webapp/channels/src/selectors/calls.ts index 0d6d750d28..c380dbfde4 100644 --- a/webapp/channels/src/selectors/calls.ts +++ b/webapp/channels/src/selectors/calls.ts @@ -5,12 +5,18 @@ import semver from 'semver'; import type {CallsConfig, UserSessionState} from '@mattermost/calls-common/lib/types'; +import {createSelector} from 'mattermost-redux/selectors/create_selector'; + import {suitePluginIds} from 'utils/constants'; import type {GlobalState} from 'types/store'; const CALLS_PLUGIN = `plugins-${suitePluginIds.calls}`; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +const pluginState = (state: GlobalState) => state[CALLS_PLUGIN]; + export function isCallsEnabled(state: GlobalState, minVersion = '0.4.2') { return Boolean(state.plugins.plugins[suitePluginIds.calls] && semver.gte(String(semver.clean(state.plugins.plugins[suitePluginIds.calls].version || '0.0.0')), minVersion)); @@ -18,32 +24,33 @@ export function isCallsEnabled(state: GlobalState, minVersion = '0.4.2') { // isCallsRingingEnabledOnServer is the flag for the ringing/notification feature in calls export function isCallsRingingEnabledOnServer(state: GlobalState) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return Boolean(state[CALLS_PLUGIN]?.callsConfig?.EnableRinging); + return Boolean(pluginState(state)?.callsConfig?.EnableRinging); } -export function getSessionsInCalls(state: GlobalState): Record> { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return state[CALLS_PLUGIN]?.sessions || {}; -} +export const getSessionsInCalls = createSelector( + 'getSessionsInCalls', + pluginState, + (state): Record> => { + return state?.sessions || {}; + }, +); -export function getCallsConfig(state: GlobalState): CallsConfig { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return state[CALLS_PLUGIN]?.callsConfig; -} +export const getCallsConfig = createSelector( + 'getCallsConfig', + pluginState, + (state): CallsConfig => { + return state?.callsConfig || {}; + }, +); export function getCallsChannelState(state: GlobalState, channelId: string): {enabled?: boolean} { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - if (!state[CALLS_PLUGIN] || !state[CALLS_PLUGIN].channels) { + const callsState = pluginState(state); + + if (!callsState || !callsState.channels || !callsState.channels[channelId]) { return {}; } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return state[CALLS_PLUGIN].channels[channelId] || {}; + + return callsState.channels[channelId]; } export function callsChannelExplicitlyEnabled(state: GlobalState, channelId: string) {