[MM-63838] Fix potential TypeError when Calls is disabled (#30867)

* Fix potential TypeError when Calls is disabled

* Use createSelector
Этот коммит содержится в:
Claudio Costa
2025-05-16 08:36:57 -06:00
коммит произвёл GitHub
родитель bc561620cb
Коммит d69925a415
2 изменённых файлов: 416 добавлений и 20 удалений

Просмотреть файл

@@ -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 <button>{'Start Call'}</button>;
};
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(
<Provider store={store}>
<CallButton {...baseProps}/>
</Provider>,
);
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(
<Provider store={store}>
<CallButton {...baseProps}/>
</Provider>,
);
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(
<Provider store={store}>
<CallButton {...baseProps}/>
</Provider>,
);
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(
<Provider store={store}>
<CallButton {...baseProps}/>
</Provider>,
);
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(
<Provider store={store}>
<CallButton {...baseProps}/>
</Provider>,
);
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(
<Provider store={store}>
<CallButton {...baseProps}/>
</Provider>,
);
const button = screen.getByLabelText('Call with Test User is ongoing');
expect(button).toBeInTheDocument();
expect(button).toBeDisabled();
});
});

Просмотреть файл

@@ -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<string, Record<string, UserSessionState>> {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return state[CALLS_PLUGIN]?.sessions || {};
}
export const getSessionsInCalls = createSelector(
'getSessionsInCalls',
pluginState,
(state): Record<string, Record<string, UserSessionState>> => {
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) {