diff --git a/addons/mail/static/src/components/call_action_list/call_action_list.xml b/addons/mail/static/src/components/call_action_list/call_action_list.xml index 1fd181ee825..bbe3db6e29b 100644 --- a/addons/mail/static/src/components/call_action_list/call_action_list.xml +++ b/addons/mail/static/src/components/call_action_list/call_action_list.xml @@ -5,7 +5,7 @@
- + - + + + + + + + + - - + + - - - - - - - -
diff --git a/addons/mail/static/src/components/call_settings_menu/call_settings_menu.scss b/addons/mail/static/src/components/call_settings_menu/call_settings_menu.scss index 10abe986ea5..c328ce62320 100644 --- a/addons/mail/static/src/components/call_settings_menu/call_settings_menu.scss +++ b/addons/mail/static/src/components/call_settings_menu/call_settings_menu.scss @@ -2,13 +2,14 @@ // Layout // ------------------------------------------------------------------ +.o_CallSettingsMenu_categoryTitle { + padding: map-get($spacers, 2) 0; +} + .o_CallSettingsMenu { -webkit-user-select: none; user-select: none; -} - -.o_CallSettingsMenu_option { - min-height: 40px; + padding: map-get($spacers, 2) map-get($spacers, 3); } .o_CallSettingsMenu_optionInputGroupInput { diff --git a/addons/mail/static/src/components/call_settings_menu/call_settings_menu.xml b/addons/mail/static/src/components/call_settings_menu/call_settings_menu.xml index 3c69b71ede5..4b8100eeee9 100644 --- a/addons/mail/static/src/components/call_settings_menu/call_settings_menu.xml +++ b/addons/mail/static/src/components/call_settings_menu/call_settings_menu.xml @@ -2,68 +2,78 @@ -
-
- -
-
- -
- -
- -
-
Press a key to register it as the push-to-talk shortcut
-
-
+
Press a key to register it as the push-to-talk shortcut
+
+ +
+
+ +
+ +
+
+
+
+
Video Settings
+
+
- - -
- -
-
+
diff --git a/addons/mail/static/src/components/call_view/call_view.xml b/addons/mail/static/src/components/call_view/call_view.xml index 68a7ce9b2c0..177c98c3c4f 100644 --- a/addons/mail/static/src/components/call_view/call_view.xml +++ b/addons/mail/static/src/components/call_view/call_view.xml @@ -9,15 +9,6 @@ - - - - - - - - - diff --git a/addons/mail/static/src/components/chat_window/chat_window.xml b/addons/mail/static/src/components/chat_window/chat_window.xml index 2961796f4f8..cdd6119ee34 100644 --- a/addons/mail/static/src/components/chat_window/chat_window.xml +++ b/addons/mail/static/src/components/chat_window/chat_window.xml @@ -22,6 +22,9 @@ + + + diff --git a/addons/mail/static/src/components/chat_window_header/chat_window_header.xml b/addons/mail/static/src/components/chat_window_header/chat_window_header.xml index ff1ae1dff88..27d1a58228c 100644 --- a/addons/mail/static/src/components/chat_window_header/chat_window_header.xml +++ b/addons/mail/static/src/components/chat_window_header/chat_window_header.xml @@ -55,6 +55,16 @@ + +
+ +
+
+ +
+ +
+
diff --git a/addons/mail/static/src/components/thread_view/thread_view.scss b/addons/mail/static/src/components/thread_view/thread_view.scss index bac6e4d204e..697d3175792 100644 --- a/addons/mail/static/src/components/thread_view/thread_view.scss +++ b/addons/mail/static/src/components/thread_view/thread_view.scss @@ -14,6 +14,10 @@ width: $o-mail-chat-sidebar-width; } +.o_ThreadView_callSettingsMenu { + width: $o-mail-chat-sidebar-width; +} + .o_ThreadView_core { min-width: 0; } diff --git a/addons/mail/static/src/components/thread_view/thread_view.xml b/addons/mail/static/src/components/thread_view/thread_view.xml index e3a277ba2d6..1663af50397 100644 --- a/addons/mail/static/src/components/thread_view/thread_view.xml +++ b/addons/mail/static/src/components/thread_view/thread_view.xml @@ -54,6 +54,9 @@ + + +
diff --git a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.scss b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.scss index 3f9ea9a6248..0d3ec06d72a 100644 --- a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.scss +++ b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.scss @@ -19,8 +19,17 @@ // Style // ------------------------------------------------------------------ -.o_ThreadViewTopbar_button:hover { - background-color: $gray-300; +.o_ThreadViewTopbar_button { + + &:not(.o-isActive):hover { + background-color: $gray-300; + } + &.o-isActive { + background-color: $gray-300; + } + &.o-isActive:hover { + background-color: $gray-400; + } } // use selector specificity to override bootstrap style diff --git a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.xml b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.xml index 00ad4d1d05c..eac58a55f7b 100644 --- a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.xml +++ b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.xml @@ -58,8 +58,8 @@ - @@ -69,8 +69,18 @@ - + + + + + + diff --git a/addons/mail/static/src/models/call_action_list_view.js b/addons/mail/static/src/models/call_action_list_view.js index e87b2238f7a..38983a06d76 100644 --- a/addons/mail/static/src/models/call_action_list_view.js +++ b/addons/mail/static/src/models/call_action_list_view.js @@ -42,10 +42,10 @@ registerModel({ * @param {MouseEvent} ev */ async onClickRejectCall(ev) { - if (this.channel.hasPendingRtcRequest) { + if (this.thread.hasPendingRtcRequest) { return; } - await this.channel.leaveCall(); + await this.thread.leaveCall(); }, /** * @param {MouseEvent} ev @@ -57,19 +57,19 @@ registerModel({ * @param {MouseEvent} ev */ async onClickToggleAudioCall(ev) { - if (this.channel.hasPendingRtcRequest) { + if (this.thread.hasPendingRtcRequest) { return; } - await this.channel.toggleCall(); + await this.thread.toggleCall(); }, /** * @param {MouseEvent} ev */ async onClickToggleVideoCall(ev) { - if (this.channel.hasPendingRtcRequest) { + if (this.thread.hasPendingRtcRequest) { return; } - await this.channel.toggleCall({ + await this.thread.toggleCall({ startWithVideo: true, }); }, @@ -78,10 +78,10 @@ registerModel({ * @returns {string|FieldCommand} */ _computeCallButtonTitle() { - if (!this.channel) { + if (!this.thread) { return clear(); } - if (this.channel.rtc) { + if (this.thread.rtc) { return this.env._t("Disconnect"); } else { return this.env._t("Join Call"); @@ -160,10 +160,6 @@ registerModel({ compute: '_computeCameraButtonTitle', default: '', }), - channel: one('Thread', { - related: 'callMainView.channel', - required: true, - }), headphoneButtonTitle: attr({ compute: '_computeHeadphoneButtonTitle', default: '', @@ -183,5 +179,9 @@ registerModel({ compute: '_computeScreenSharingButtonTitle', default: '', }), + thread: one('Thread', { + related: 'callMainView.thread', + required: true, + }), }, }); diff --git a/addons/mail/static/src/models/call_main_view.js b/addons/mail/static/src/models/call_main_view.js index 815e7dbd667..5eb9c80ad5a 100644 --- a/addons/mail/static/src/models/call_main_view.js +++ b/addons/mail/static/src/models/call_main_view.js @@ -168,10 +168,6 @@ registerModel({ identifying: true, inverse: 'callMainView', }), - channel: one('Thread', { - related: 'callView.channel', - required: true, - }), hasSidebarButton: attr({ compute: '_computeHasSidebarButton', }), @@ -197,6 +193,10 @@ registerModel({ inverse: 'callMainViewAsShowOverlay', isCausal: true, }), + thread: one('Thread', { + related: 'callView.thread', + required: true, + }), tileContainerRef: attr(), tileHeight: attr({ default: 0, diff --git a/addons/mail/static/src/models/call_option_menu.js b/addons/mail/static/src/models/call_option_menu.js index 0bcd6cf87a3..645a4fd9f56 100644 --- a/addons/mail/static/src/models/call_option_menu.js +++ b/addons/mail/static/src/models/call_option_menu.js @@ -2,22 +2,10 @@ import { registerModel } from '@mail/model/model_core'; import { attr, one } from '@mail/model/model_field'; -import { clear } from '@mail/model/model_field_command'; registerModel({ name: 'CallOptionMenu', recordMethods: { - /** - * @param {Event} ev - */ - onChangeVideoFilterCheckbox(ev) { - const filterVideoGrid = ev.target.checked; - const activeRtcSession = this.callView.activeRtcSession; - if (filterVideoGrid && activeRtcSession && !activeRtcSession.videoStream) { - this.callView.update({ activeRtcSession: clear() }); - } - this.callView.update({ filterVideoGrid }); - }, /** * Creates and download a file that contains the logs of the current RTC call. * @@ -38,27 +26,6 @@ registerModel({ window.URL.revokeObjectURL(url); this.component.trigger('o-popover-close'); }, - /** - * @param {MouseEvent} ev - */ - onClickActivateFullScreen(ev) { - this.callView.activateFullScreen(); - this.component.trigger('o-popover-close'); - }, - /** - * @param {MouseEvent} ev - */ - onClickDeactivateFullScreen(ev) { - this.callView.deactivateFullScreen(); - this.component.trigger('o-popover-close'); - }, - /** - * @param {MouseEvent} ev - */ - onClickOptions(ev) { - this.messaging.userSetting.callSettingsMenu.toggle(); - this.component.trigger('o-popover-close'); - }, }, fields: { /** diff --git a/addons/mail/static/src/models/call_settings_menu.js b/addons/mail/static/src/models/call_settings_menu.js index 7444bc0f125..146fe2a0321 100644 --- a/addons/mail/static/src/models/call_settings_menu.js +++ b/addons/mail/static/src/models/call_settings_menu.js @@ -3,10 +3,12 @@ import { browser } from "@web/core/browser/browser"; import { registerModel } from '@mail/model/model_core'; -import { attr, one } from '@mail/model/model_field'; +import { clear } from '@mail/model/model_field_command'; +import { one } from '@mail/model/model_field'; registerModel({ name: 'CallSettingsMenu', + identifyingMode: 'xor', lifecycleHooks: { _created() { browser.addEventListener('keydown', this._onKeyDown); @@ -26,7 +28,7 @@ registerModel({ }, onChangePushToTalk() { if (this.userSetting.usePushToTalk) { - this.update({ + this.userSetting.update({ isRegisteringKey: false, }); } @@ -44,16 +46,45 @@ registerModel({ onChangeThreshold(ev) { this.userSetting.setThresholdValue(parseFloat(ev.target.value)); }, + /** + * @param {Event} ev + */ + onChangeVideoFilterCheckbox(ev) { + const showOnlyVideo = ev.target.checked; + this.thread.channel.update({ showOnlyVideo }); + if (!this.callView) { + return; + } + const activeRtcSession = this.callView.activeRtcSession; + if (showOnlyVideo && activeRtcSession && !activeRtcSession.videoStream) { + this.callView.update({ activeRtcSession: clear() }); + } + }, onClickRegisterKeyButton() { - this.update({ + this.userSetting.update({ isRegisteringKey: !this.isRegisteringKey, }); }, - toggle() { - this.update({ isOpen: !this.isOpen }); + _computeCallView() { + if (this.threadViewOwner) { + return this.threadViewOwner.callView; + } + if (this.chatWindowOwner && this.chatWindowOwner.threadView) { + return this.chatWindowOwner.threadView.callView; + } + return clear(); + }, + _computeThread() { + if (this.threadViewOwner) { + return this.threadViewOwner.thread; + } + if (this.chatWindowOwner) { + return this.chatWindowOwner.thread; + } + return clear(); }, _onKeyDown(ev) { - if (!this.isRegisteringKey) { + if (!this.userSetting.isRegisteringKey) { return; } ev.stopPropagation(); @@ -61,29 +92,33 @@ registerModel({ this.userSetting.setPushToTalkKey(ev); }, _onKeyUp(ev) { - if (!this.isRegisteringKey) { + if (!this.userSetting.isRegisteringKey) { return; } ev.stopPropagation(); ev.preventDefault(); - this.update({ + this.userSetting.update({ isRegisteringKey: false, }); }, }, fields: { - isOpen: attr({ - default: false, + callView: one('CallView', { + compute: '_computeCallView', }), - /** - * true if listening to keyboard input to register the push to talk key. - */ - isRegisteringKey: attr({ - default: false, - }), - userSetting: one('UserSetting', { + chatWindowOwner: one('ChatWindow', { identifying: true, inverse: 'callSettingsMenu', }), + thread: one('Thread', { + compute: '_computeThread', + }), + threadViewOwner: one('ThreadView', { + identifying: true, + inverse: 'callSettingsMenu', + }), + userSetting: one('UserSetting', { + related: 'messaging.userSetting', + }), }, }); diff --git a/addons/mail/static/src/models/call_view.js b/addons/mail/static/src/models/call_view.js index 57762a25172..6dc2e2537b0 100644 --- a/addons/mail/static/src/models/call_view.js +++ b/addons/mail/static/src/models/call_view.js @@ -84,9 +84,12 @@ registerModel({ return clear(); }, _computeFilteredChannelMembers() { + if (!this.channel) { + return clear(); + } const channelMembers = []; - for (const channelMember of this.channel.channel.callParticipants) { - if (this.filterVideoGrid && !channelMember.isStreaming) { + for (const channelMember of this.channel.callParticipants) { + if (this.channel.showOnlyVideo && this.thread.videoCount > 0 && !channelMember.isStreaming) { continue; } channelMembers.push(channelMember); @@ -97,7 +100,7 @@ registerModel({ * @private */ _computeIsMinimized() { - if (!this.threadView) { + if (!this.threadView || !this.thread) { return true; } if (this.isFullScreen || this.threadView.compact) { @@ -106,7 +109,7 @@ registerModel({ if (this.activeRtcSession) { return false; } - return !this.channel.rtc || this.channel.videoCount === 0; + return !this.thread.rtc || this.thread.videoCount === 0; }, /** * @private @@ -127,14 +130,16 @@ registerModel({ */ _onChangeRtcChannel() { this.deactivateFullScreen(); - this.update({ filterVideoGrid: false }); + if (!this.thread && !this.thread.rtc) { + this.channel.update({ showOnlyVideo: false }); + } }, /** * @private */ _onChangeVideoCount() { - if (this.channel.videoCount === 0) { - this.update({ filterVideoGrid: false }); + if (this.thread.videoCount === 0) { + this.channel.update({ showOnlyVideo: false }); } }, /** @@ -172,19 +177,12 @@ registerModel({ inverse: 'callView', isCausal: true, }), - channel: one('Thread', { - related: 'threadView.thread', - required: true, + channel: one('Channel', { + related: 'thread.channel', }), filteredChannelMembers: many('ChannelMember', { compute: '_computeFilteredChannelMembers', }), - /** - * Determines whether we only display the videos or all the participants - */ - filterVideoGrid: attr({ - default: false, - }), /** * Determines if the viewer should be displayed fullScreen. */ @@ -222,6 +220,10 @@ registerModel({ settingsTitle: attr({ compute: '_computeSettingsTitle', }), + thread: one('Thread', { + related: 'threadView.thread', + required: true, + }), /** * ThreadView on which the call view is attached. */ @@ -232,11 +234,11 @@ registerModel({ }, onChanges: [ { - dependencies: ['channel.rtc'], + dependencies: ['thread.rtc'], methodName: '_onChangeRtcChannel', }, { - dependencies: ['channel.videoCount'], + dependencies: ['thread.videoCount'], methodName: '_onChangeVideoCount', }, ], diff --git a/addons/mail/static/src/models/channel.js b/addons/mail/static/src/models/channel.js index 862c4b10b82..aab8942b0ee 100644 --- a/addons/mail/static/src/models/channel.js +++ b/addons/mail/static/src/models/channel.js @@ -104,6 +104,12 @@ registerModel({ inverse: 'channelAsOnlineMember', sort: '_sortMembers', }), + /** + * Determines whether we only display the participants who broadcast a video or all of them. + */ + showOnlyVideo: attr({ + default: false, + }), thread: one('Thread', { compute: '_computeThread', inverse: 'channel', diff --git a/addons/mail/static/src/models/chat_window.js b/addons/mail/static/src/models/chat_window.js index d6994d3a04c..4f31d8ae7f8 100644 --- a/addons/mail/static/src/models/chat_window.js +++ b/addons/mail/static/src/models/chat_window.js @@ -194,6 +194,13 @@ registerModel({ this.fold(); } }, + /** + * @param {MouseEvent} ev + */ + onClickHideCallSettingsMenu(ev) { + markEventHandled(ev, 'ChatWindow.onClickCommand'); + this.update({ isCallSettingsMenuOpen: false }); + }, /** * Handles click on the "stop adding users" button. * @@ -240,6 +247,16 @@ registerModel({ this.channelInvitationForm.searchPartnersToInvite(); } }, + /** + * @param {MouseEvent} ev + */ + onClickShowCallSettingsMenu(ev) { + markEventHandled(ev, 'ChatWindow.onClickCommand'); + this.update({ + isCallSettingsMenuOpen: true, + isMemberListOpened: false, + }); + }, /** * @param {MouseEvent} ev */ @@ -247,6 +264,7 @@ registerModel({ markEventHandled(ev, 'ChatWindow.onClickShowMemberList'); this.update({ channelInvitationForm: clear(), + isCallSettingsMenuOpen: false, isMemberListOpened: true, }); }, @@ -342,6 +360,16 @@ registerModel({ this.thread.notifyFoldStateToServer('open'); } }, + /** + * @private + * @returns {FieldCommand} + */ + _computeCallSettingsMenu() { + if (this.isCallSettingsMenuOpen) { + return {}; + } + return clear(); + }, /** * @private * @returns {FieldCommand} @@ -404,7 +432,7 @@ registerModel({ * @returns {boolean} */ _computeHasThreadView() { - return this.isVisible && !this.isFolded && !!this.thread && !this.isMemberListOpened && !this.channelInvitationForm; + return this.isVisible && !this.isFolded && !!this.thread && !this.isMemberListOpened && !this.channelInvitationForm && !this.isCallSettingsMenuOpen; }, /** * @private @@ -559,6 +587,14 @@ registerModel({ }, }, fields: { + /** + * Model for the component with the controls for RTC related settings. + */ + callSettingsMenu: one('CallSettingsMenu', { + compute: '_computeCallSettingsMenu', + inverse: 'chatWindowOwner', + isCausal: true, + }), /** * Determines the channel invitation form displayed by this chat window * (if any). Only makes sense if hasInviteFeature is true. @@ -609,6 +645,9 @@ registerModel({ hasThreadView: attr({ compute: '_computeHasThreadView', }), + isCallSettingsMenuOpen: attr({ + default: false, + }), /** * Determine whether the chat window should be programmatically * focused by observed component of chat window. Those components diff --git a/addons/mail/static/src/models/rtc.js b/addons/mail/static/src/models/rtc.js index 3c47606cc65..a79d04b0fdf 100644 --- a/addons/mail/static/src/models/rtc.js +++ b/addons/mail/static/src/models/rtc.js @@ -311,6 +311,9 @@ registerModel({ if (this.disconnectAudioMonitor) { this.disconnectAudioMonitor(); } + if (!this.currentRtcSession) { + return; + } if (this.messaging.userSetting.usePushToTalk || !this.channel || !this.audioTrack) { this.currentRtcSession.update({ isTalking: false }); await this._updateLocalAudioTrackEnabledState(); @@ -1023,7 +1026,7 @@ registerModel({ if (this.currentRtcSession.isMute) { return; } - if (this.messaging.userSetting.callSettingsMenu.isRegisteringKey) { + if (this.messaging.userSetting.isRegisteringKey) { return; } this.messaging.browser.clearTimeout(this.pushToTalkTimeout); diff --git a/addons/mail/static/src/models/thread_view.js b/addons/mail/static/src/models/thread_view.js index 038a29ae7b0..86b1f67ee0b 100644 --- a/addons/mail/static/src/models/thread_view.js +++ b/addons/mail/static/src/models/thread_view.js @@ -82,6 +82,12 @@ registerModel({ messageListViewMessageViewItem.messageView.startEditing(); } }, + _computeCallSettingsMenu() { + if (this.isCallSettingsMenuOpen) { + return {}; + } + return clear(); + }, /** * @private */ @@ -322,6 +328,14 @@ registerModel({ }, }, fields: { + /** + * Model for the component with the controls for RTC related settings. + */ + callSettingsMenu: one('CallSettingsMenu', { + compute: '_computeCallSettingsMenu', + inverse: 'threadViewOwner', + isCausal: true, + }), channelMemberListView: one('ChannelMemberListView', { compute: '_computeChannelMemberListView', inverse: 'threadViewOwner', @@ -383,6 +397,9 @@ registerModel({ hasTopbar: attr({ related: 'threadViewer.hasTopbar', }), + isCallSettingsMenuOpen: attr({ + default: false, + }), isComposerFocused: attr({ related: 'composerView.isFocused', }), diff --git a/addons/mail/static/src/models/thread_view_topbar.js b/addons/mail/static/src/models/thread_view_topbar.js index e6c552238ca..f3cc650e133 100644 --- a/addons/mail/static/src/models/thread_view_topbar.js +++ b/addons/mail/static/src/models/thread_view_topbar.js @@ -26,6 +26,12 @@ registerModel({ startWithVideo: true, }); }, + /** + * @param {Event} ev + */ + onClickHideCallSettingsMenu(ev) { + this.threadView.update({ isCallSettingsMenuOpen: false }); + }, /** * Handles click on the "hide member list" button. * @@ -64,13 +70,26 @@ registerModel({ } await this.thread.toggleCall(); }, + /** + * @param {Event} ev + */ + onClickShowCallSettingsMenu(ev) { + // FIXME maybe find another way to prevent both being open at the same time? + this.threadView.update({ + isCallSettingsMenuOpen: true, + isMemberListOpened: false, + }); + }, /** * Handles click on the "show member list" button. * * @param {Event} ev */ onClickShowMemberList(ev) { - this.threadView.update({ isMemberListOpened: true }); + this.threadView.update({ + isCallSettingsMenuOpen: false, + isMemberListOpened: true, + }); }, /** * Handles click on the "thread name" of this top bar. diff --git a/addons/mail/static/src/models/user_setting.js b/addons/mail/static/src/models/user_setting.js index c2a3e207a7b..ccee9a45fae 100644 --- a/addons/mail/static/src/models/user_setting.js +++ b/addons/mail/static/src/models/user_setting.js @@ -3,7 +3,7 @@ import { browser } from "@web/core/browser/browser"; import { registerModel } from '@mail/model/model_core'; -import { attr, one } from '@mail/model/model_field'; +import { attr } from '@mail/model/model_field'; import { clear } from '@mail/model/model_field_command'; /** @@ -282,15 +282,13 @@ registerModel({ audioInputDeviceId: attr({ default: '', }), - /** - * Model for the component with the controls for RTC related settings. - */ - callSettingsMenu: one('CallSettingsMenu', { - default: {}, - inverse: 'userSetting', - isCausal: true, - }), globalSettingsTimeout: attr(), + /** + * true if listening to keyboard input to register the push to talk key. + */ + isRegisteringKey: attr({ + default: false, + }), /** * String that encodes the push-to-talk key with its modifiers. */ diff --git a/addons/mail/static/tests/qunit_suite_tests/components/chat_window_manager_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/chat_window_manager_tests.js index 14126fd72b3..f429c4af5b6 100644 --- a/addons/mail/static/tests/qunit_suite_tests/components/chat_window_manager_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/components/chat_window_manager_tests.js @@ -409,7 +409,7 @@ QUnit.test('new message autocomplete should automatically select first result', }); QUnit.test('chat window: basic rendering', async function (assert) { - assert.expect(14); + assert.expect(15); const pyEnv = await startServer(); const mailChannelId1 = pyEnv['mail.channel'].create({ name: "General" }); @@ -450,8 +450,8 @@ QUnit.test('chat window: basic rendering', async function (assert) { ); assert.strictEqual( chatWindowHeader.querySelectorAll(`:scope .o_ChatWindowHeader_command`).length, - 5, - "should have 5 commands in header part" + 6, + "should have 6 commands in header part" ); assert.strictEqual( chatWindowHeader.querySelectorAll(`:scope .o_ChatWindowHeader_commandPhone`).length, @@ -468,6 +468,11 @@ QUnit.test('chat window: basic rendering', async function (assert) { 1, "should have command to show the member list" ); + assert.strictEqual( + chatWindowHeader.querySelectorAll(`:scope .o_ChatWindowHeader_commandShowCallSettingsMenu`).length, + 1, + "should have command to show the call settings menu" + ); assert.strictEqual( chatWindowHeader.querySelectorAll(`:scope .o_ChatWindowHeader_commandExpand`).length, 1,