MM-59065 - New channel menu using new menu system (#30093)
* New channel menu using new menu system * fix e2e-tests * remove extraneous separator * lint fix * fix test after merge * update to pass properties to first menu item * fix e2etest * refactor: Update channel header menu items to use const event handlers * refactor: Extract plugin item click handler in channel header menu * refactor: Improve error handling and button click handlers in mobile channel header plugins * lint fixes * updates for code reveiw * run i18n-extract * fix unit test * fix: Close channel dropdown menu by clicking channel header title * fix: Use keyboard escape to close channel dropdown menu in e2e tests * fix cypress test * fix: Resolve MUI Menu component fragment rendering issue * cleanup * remove unneccessary css * fixing testing issues --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
8eadf849bb
Коммит
fd717cfa64
@@ -100,7 +100,8 @@ describe('Settings > Sidebar > General', () => {
|
|||||||
cy.uiClose();
|
cy.uiClose();
|
||||||
|
|
||||||
// # Open channel menu and click 'Add Members'
|
// # Open channel menu and click 'Add Members'
|
||||||
cy.uiOpenChannelMenu('Add Members');
|
cy.uiOpenChannelMenu('Members');
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
|
|
||||||
// * Verify that the modal is open
|
// * Verify that the modal is open
|
||||||
cy.get('#addUsersToChannelModal').should('be.visible').findByText(`Add people to ${channel.display_name}`);
|
cy.get('#addUsersToChannelModal').should('be.visible').findByText(`Add people to ${channel.display_name}`);
|
||||||
|
|||||||
@@ -48,55 +48,41 @@ describe('Archive channel header spec', () => {
|
|||||||
// * The dropdown menu of the channel header should be visible;
|
// * The dropdown menu of the channel header should be visible;
|
||||||
cy.get('#channelLeaveChannel').should('be.visible');
|
cy.get('#channelLeaveChannel').should('be.visible');
|
||||||
|
|
||||||
// * Rename menu option should be visible;
|
// * Channel Settings submenu should be visible;
|
||||||
cy.get('#channelRename').should('be.visible');
|
cy.get('#channelSettings').should('be.visible');
|
||||||
|
|
||||||
// * Edit header menu option should be visible;
|
|
||||||
cy.get('#channelEditHeader').should('be.visible');
|
|
||||||
|
|
||||||
// * Edit purpose menu option should be visible;
|
|
||||||
cy.get('#channelEditPurpose').should('be.visible');
|
|
||||||
|
|
||||||
// * Convert channel to private menu option should be visible;
|
|
||||||
cy.get('#channelConvertToPrivate').should('be.visible');
|
|
||||||
|
|
||||||
// * Archive channel menu option should be visible;
|
// * Archive channel menu option should be visible;
|
||||||
cy.get('#channelArchiveChannel').should('be.visible');
|
cy.get('#channelArchiveChannel').should('be.visible');
|
||||||
|
|
||||||
// * Add members menu option should be visible;
|
// * Members menu option should be visible;
|
||||||
cy.get('#channelInviteMembers').should('be.visible');
|
cy.get('#channelMembers').should('be.visible');
|
||||||
|
|
||||||
// * Notification preferences option should be visible;
|
// * Notification preferences option should be visible;
|
||||||
cy.get('#channelNotificationPreferences').should('be.visible');
|
cy.get('#channelNotificationPreferences').should('be.visible');
|
||||||
|
|
||||||
// # Close the channel dropdown menu
|
// # Close the channel dropdown menu using keyboard escape
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('body').type('{esc}{esc}');
|
||||||
|
|
||||||
// # Archive the channel
|
// # Archive the channel
|
||||||
cy.uiArchiveChannel();
|
cy.uiArchiveChannel();
|
||||||
|
|
||||||
// * Rename menu option should not be visible;
|
// # click on channel drop-down menu
|
||||||
cy.get('#channelRename').should('not.exist');
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
// * Edit header menu option should not be visible;
|
|
||||||
cy.get('#channelEditHeader').should('not.exist');
|
|
||||||
|
|
||||||
// * Edit purpose menu option should not be visible;
|
|
||||||
cy.get('#channelEditPurpose').should('not.exist');
|
|
||||||
|
|
||||||
// * Convert channel to private menu option should not be visible;
|
|
||||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
|
||||||
|
|
||||||
// * Archive channel menu option should not be visible;
|
// * Archive channel menu option should not be visible;
|
||||||
cy.get('#channelArchiveChannel').should('not.exist');
|
cy.get('#channelArchiveChannel').should('not.exist');
|
||||||
|
|
||||||
// * Add members menu option should not be visible;
|
// * Channel Settings submenu should be visible;
|
||||||
cy.get('#channelInviteMembers').should('not.exist');
|
cy.get('#channelSettings').should('not.exist');
|
||||||
|
|
||||||
|
// * Members menu option should be visible;
|
||||||
|
// as it now displays in RHS
|
||||||
|
cy.get('#channelMembers').should('be.visible');
|
||||||
|
|
||||||
// * Notification preferences option should not be visible;
|
// * Notification preferences option should not be visible;
|
||||||
cy.get('#channelNotificationPreferences').should('not.exist');
|
cy.get('#channelNotificationPreferences').should('not.exist');
|
||||||
|
|
||||||
// # Close the channel dropdown menu
|
// # Close the channel dropdown menu using keyboard escape
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('body').type('{esc}{esc}');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,14 +28,11 @@ describe('Archive channel members spec', () => {
|
|||||||
// # click on channel drop-down menu
|
// # click on channel drop-down menu
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
// * View members menu option should not be visible;
|
// * Members menu option should be visible;
|
||||||
cy.get('#channelViewMembers').should('not.exist');
|
cy.get('#channelMembers').should('be.visible');
|
||||||
|
|
||||||
// * Manage members menu option should be visible;
|
|
||||||
cy.get('#channelManageMembers').should('be.visible');
|
|
||||||
|
|
||||||
// # Close the channel dropdown menu
|
// # Close the channel dropdown menu
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('body').type('{esc}{esc}');
|
||||||
|
|
||||||
// # Archive the channel
|
// # Archive the channel
|
||||||
cy.uiArchiveChannel();
|
cy.uiArchiveChannel();
|
||||||
@@ -43,11 +40,8 @@ describe('Archive channel members spec', () => {
|
|||||||
// # click on channel drop-down menu
|
// # click on channel drop-down menu
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
// * Manage members menu option should not be visible;
|
|
||||||
cy.get('#channelManageMembers').should('not.exist');
|
|
||||||
|
|
||||||
// # click on view members menu option;
|
// # click on view members menu option;
|
||||||
cy.get('#channelViewMembers button').should('be.visible').click();
|
cy.get('#channelMembers').should('be.visible').click();
|
||||||
|
|
||||||
// * Ensure there are no options to change channel roles or membership
|
// * Ensure there are no options to change channel roles or membership
|
||||||
cy.uiGetRHS().findByText('Manage').should('not.exist');
|
cy.uiGetRHS().findByText('Manage').should('not.exist');
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ describe('Channels', () => {
|
|||||||
cy.get('#channelHeaderTitle').should('contain', testChannel.display_name).click();
|
cy.get('#channelHeaderTitle').should('contain', testChannel.display_name).click();
|
||||||
|
|
||||||
// * Verify that the menu is opened
|
// * Verify that the menu is opened
|
||||||
cy.get('.Menu__content').should('be.visible').within(() => {
|
cy.get('#channelHeaderDropdownMenu').should('be.visible').within(() => {
|
||||||
// # Archive the channel
|
// # Archive the channel
|
||||||
cy.findByText('Archive Channel').should('be.visible').click();
|
cy.findByText('Archive Channel').should('be.visible').click();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ describe('Channel Settings - Channel Header', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||||
|
|
||||||
// # Click on the channel name in the channel header to open the channel menu options
|
// # Click on the channel name in the channel header to open the channel menu options
|
||||||
cy.get(`[aria-label="${channel.name.split('-').join(' ')} channel menu"]`).click();
|
cy.get(`[aria-label="${channel.display_name.split('-').join(' ').toLowerCase()} channel menu"]`).click();
|
||||||
|
|
||||||
// # Select the "Edit Channel Header" option from the dropdown
|
// # Select the "Edit Channel Header" option from the dropdown
|
||||||
|
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||||
cy.findByText('Edit Channel Header').click();
|
cy.findByText('Edit Channel Header').click();
|
||||||
|
|
||||||
// # Type something in the header edit box
|
// # Type something in the header edit box
|
||||||
|
|||||||
@@ -205,15 +205,12 @@ describe('Channel members RHS', () => {
|
|||||||
// # Go to test channel
|
// # Go to test channel
|
||||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||||
|
|
||||||
cy.uiOpenChannelMenu('Manage Members');
|
cy.uiOpenChannelMenu('Members');
|
||||||
|
|
||||||
// * RHS Container should be open in edit mode
|
// * RHS Container should be open in edit mode
|
||||||
cy.get('#rhsContainer').then((rhsContainer) => {
|
cy.get('#rhsContainer').then((rhsContainer) => {
|
||||||
cy.wrap(rhsContainer).findByText('Members').should('be.visible');
|
cy.wrap(rhsContainer).findByText('Members').should('be.visible');
|
||||||
cy.wrap(rhsContainer).findByText(testChannel.display_name).should('be.visible');
|
cy.wrap(rhsContainer).findByText(testChannel.display_name).should('be.visible');
|
||||||
|
|
||||||
// Done button should be visible
|
|
||||||
cy.wrap(rhsContainer).findByText('Done').should('be.visible');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -267,7 +264,7 @@ describe('Channel members RHS', () => {
|
|||||||
// # Go to test channel
|
// # Go to test channel
|
||||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||||
|
|
||||||
cy.uiOpenChannelMenu('View Members');
|
cy.uiOpenChannelMenu('Members');
|
||||||
|
|
||||||
// * RHS Container should be open in edit mode
|
// * RHS Container should be open in edit mode
|
||||||
ensureChannelMembersRHSExists(testChannel);
|
ensureChannelMembersRHSExists(testChannel);
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ describe('Channel Settings', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||||
|
|
||||||
// # Go to channel dropdown > Rename channel
|
// # Go to channel dropdown > Rename channel
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||||
cy.findByText('Rename Channel').click();
|
cy.findByText('Rename Channel').click();
|
||||||
|
|
||||||
// # Try to enter existing URL and save
|
// # Try to enter existing URL and save
|
||||||
@@ -63,7 +64,7 @@ describe('Channel Settings', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||||
|
|
||||||
// # Go to channel dropdown > Mute channel
|
// # Go to channel dropdown > Mute channel
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
cy.get('#channelHeaderDropdownMenu').should('exist').
|
cy.get('#channelHeaderDropdownMenu').should('exist').
|
||||||
findByText('Mute Channel').should('be.visible').click();
|
findByText('Mute Channel').should('be.visible').click();
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ describe('Channel Settings', () => {
|
|||||||
get('a').should('have.class', 'muted');
|
get('a').should('have.class', 'muted');
|
||||||
|
|
||||||
// # Click Unmute channel while menu is open
|
// # Click Unmute channel while menu is open
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
cy.get('#channelHeaderDropdownMenu').should('exist').
|
cy.get('#channelHeaderDropdownMenu').should('exist').
|
||||||
findByText('Unmute Channel').should('be.visible').click();
|
findByText('Unmute Channel').should('be.visible').click();
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ describe('Close direct messages', () => {
|
|||||||
it('Through channel header dropdown menu', () => {
|
it('Through channel header dropdown menu', () => {
|
||||||
createAndVisitDMChannel([testUser.id, otherUser.id]).then((channel) => {
|
createAndVisitDMChannel([testUser.id, otherUser.id]).then((channel) => {
|
||||||
// # Open channel header dropdown menu and click on Close Direct Message
|
// # Open channel header dropdown menu and click on Close Direct Message
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
cy.findByText('Close Direct Message').click();
|
cy.findByText('Close Direct Message').click();
|
||||||
|
|
||||||
verifyChannelWasProperlyClosed(channel.name);
|
verifyChannelWasProperlyClosed(channel.name);
|
||||||
@@ -104,7 +104,7 @@ describe('Close group messages', () => {
|
|||||||
it('Through channel header dropdown menu', () => {
|
it('Through channel header dropdown menu', () => {
|
||||||
createAndVisitGMChannel([otherUser1, otherUser2]).then((channel) => {
|
createAndVisitGMChannel([otherUser1, otherUser2]).then((channel) => {
|
||||||
// # Open channel header dropdown menu and click on Close Direct Message
|
// # Open channel header dropdown menu and click on Close Direct Message
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
cy.findByText('Close Group Message').click();
|
cy.findByText('Close Group Message').click();
|
||||||
|
|
||||||
verifyChannelWasProperlyClosed(channel.name);
|
verifyChannelWasProperlyClosed(channel.name);
|
||||||
|
|||||||
@@ -41,14 +41,20 @@ describe('Channels', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||||
|
|
||||||
// # Click the channel header dropdown
|
// # Click the channel header dropdown
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// open ChannelSettings submenu
|
||||||
|
cy.findByRole('menuitem', {name: 'Channel Settings'}).trigger('mouseover');
|
||||||
|
|
||||||
// * Channel convert to private should be visible and confirm
|
// * Channel convert to private should be visible and confirm
|
||||||
cy.get('#channelConvertToPrivate').should('be.visible').click();
|
cy.get('#channelConvertToPrivate').should('be.visible').click();
|
||||||
cy.findByTestId('convertChannelConfirm').should('be.visible').click();
|
cy.findByTestId('convertChannelConfirm').should('be.visible').click();
|
||||||
|
|
||||||
// # Click the channel header dropdown
|
// # Click the channel header dropdown
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// open ChannelSettings submenu
|
||||||
|
cy.findByRole('menuitem', {name: 'Channel Settings'}).trigger('mouseover');
|
||||||
|
|
||||||
// * Channel convert to private should no longer be visible
|
// * Channel convert to private should no longer be visible
|
||||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
cy.get('#channelConvertToPrivate').should('not.exist');
|
||||||
@@ -66,7 +72,10 @@ describe('Channels', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||||
|
|
||||||
// # Click the channel header dropdown
|
// # Click the channel header dropdown
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// open ChannelSettings submenu
|
||||||
|
cy.findByRole('menuitem', {name: 'Channel Settings'}).trigger('mouseover');
|
||||||
|
|
||||||
// * Channel convert to private should no longer be visible
|
// * Channel convert to private should no longer be visible
|
||||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
cy.get('#channelConvertToPrivate').should('not.exist');
|
||||||
@@ -91,7 +100,10 @@ describe('Channels', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||||
|
|
||||||
// # Click the channel header dropdown
|
// # Click the channel header dropdown
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// open ChannelSettings submenu
|
||||||
|
cy.findByRole('menuitem', {name: 'Channel Settings'}).trigger('mouseover');
|
||||||
|
|
||||||
// * Channel convert to private should not be visible
|
// * Channel convert to private should not be visible
|
||||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
cy.get('#channelConvertToPrivate').should('not.exist');
|
||||||
@@ -109,14 +121,20 @@ describe('Channels', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||||
|
|
||||||
// # Click the channel header dropdown
|
// # Click the channel header dropdown
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// open ChannelSettings submenu
|
||||||
|
cy.findByRole('menuitem', {name: 'Channel Settings'}).trigger('mouseover');
|
||||||
|
|
||||||
// * Channel convert to private should be visible and confirm
|
// * Channel convert to private should be visible and confirm
|
||||||
cy.get('#channelConvertToPrivate').should('be.visible').click();
|
cy.get('#channelConvertToPrivate').should('be.visible').click();
|
||||||
cy.findByTestId('convertChannelConfirm').should('be.visible').click();
|
cy.findByTestId('convertChannelConfirm').should('be.visible').click();
|
||||||
|
|
||||||
// # Click the channel header dropdown
|
// # Click the channel header dropdown
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// open ChannelSettings submenu
|
||||||
|
cy.findByRole('menuitem', {name: 'Channel Settings'}).trigger('mouseover');
|
||||||
|
|
||||||
// * Channel convert to private should no longer be visible
|
// * Channel convert to private should no longer be visible
|
||||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
cy.get('#channelConvertToPrivate').should('not.exist');
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ describe('Group Message Conversion To Private Channel', () => {
|
|||||||
|
|
||||||
// Open the GM conversion dialog
|
// Open the GM conversion dialog
|
||||||
cy.get('#channelHeaderDropdownButton').click();
|
cy.get('#channelHeaderDropdownButton').click();
|
||||||
|
cy.findByRole('menuitem', {name: 'Settings'}).trigger('mouseover');
|
||||||
cy.findByText('Convert to Private Channel').click();
|
cy.findByText('Convert to Private Channel').click();
|
||||||
|
|
||||||
// the dialog has a animation, so we're waiting for it to finish
|
// the dialog has a animation, so we're waiting for it to finish
|
||||||
@@ -102,6 +103,7 @@ describe('Group Message Conversion To Private Channel', () => {
|
|||||||
// Open the GM
|
// Open the GM
|
||||||
cy.visit(`/${testTeam1.name}/messages/${gm2.name}`);
|
cy.visit(`/${testTeam1.name}/messages/${gm2.name}`);
|
||||||
cy.get('#channelHeaderDropdownButton').click();
|
cy.get('#channelHeaderDropdownButton').click();
|
||||||
|
cy.findByRole('menuitem', {name: 'Settings'}).trigger('mouseover');
|
||||||
cy.findByText('Convert to Private Channel').click();
|
cy.findByText('Convert to Private Channel').click();
|
||||||
cy.get('.GenericModal__button.delete.disabled').wait(2000);
|
cy.get('.GenericModal__button.delete.disabled').wait(2000);
|
||||||
|
|
||||||
@@ -141,6 +143,7 @@ describe('Group Message Conversion To Private Channel', () => {
|
|||||||
// Open the GM
|
// Open the GM
|
||||||
cy.visit(`/${testTeam1.name}/messages/${gm3.name}`);
|
cy.visit(`/${testTeam1.name}/messages/${gm3.name}`);
|
||||||
cy.get('#channelHeaderDropdownButton').click();
|
cy.get('#channelHeaderDropdownButton').click();
|
||||||
|
cy.findByRole('menuitem', {name: 'Settings'}).trigger('mouseover');
|
||||||
cy.findByText('Convert to Private Channel').click();
|
cy.findByText('Convert to Private Channel').click();
|
||||||
cy.findByText('Unable to convert to a channel because group members are part of different teams').wait(2000);
|
cy.findByText('Unable to convert to a channel because group members are part of different teams').wait(2000);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ describe('Leave and Archive channel actions display as destructive', () => {
|
|||||||
cy.findByText('View Info').should('be.visible');
|
cy.findByText('View Info').should('be.visible');
|
||||||
|
|
||||||
// * Move to... menu option should be visible
|
// * Move to... menu option should be visible
|
||||||
cy.findByText('Move to...').should('be.visible').children().trigger('mouseover');
|
cy.findByText('Move to...').should('be.visible').trigger('mouseover');
|
||||||
|
|
||||||
// * Favorites Sub-menu option should be visible
|
// * Favorites Sub-menu option should be visible
|
||||||
cy.findByText('Favorites').should('be.visible');
|
cy.findByText('Favorites').should('be.visible');
|
||||||
@@ -45,17 +45,20 @@ describe('Leave and Archive channel actions display as destructive', () => {
|
|||||||
// * New Category Sub-menu option should be visible
|
// * New Category Sub-menu option should be visible
|
||||||
cy.findByText('New Category').should('be.visible');
|
cy.findByText('New Category').should('be.visible');
|
||||||
|
|
||||||
|
// * Move to... close menu option
|
||||||
|
cy.findByText('Move to...').should('be.visible').trigger('mouseout');
|
||||||
|
|
||||||
// * Notification Preferences menu option should be visible
|
// * Notification Preferences menu option should be visible
|
||||||
cy.get('#channelNotificationPreferences').should('be.visible');
|
cy.get('#channelNotificationPreferences').should('be.visible');
|
||||||
|
|
||||||
// * Mute Channel menu option should be visible
|
// * Mute Channel menu option should be visible
|
||||||
cy.get('#channelToggleMuteChannel').should('be.visible');
|
cy.get('#channelToggleMuteChannel').should('be.visible');
|
||||||
|
|
||||||
// * Invite Members menu option should be visible
|
|
||||||
cy.get('#channelInviteMembers').should('be.visible');
|
|
||||||
|
|
||||||
// * Manage Members menu option should be visible
|
// * Manage Members menu option should be visible
|
||||||
cy.get('#channelManageMembers').should('be.visible');
|
cy.get('#channelMembers').should('be.visible');
|
||||||
|
|
||||||
|
// * Channel Settings menu option should be visible
|
||||||
|
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||||
|
|
||||||
// * Edit Channel Header menu option should be visible
|
// * Edit Channel Header menu option should be visible
|
||||||
cy.get('#channelEditHeader').should('be.visible');
|
cy.get('#channelEditHeader').should('be.visible');
|
||||||
@@ -66,11 +69,14 @@ describe('Leave and Archive channel actions display as destructive', () => {
|
|||||||
// * Rename Channel menu option should be visible
|
// * Rename Channel menu option should be visible
|
||||||
cy.get('#channelRename').should('be.visible');
|
cy.get('#channelRename').should('be.visible');
|
||||||
|
|
||||||
// * Archive Channel menu option should be visible and have a background-color (destructive)
|
// * Channel Settings close menu option
|
||||||
cy.get('#channelArchiveChannel').should('be.visible').children().focus().should('have.css', 'background-color', 'rgb(210, 75, 78)');
|
cy.findByText('Channel Settings').should('be.visible').trigger('mouseout');
|
||||||
|
|
||||||
// * Leave Channel menu option should be visible and hav a background-color (destructive)
|
// * Archive Channel menu option should be visible and have a background-color (destructive)
|
||||||
cy.get('#channelLeaveChannel').should('be.visible').children().focus().should('have.css', 'background-color', 'rgb(210, 75, 78)');
|
cy.get('#channelArchiveChannel').should('be.visible').focus().should('have.css', 'background-color', 'rgb(210, 75, 78)');
|
||||||
|
|
||||||
|
// // * Leave Channel menu option should be visible and hav a background-color (destructive)
|
||||||
|
cy.get('#channelLeaveChannel').should('be.visible').focus().should('have.css', 'background-color', 'rgb(210, 75, 78)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MM-T4943_2 Leave channel actions display as destructive in the Edit Channel Menu ', () => {
|
it('MM-T4943_2 Leave channel actions display as destructive in the Edit Channel Menu ', () => {
|
||||||
|
|||||||
@@ -60,49 +60,52 @@ describe('Channel Settings', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MM-T859_1 Single User: Usernames are links, open profile popovers', () => {
|
// it('MM-T859_1 Single User: Usernames are links, open profile popovers', () => {
|
||||||
// # Create and visit new channel
|
// // # Create and visit new channel
|
||||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
// cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
// cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||||
|
|
||||||
// # Add users to channel
|
// // # Add users to channel
|
||||||
addNumberOfUsersToChannel(1, false);
|
// addNumberOfUsersToChannel(1, false);
|
||||||
|
|
||||||
cy.getLastPostId().then((id) => {
|
// cy.getLastPostId().then((id) => {
|
||||||
// * The system message should contain 'added to the channel by you'
|
// // * The system message should contain 'added to the channel by you'
|
||||||
cy.get(`#postMessageText_${id}`).should('contain', 'added to the channel by you');
|
// cy.get(`#postMessageText_${id}`).should('contain', 'added to the channel by you');
|
||||||
|
|
||||||
// # Verify username link
|
// // # Verify username link
|
||||||
verifyMentionedUserAndProfilePopover(id);
|
// verifyMentionedUserAndProfilePopover(id);
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
it('MM-T859_2 Combined Users: Usernames are links, open profile popovers', () => {
|
// it('MM-T859_2 Combined Users: Usernames are links, open profile popovers', () => {
|
||||||
// # Create and visit new channel
|
// // # Create and visit new channel
|
||||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
// cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
// cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||||
|
|
||||||
addNumberOfUsersToChannel(3, false);
|
// addNumberOfUsersToChannel(3, false);
|
||||||
|
|
||||||
cy.getLastPostId().then((id) => {
|
// cy.getLastPostId().then((id) => {
|
||||||
cy.get(`#postMessageText_${id}`).should('contain', '2 others were added to the channel by you');
|
// cy.get(`#postMessageText_${id}`).should('contain', '2 others were added to the channel by you');
|
||||||
|
|
||||||
// # Click "2 others" to expand more users
|
// // # Click "2 others" to expand more users
|
||||||
cy.get(`#post_${id}`).find('.markdown__paragraph-inline').siblings('a').first().click().then(() => {
|
// cy.get(`#post_${id}`).find('.markdown__paragraph-inline').siblings('a').first().click().then(() => {
|
||||||
// # Verify each username link
|
// // # Verify each username link
|
||||||
verifyMentionedUserAndProfilePopover(id);
|
// verifyMentionedUserAndProfilePopover(id);
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
it('MM-T856_1 Add existing users to public channel from drop-down > Add Members', () => {
|
it('MM-T856_1 Add existing users to public channel from drop-down > Add Members', () => {
|
||||||
// # Visit the add users channel
|
// # Visit the add users channel
|
||||||
cy.visit(`/${testTeam.name}/channels/${addedUsersChannel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${addedUsersChannel.name}`);
|
||||||
|
|
||||||
// # Open channel menu and click 'Add Members'
|
// # Open channel menu and click 'Add Members'
|
||||||
cy.uiOpenChannelMenu('Add Members');
|
cy.uiOpenChannelMenu('Members');
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
|
|
||||||
|
// * Assert that modal appears
|
||||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||||
|
|
||||||
// # Type into the input box to search for a user
|
// # Type into the input box to search for a user
|
||||||
@@ -160,7 +163,10 @@ describe('Channel Settings', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||||
|
|
||||||
// # Open channel menu and click 'Add Members'
|
// # Open channel menu and click 'Add Members'
|
||||||
cy.uiOpenChannelMenu('Add Members');
|
cy.uiOpenChannelMenu('Members');
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
|
|
||||||
|
// * Assert that modal appears
|
||||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||||
|
|
||||||
// # Type into the input box to search for already added user
|
// # Type into the input box to search for already added user
|
||||||
@@ -182,7 +188,8 @@ describe('Channel Settings', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||||
|
|
||||||
// # Open channel menu and click 'Add Members'
|
// # Open channel menu and click 'Add Members'
|
||||||
cy.uiOpenChannelMenu('Add Members');
|
cy.uiOpenChannelMenu('Members');
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
|
|
||||||
// * Assert that modal appears
|
// * Assert that modal appears
|
||||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||||
@@ -230,7 +237,8 @@ describe('Channel Settings', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||||
|
|
||||||
// # Open channel menu and click 'Add Members'
|
// # Open channel menu and click 'Add Members'
|
||||||
cy.uiOpenChannelMenu('Add Members');
|
cy.uiOpenChannelMenu('Members');
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
|
|
||||||
// * Assert that modal appears
|
// * Assert that modal appears
|
||||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||||
@@ -275,7 +283,8 @@ describe('Channel Settings', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||||
|
|
||||||
// # Open channel menu and click 'Add Members'
|
// # Open channel menu and click 'Add Members'
|
||||||
cy.uiOpenChannelMenu('Add Members');
|
cy.uiOpenChannelMenu('Members');
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
|
|
||||||
// * Assert that modal appears
|
// * Assert that modal appears
|
||||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||||
@@ -324,7 +333,8 @@ describe('Channel Settings', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||||
|
|
||||||
// # Open channel menu and click 'Add Members'
|
// # Open channel menu and click 'Add Members'
|
||||||
cy.uiOpenChannelMenu('Add Members');
|
cy.uiOpenChannelMenu('Members');
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
|
|
||||||
// * Assert that modal appears
|
// * Assert that modal appears
|
||||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||||
@@ -369,7 +379,8 @@ function verifyMentionedUserAndProfilePopover(postId: string) {
|
|||||||
|
|
||||||
function addNumberOfUsersToChannel(num = 1, allowExisting = false) {
|
function addNumberOfUsersToChannel(num = 1, allowExisting = false) {
|
||||||
// # Open channel menu and click 'Add Members'
|
// # Open channel menu and click 'Add Members'
|
||||||
cy.uiOpenChannelMenu('Add Members');
|
cy.uiOpenChannelMenu('Members');
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||||
|
|
||||||
// * Assert that modal appears
|
// * Assert that modal appears
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ describe('Channel routing', () => {
|
|||||||
cy.uiCreateChannel({name: 'Test__Channel'});
|
cy.uiCreateChannel({name: 'Test__Channel'});
|
||||||
|
|
||||||
// # Click on channel menu and press rename channel
|
// # Click on channel menu and press rename channel
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// * Channel Settings menu option should be visible
|
||||||
|
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||||
cy.findByText('Rename Channel').click();
|
cy.findByText('Rename Channel').click();
|
||||||
|
|
||||||
// # Assert if the rename modal present
|
// # Assert if the rename modal present
|
||||||
@@ -97,7 +100,10 @@ describe('Channel routing', () => {
|
|||||||
|
|
||||||
// # In a test channel, click the "v" to the right of the channel name in the header
|
// # In a test channel, click the "v" to the right of the channel name in the header
|
||||||
cy.findByText(`${testChannel.display_name}`).click();
|
cy.findByText(`${testChannel.display_name}`).click();
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// * Channel Settings menu option should be visible
|
||||||
|
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||||
|
|
||||||
// # Select "Rename Channel"
|
// # Select "Rename Channel"
|
||||||
cy.findByText('Rename Channel').click();
|
cy.findByText('Rename Channel').click();
|
||||||
@@ -113,7 +119,10 @@ describe('Channel routing', () => {
|
|||||||
cy.get('#channelHeaderTitle').contains(`${firstWord} ${secondWord}`);
|
cy.get('#channelHeaderTitle').contains(`${firstWord} ${secondWord}`);
|
||||||
|
|
||||||
// # In a test channel, click the "v" to the right of the channel name in the header
|
// # In a test channel, click the "v" to the right of the channel name in the header
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// * Channel Settings menu option should be visible
|
||||||
|
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||||
|
|
||||||
// # Select "Rename Channel"
|
// # Select "Rename Channel"
|
||||||
cy.findByText('Rename Channel').click();
|
cy.findByText('Rename Channel').click();
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||||
|
|
||||||
// # Open Channel Members Dialog
|
// # Open Channel Members Dialog
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
cy.findByText('Manage Members').click().wait(TIMEOUTS.FIVE_SEC);
|
cy.findByText('Manage Members').click().wait(TIMEOUTS.FIVE_SEC);
|
||||||
|
|
||||||
// * Verify the accessibility support in Manage Members Dialog
|
// * Verify the accessibility support in Manage Members Dialog
|
||||||
@@ -118,7 +118,7 @@ function verifyMainMenuModal(menuItem: string, modalName?: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function verifyChannelMenuModal(menuItem: string, modalName?: string) {
|
function verifyChannelMenuModal(menuItem: string, modalName?: string) {
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
verifyModal(menuItem, modalName);
|
verifyModal(menuItem, modalName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,8 +149,8 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
|||||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||||
|
|
||||||
// # Open Add Members Dialog
|
// # Open Add Members Dialog
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.uiOpenChannelMenu('Members');
|
||||||
cy.findByText('Add Members').click();
|
cy.uiGetButton('Add').click();
|
||||||
|
|
||||||
// * Verify the accessibility support in Add people Dialog
|
// * Verify the accessibility support in Add people Dialog
|
||||||
cy.findAllByRole('dialog').eq(0).within(() => {
|
cy.findAllByRole('dialog').eq(0).within(() => {
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ describe('Verify Guest User Identification in different screens', () => {
|
|||||||
|
|
||||||
it('MM-T1370 Verify Guest Badge in Channel Members dropdown and dialog', () => {
|
it('MM-T1370 Verify Guest Badge in Channel Members dropdown and dialog', () => {
|
||||||
// # Open Channel Members RHS
|
// # Open Channel Members RHS
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
cy.get('#channelManageMembers').click().wait(TIMEOUTS.HALF_SEC);
|
cy.get('#channelMembers').click().wait(TIMEOUTS.HALF_SEC);
|
||||||
cy.uiGetRHS().findByTestId(`memberline-${guestUser.id}`).within(($el) => {
|
cy.uiGetRHS().findByTestId(`memberline-${guestUser.id}`).within(($el) => {
|
||||||
cy.wrap($el).get('.Tag').should('be.visible').should('have.text', 'GUEST');
|
cy.wrap($el).get('.Tag').should('be.visible').should('have.text', 'GUEST');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -118,16 +118,10 @@ describe('Team Permissions', () => {
|
|||||||
cy.uiOpenChannelMenu().wait(TIMEOUTS.HALF_SEC);
|
cy.uiOpenChannelMenu().wait(TIMEOUTS.HALF_SEC);
|
||||||
|
|
||||||
// * Verify dropdown opens
|
// * Verify dropdown opens
|
||||||
cy.get('#channelHeaderDropdownMenu .Menu__content.dropdown-menu').should('be.visible');
|
cy.get('#channelHeaderDropdownMenu').should('be.visible');
|
||||||
|
|
||||||
// * Verify `Add Members` menu item is not present
|
|
||||||
cy.get('#channelAddMembers').should('not.exist');
|
|
||||||
|
|
||||||
// * Verify `Manage Members` menu item is not present
|
|
||||||
cy.get('#channelManageMembers').should('not.exist');
|
|
||||||
|
|
||||||
// * Verify `View Members` menu item is visible
|
// * Verify `View Members` menu item is visible
|
||||||
cy.get('#channelViewMembers').should('be.visible');
|
cy.get('#channelMembers').should('be.visible');
|
||||||
|
|
||||||
// # Close channel header menu
|
// # Close channel header menu
|
||||||
cy.get('body').type('{esc}');
|
cy.get('body').type('{esc}');
|
||||||
@@ -166,10 +160,11 @@ describe('Team Permissions', () => {
|
|||||||
cy.uiOpenChannelMenu().wait(TIMEOUTS.HALF_SEC);
|
cy.uiOpenChannelMenu().wait(TIMEOUTS.HALF_SEC);
|
||||||
|
|
||||||
// * Verify dropdown opens
|
// * Verify dropdown opens
|
||||||
cy.get('#channelHeaderDropdownMenu .Menu__content.dropdown-menu').should('be.visible');
|
cy.get('#channelHeaderDropdownMenu').should('be.visible');
|
||||||
|
|
||||||
// # Click on `Invite Members`
|
// # Click on `Members`
|
||||||
cy.get('#channelInviteMembers').should('be.visible').click().wait(TIMEOUTS.HALF_SEC);
|
cy.get('#channelMembers').should('be.visible').click().wait(TIMEOUTS.HALF_SEC);
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
|
|
||||||
// # Search and select otherUser
|
// # Search and select otherUser
|
||||||
cy.get('#selectItems input').typeWithForce(otherUser.username).wait(TIMEOUTS.HALF_SEC);
|
cy.get('#selectItems input').typeWithForce(otherUser.username).wait(TIMEOUTS.HALF_SEC);
|
||||||
@@ -182,10 +177,11 @@ describe('Team Permissions', () => {
|
|||||||
cy.uiOpenChannelMenu().wait(TIMEOUTS.HALF_SEC);
|
cy.uiOpenChannelMenu().wait(TIMEOUTS.HALF_SEC);
|
||||||
|
|
||||||
// * Verify dropdown opens
|
// * Verify dropdown opens
|
||||||
cy.get('#channelHeaderDropdownMenu .Menu__content.dropdown-menu').should('be.visible');
|
cy.get('#channelHeaderDropdownMenu').should('be.visible');
|
||||||
|
|
||||||
// # Click on `Manage Members`
|
// # Click on `Members`
|
||||||
cy.get('#channelManageMembers').should('be.visible').click().wait(TIMEOUTS.HALF_SEC);
|
cy.get('#channelMembers').should('be.visible').click().wait(TIMEOUTS.HALF_SEC);
|
||||||
|
cy.uiGetButton('Manage').click();
|
||||||
|
|
||||||
// # Click on `Member`
|
// # Click on `Member`
|
||||||
cy.uiGetRHS().findByTestId(`memberline-${otherUser.id}`).within(() => {
|
cy.uiGetRHS().findByTestId(`memberline-${otherUser.id}`).within(() => {
|
||||||
@@ -210,7 +206,8 @@ describe('Team Permissions', () => {
|
|||||||
cy.uiOpenChannelMenu().wait(TIMEOUTS.HALF_SEC);
|
cy.uiOpenChannelMenu().wait(TIMEOUTS.HALF_SEC);
|
||||||
|
|
||||||
// * Verify dropdown opens
|
// * Verify dropdown opens
|
||||||
cy.get('#channelHeaderDropdownMenu .Menu__content.dropdown-menu').should('be.visible');
|
cy.get('#channelHeaderDropdownMenu').should('be.visible');
|
||||||
|
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||||
|
|
||||||
// * Verify `Edit Channel Header` menu item is visible
|
// * Verify `Edit Channel Header` menu item is visible
|
||||||
cy.get('#channelEditHeader').should('be.visible');
|
cy.get('#channelEditHeader').should('be.visible');
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ function verifyRenamePrivateChannel(teamName, privateChannelName, testUsers) {
|
|||||||
cy.visit(`/${teamName}/channels/${privateChannelName}`);
|
cy.visit(`/${teamName}/channels/${privateChannelName}`);
|
||||||
|
|
||||||
// * Click the dropdown menu and verify if the rename option is visible or not
|
// * Click the dropdown menu and verify if the rename option is visible or not
|
||||||
cy.get('#channelHeaderDropdownIcon', {timeout: TIMEOUTS.TWO_MIN}).should('be.visible').click();
|
cy.get('#channelHeaderTitle', {timeout: TIMEOUTS.TWO_MIN}).should('be.visible').click();
|
||||||
cy.get('#channelRename').should(canRename ? 'be.visible' : 'not.exist');
|
cy.get('#channelRename').should(canRename ? 'be.visible' : 'not.exist');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,8 +115,13 @@ describe('Keyboard shortcut CTRL/CMD+Shift+\\ for adding reaction to last messag
|
|||||||
verifyEmojiPickerNotOpen();
|
verifyEmojiPickerNotOpen();
|
||||||
|
|
||||||
['Edit Channel Header', 'Rename Channel'].forEach((modal) => {
|
['Edit Channel Header', 'Rename Channel'].forEach((modal) => {
|
||||||
// # Open the modal and do keyboard shortcut
|
// # Click on the channel name in the channel header to open the channel menu options
|
||||||
cy.uiOpenChannelMenu(modal);
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// # Select the "Edit Channel Header" option from the dropdown
|
||||||
|
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||||
|
cy.findByText(modal).click();
|
||||||
|
|
||||||
doReactToLastMessageShortcut();
|
doReactToLastMessageShortcut();
|
||||||
|
|
||||||
// * Verify emoji picker is not open
|
// * Verify emoji picker is not open
|
||||||
@@ -135,6 +140,9 @@ describe('Keyboard shortcut CTRL/CMD+Shift+\\ for adding reaction to last messag
|
|||||||
// # Verify emoji picker is not open
|
// # Verify emoji picker is not open
|
||||||
cy.get('#emojiPicker').should('not.exist');
|
cy.get('#emojiPicker').should('not.exist');
|
||||||
|
|
||||||
|
// close channel Menu
|
||||||
|
pressEscapeKey();
|
||||||
|
|
||||||
// * Open the main menu dropdown and do keyboard shortcut
|
// * Open the main menu dropdown and do keyboard shortcut
|
||||||
cy.uiOpenTeamMenu();
|
cy.uiOpenTeamMenu();
|
||||||
doReactToLastMessageShortcut();
|
doReactToLastMessageShortcut();
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ describe('Channels', () => {
|
|||||||
cy.get('#channelHeaderTitle').should('contain', testChannel.display_name).click();
|
cy.get('#channelHeaderTitle').should('contain', testChannel.display_name).click();
|
||||||
|
|
||||||
// * Verify that the menu is opened
|
// * Verify that the menu is opened
|
||||||
cy.get('.Menu__content').should('be.visible').within(() => {
|
cy.get('#channelHeaderDropdownMenu').should('be.visible').within(() => {
|
||||||
// # Archive the channel
|
// # Archive the channel
|
||||||
cy.findByText('Archive Channel').should('be.visible').click();
|
cy.findByText('Archive Channel').should('be.visible').click();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -170,14 +170,14 @@ describe('Direct Message', () => {
|
|||||||
// # Visit the DM channel
|
// # Visit the DM channel
|
||||||
cy.visit(`/${testTeam.name}/messages/@${otherUser.username}`);
|
cy.visit(`/${testTeam.name}/messages/@${otherUser.username}`);
|
||||||
|
|
||||||
// # Open channel menu and click Mute Conversation
|
// # Open channel menu and click Mute
|
||||||
cy.uiOpenChannelMenu('Mute Conversation');
|
cy.uiOpenChannelMenu('Mute');
|
||||||
|
|
||||||
// * Assert that channel appears as muted on the LHS
|
// * Assert that channel appears as muted on the LHS
|
||||||
cy.uiGetLhsSection('DIRECT MESSAGES').find('.muted').first().should('contain', otherUser.username);
|
cy.uiGetLhsSection('DIRECT MESSAGES').find('.muted').first().should('contain', otherUser.username);
|
||||||
|
|
||||||
// # Open channel menu and click Unmute Conversation
|
// # Open channel menu and click Unmute
|
||||||
cy.uiOpenChannelMenu('Unmute Conversation');
|
cy.uiOpenChannelMenu('Unmute');
|
||||||
|
|
||||||
// * Assert that channel does not appear as muted on the LHS
|
// * Assert that channel does not appear as muted on the LHS
|
||||||
cy.uiGetLhsSection('DIRECT MESSAGES').find('.muted').should('not.exist');
|
cy.uiGetLhsSection('DIRECT MESSAGES').find('.muted').should('not.exist');
|
||||||
|
|||||||
@@ -142,7 +142,8 @@ function verifyFocusInAddChannelMemberModal() {
|
|||||||
cy.get('#channelLeaveChannel').should('be.visible');
|
cy.get('#channelLeaveChannel').should('be.visible');
|
||||||
|
|
||||||
// # Click 'Add Members'
|
// # Click 'Add Members'
|
||||||
cy.get('#channelInviteMembers').click();
|
cy.get('#channelMembers').click();
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
|
|
||||||
// * Assert that modal appears
|
// * Assert that modal appears
|
||||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||||
|
|||||||
@@ -30,16 +30,16 @@ describe('Header', () => {
|
|||||||
|
|
||||||
it('MM-T88 An ellipsis indicates the channel header is too long - public or private channel Quote icon displays at beginning of channel header', () => {
|
it('MM-T88 An ellipsis indicates the channel header is too long - public or private channel Quote icon displays at beginning of channel header', () => {
|
||||||
// * Verify with short channel header
|
// * Verify with short channel header
|
||||||
updateAndVerifyChannelHeader('>', 'newheader');
|
updateAndVerifyChannelHeader(false, '>', 'newheader');
|
||||||
|
|
||||||
// * Verify with long channel header
|
// * Verify with long channel header
|
||||||
updateAndVerifyChannelHeader('>', 'newheader'.repeat(20));
|
updateAndVerifyChannelHeader(false, '>', 'newheader'.repeat(20));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MM-T881_1 - Header: Markdown quote', () => {
|
it('MM-T881_1 - Header: Markdown quote', () => {
|
||||||
// # Update channel header text
|
// # Update channel header text
|
||||||
const header = 'This is a quote in the header';
|
const header = 'This is a quote in the header';
|
||||||
updateAndVerifyChannelHeader('>', header);
|
updateAndVerifyChannelHeader(false, '>', header);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MM-T89 An ellipsis indicates the channel header is too long - DM', () => {
|
it('MM-T89 An ellipsis indicates the channel header is too long - DM', () => {
|
||||||
@@ -56,7 +56,7 @@ describe('Header', () => {
|
|||||||
// # Update DM channel header
|
// # Update DM channel header
|
||||||
const header = `quote ${'newheader'.repeat(15)}`;
|
const header = `quote ${'newheader'.repeat(15)}`;
|
||||||
|
|
||||||
updateAndVerifyChannelHeader('>', header);
|
updateAndVerifyChannelHeader(true, '>', header);
|
||||||
|
|
||||||
// # Hover on channel header
|
// # Hover on channel header
|
||||||
cy.get('#channelHeaderDescription .header-description__text').trigger('mouseenter');
|
cy.get('#channelHeaderDescription .header-description__text').trigger('mouseenter');
|
||||||
@@ -70,9 +70,13 @@ describe('Header', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateAndVerifyChannelHeader(prefix, header) {
|
function updateAndVerifyChannelHeader(isDM, prefix, header) {
|
||||||
// # Update channel header
|
// # Update channel header
|
||||||
cy.updateChannelHeader(prefix + header);
|
if (isDM) {
|
||||||
|
cy.updateDMGMChannelHeader(prefix + header);
|
||||||
|
} else {
|
||||||
|
cy.updateChannelHeader(prefix + header);
|
||||||
|
}
|
||||||
|
|
||||||
// * Should render blockquote if it starts with ">"
|
// * Should render blockquote if it starts with ">"
|
||||||
if (prefix === '>') {
|
if (prefix === '>') {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ describe('Multi-user group messages', () => {
|
|||||||
cy.get('#saveItems').click();
|
cy.get('#saveItems').click();
|
||||||
|
|
||||||
// # Click channel dropdown icon to open menu
|
// # Click channel dropdown icon to open menu
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
// # Click 'close' item
|
// # Click 'close' item
|
||||||
cy.findByText('Close Group Message').should('be.visible').click();
|
cy.findByText('Close Group Message').should('be.visible').click();
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ describe('Multi-user group messages', () => {
|
|||||||
cy.postMessage('messages');
|
cy.postMessage('messages');
|
||||||
|
|
||||||
// # Open channel menu and click Add Members
|
// # Open channel menu and click Add Members
|
||||||
cy.uiOpenChannelMenu('Add Members');
|
cy.uiOpenChannelMenu('Members');
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
|
|
||||||
// * Verify message says: "This will start a new conversation. If you're adding a lot of people, consider creating a private channel instead."
|
// * Verify message says: "This will start a new conversation. If you're adding a lot of people, consider creating a private channel instead."
|
||||||
cy.get('#moreDmModal').should('be.visible');
|
cy.get('#moreDmModal').should('be.visible');
|
||||||
@@ -139,7 +140,6 @@ describe('Multi-user group messages', () => {
|
|||||||
cy.contains('p.channel-intro__text', 'This is the start of your group message history with');
|
cy.contains('p.channel-intro__text', 'This is the start of your group message history with');
|
||||||
|
|
||||||
// * New user is added to the GM
|
// * New user is added to the GM
|
||||||
cy.get('.member-rhs__trigger').click();
|
|
||||||
cy.uiGetRHS().contains(testUser.username).should('be.visible');
|
cy.uiGetRHS().contains(testUser.username).should('be.visible');
|
||||||
|
|
||||||
// * Other users are still there
|
// * Other users are still there
|
||||||
|
|||||||
@@ -136,8 +136,10 @@ describe('Multi-user group header', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function editHeader(header) {
|
function editHeader(header) {
|
||||||
// # Click edit conversation header
|
// # Click edit header
|
||||||
cy.uiOpenChannelMenu('Edit Conversation Header');
|
cy.uiOpenChannelMenu('');
|
||||||
|
cy.findByRole('menuitem', {name: 'Settings'}).trigger('mouseover');
|
||||||
|
cy.findByText('Edit Header').click();
|
||||||
|
|
||||||
// * Verify the modal open to add header
|
// * Verify the modal open to add header
|
||||||
cy.get('#editChannelHeaderModalLabel').should('be.visible').wait(TIMEOUTS.ONE_SEC);
|
cy.get('#editChannelHeaderModalLabel').should('be.visible').wait(TIMEOUTS.ONE_SEC);
|
||||||
|
|||||||
@@ -62,10 +62,11 @@ describe('Multi-user group messages', () => {
|
|||||||
and('have.css', 'text-overflow', 'ellipsis');
|
and('have.css', 'text-overflow', 'ellipsis');
|
||||||
|
|
||||||
// # From the group message's window, click on the user list's dropdown
|
// # From the group message's window, click on the user list's dropdown
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
// # From the dropdown menu, click on "Add members"
|
// # From the dropdown menu, click on "Add members"
|
||||||
cy.get('#channelAddMembers').click();
|
cy.get('#channelMembers').click();
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
|
|
||||||
// # Try to add one more user from the group message's list
|
// # Try to add one more user from the group message's list
|
||||||
addUsersToGMViaModal(1);
|
addUsersToGMViaModal(1);
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ describe('System Console > User Management > Deactivation', () => {
|
|||||||
cy.sendDirectMessageToUser(other, MESSAGES.SMALL);
|
cy.sendDirectMessageToUser(other, MESSAGES.SMALL);
|
||||||
|
|
||||||
// # Close DM
|
// # Close DM
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
cy.findByText('Close Direct Message').click();
|
cy.findByText('Close Direct Message').click();
|
||||||
|
|
||||||
// # Deactivate the user
|
// # Deactivate the user
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ describe('channels > general actions', {testIsolation: true}, () => {
|
|||||||
|
|
||||||
// # Open Channel Header and the Channel Actions modal
|
// # Open Channel Header and the Channel Actions modal
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// * More actions menu option should be visible
|
||||||
|
cy.findByText('More actions').should('be.visible').trigger('mouseover');
|
||||||
cy.findByText('Channel Actions').click();
|
cy.findByText('Channel Actions').click();
|
||||||
|
|
||||||
// # Enable the categorization action and set the name
|
// # Enable the categorization action and set the name
|
||||||
@@ -87,6 +90,9 @@ describe('channels > general actions', {testIsolation: true}, () => {
|
|||||||
|
|
||||||
// # Open Channel Header and the Channel Actions modal
|
// # Open Channel Header and the Channel Actions modal
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// * More actions menu option should be visible
|
||||||
|
cy.findByText('More actions').should('be.visible').trigger('mouseover');
|
||||||
cy.findByText('Channel Actions').click();
|
cy.findByText('Channel Actions').click();
|
||||||
|
|
||||||
// # Toggle on and set the welcome message
|
// # Toggle on and set the welcome message
|
||||||
@@ -136,6 +142,9 @@ describe('channels > general actions', {testIsolation: true}, () => {
|
|||||||
|
|
||||||
// # Open Channel Header and the Channel Actions modal
|
// # Open Channel Header and the Channel Actions modal
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// * More actions menu option should be visible
|
||||||
|
cy.findByText('More actions').should('be.visible').trigger('mouseover');
|
||||||
cy.findByText('Channel Actions').click();
|
cy.findByText('Channel Actions').click();
|
||||||
|
|
||||||
// # Set a keyword, enable the playbook trigger,
|
// # Set a keyword, enable the playbook trigger,
|
||||||
@@ -196,6 +205,9 @@ describe('channels > general actions', {testIsolation: true}, () => {
|
|||||||
|
|
||||||
// # Open Channel Header and the Channel Actions modal
|
// # Open Channel Header and the Channel Actions modal
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// * More actions menu option should be visible
|
||||||
|
cy.findByText('More actions').should('be.visible').trigger('mouseover');
|
||||||
cy.findByText('Channel Actions').click();
|
cy.findByText('Channel Actions').click();
|
||||||
|
|
||||||
// # Set a keyword, enable the playbook trigger,
|
// # Set a keyword, enable the playbook trigger,
|
||||||
@@ -272,6 +284,9 @@ describe('channels > general actions', {testIsolation: true}, () => {
|
|||||||
|
|
||||||
// # Open Channel Header and the Channel Actions modal
|
// # Open Channel Header and the Channel Actions modal
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// * More actions menu option should be visible
|
||||||
|
cy.findByText('More actions').should('be.visible').trigger('mouseover');
|
||||||
cy.findByText('Channel Actions').click();
|
cy.findByText('Channel Actions').click();
|
||||||
|
|
||||||
// # Set a keyword, enable the playbook trigger,
|
// # Set a keyword, enable the playbook trigger,
|
||||||
@@ -311,6 +326,9 @@ describe('channels > general actions', {testIsolation: true}, () => {
|
|||||||
|
|
||||||
// # Open Channel Header and the Channel Actions modal
|
// # Open Channel Header and the Channel Actions modal
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// * More actions menu option should be visible
|
||||||
|
cy.findByText('More actions').should('be.visible').trigger('mouseover');
|
||||||
cy.findByText('Channel Actions').click();
|
cy.findByText('Channel Actions').click();
|
||||||
|
|
||||||
// * Verify the toggles are disabled
|
// * Verify the toggles are disabled
|
||||||
@@ -333,6 +351,9 @@ describe('channels > general actions', {testIsolation: true}, () => {
|
|||||||
|
|
||||||
// # Open Channel Header and the Channel Actions modal
|
// # Open Channel Header and the Channel Actions modal
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// * More actions menu option should be visible
|
||||||
|
cy.findByText('More actions').should('be.visible').trigger('mouseover');
|
||||||
cy.findByText('Channel Actions').click();
|
cy.findByText('Channel Actions').click();
|
||||||
|
|
||||||
// # Enable the categorization action and set the name
|
// # Enable the categorization action and set the name
|
||||||
@@ -353,6 +374,9 @@ describe('channels > general actions', {testIsolation: true}, () => {
|
|||||||
|
|
||||||
// # Open Channel Header and the Channel Actions modal
|
// # Open Channel Header and the Channel Actions modal
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
|
// * More actions menu option should be visible
|
||||||
|
cy.findByText('More actions').should('be.visible').trigger('mouseover');
|
||||||
cy.findByText('Channel Actions').click();
|
cy.findByText('Channel Actions').click();
|
||||||
|
|
||||||
// * Verify that the categorization action is disabled
|
// * Verify that the categorization action is disabled
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ describe('channels > run', {testIsolation: true}, () => {
|
|||||||
|
|
||||||
// # Open the channel header
|
// # Open the channel header
|
||||||
cy.get('#channelHeaderTitle').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||||
|
|
||||||
// * Verify the ability to edit the channel header exists
|
// * Verify the ability to edit the channel header exists
|
||||||
cy.get('#channelEditHeader').should('exist');
|
cy.get('#channelEditHeader').should('exist');
|
||||||
|
|||||||
@@ -43,8 +43,9 @@ Cypress.Commands.add('uiCreateChannel', ({
|
|||||||
|
|
||||||
Cypress.Commands.add('uiAddUsersToCurrentChannel', (usernameList) => {
|
Cypress.Commands.add('uiAddUsersToCurrentChannel', (usernameList) => {
|
||||||
if (usernameList.length) {
|
if (usernameList.length) {
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
cy.get('#channelAddMembers').click();
|
cy.get('#channelMembers').click();
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||||
usernameList.forEach((username) => {
|
usernameList.forEach((username) => {
|
||||||
cy.get('#selectItems input').typeWithForce(`@${username}{enter}`);
|
cy.get('#selectItems input').typeWithForce(`@${username}{enter}`);
|
||||||
@@ -56,8 +57,9 @@ Cypress.Commands.add('uiAddUsersToCurrentChannel', (usernameList) => {
|
|||||||
|
|
||||||
Cypress.Commands.add('uiInviteUsersToCurrentChannel', (usernameList) => {
|
Cypress.Commands.add('uiInviteUsersToCurrentChannel', (usernameList) => {
|
||||||
if (usernameList.length) {
|
if (usernameList.length) {
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
cy.get('#channelInviteMembers').click();
|
cy.get('#channelMembers').click();
|
||||||
|
cy.uiGetButton('Add').click();
|
||||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||||
usernameList.forEach((username) => {
|
usernameList.forEach((username) => {
|
||||||
cy.get('#selectItems input').typeWithForce(`@${username}{enter}`);
|
cy.get('#selectItems input').typeWithForce(`@${username}{enter}`);
|
||||||
@@ -68,19 +70,19 @@ Cypress.Commands.add('uiInviteUsersToCurrentChannel', (usernameList) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('uiArchiveChannel', () => {
|
Cypress.Commands.add('uiArchiveChannel', () => {
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
cy.get('#channelArchiveChannel').click();
|
cy.get('#channelArchiveChannel').click();
|
||||||
return cy.get('#deleteChannelModalDeleteButton').click();
|
return cy.get('#deleteChannelModalDeleteButton').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('uiUnarchiveChannel', () => {
|
Cypress.Commands.add('uiUnarchiveChannel', () => {
|
||||||
cy.get('#channelHeaderDropdownIcon').should('be.visible').click();
|
cy.get('#channelHeaderTitle').should('be.visible').click();
|
||||||
cy.get('#channelUnarchiveChannel').should('be.visible').click();
|
cy.get('#channelUnarchiveChannel').should('be.visible').click();
|
||||||
return cy.get('#unarchiveChannelModalDeleteButton').should('be.visible').click();
|
return cy.get('#unarchiveChannelModalDeleteButton').should('be.visible').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('uiLeaveChannel', (isPrivate = false) => {
|
Cypress.Commands.add('uiLeaveChannel', (isPrivate = false) => {
|
||||||
cy.get('#channelHeaderDropdownIcon').click();
|
cy.get('#channelHeaderTitle').click();
|
||||||
|
|
||||||
if (isPrivate) {
|
if (isPrivate) {
|
||||||
cy.get('#channelLeaveChannel').click();
|
cy.get('#channelLeaveChannel').click();
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ Cypress.Commands.add('uiGetChannelFileButton', () => {
|
|||||||
Cypress.Commands.add('uiGetChannelMenu', (options = {exist: true}) => {
|
Cypress.Commands.add('uiGetChannelMenu', (options = {exist: true}) => {
|
||||||
if (options.exist) {
|
if (options.exist) {
|
||||||
return cy.get('#channelHeaderDropdownMenu').
|
return cy.get('#channelHeaderDropdownMenu').
|
||||||
find('.dropdown-menu').
|
|
||||||
should('be.visible');
|
should('be.visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -462,13 +462,15 @@ function getCurrentChannelId(): ChainableT<string> {
|
|||||||
Cypress.Commands.add('getCurrentChannelId', getCurrentChannelId);
|
Cypress.Commands.add('getCurrentChannelId', getCurrentChannelId);
|
||||||
|
|
||||||
function updateChannelHeader(text: string) {
|
function updateChannelHeader(text: string) {
|
||||||
cy.get('#channelHeaderDropdownIcon').
|
cy.get('#channelHeaderTitle').
|
||||||
should('be.visible').
|
should('be.visible').
|
||||||
click();
|
click();
|
||||||
cy.get('.Menu__content').
|
cy.get('#channelHeaderDropdownMenu').
|
||||||
should('be.visible').
|
should('be.visible');
|
||||||
find('#channelEditHeader').
|
|
||||||
click();
|
// * Channel Settings menu option should be visible
|
||||||
|
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||||
|
cy.findByText('Edit Channel Header').click();
|
||||||
cy.get('#edit_textbox').
|
cy.get('#edit_textbox').
|
||||||
clear().
|
clear().
|
||||||
type(text).
|
type(text).
|
||||||
@@ -478,6 +480,24 @@ function updateChannelHeader(text: string) {
|
|||||||
|
|
||||||
Cypress.Commands.add('updateChannelHeader', updateChannelHeader);
|
Cypress.Commands.add('updateChannelHeader', updateChannelHeader);
|
||||||
|
|
||||||
|
function updateDMGMChannelHeader(text: string) {
|
||||||
|
cy.get('#channelHeaderTitle').
|
||||||
|
should('be.visible').
|
||||||
|
click();
|
||||||
|
cy.get('#channelHeaderDropdownMenu').
|
||||||
|
should('be.visible');
|
||||||
|
|
||||||
|
// * Channel Settings menu option should be visible
|
||||||
|
cy.findByText('Edit Header').click();
|
||||||
|
cy.get('#edit_textbox').
|
||||||
|
clear().
|
||||||
|
type(text).
|
||||||
|
type('{enter}').
|
||||||
|
wait(TIMEOUTS.HALF_SEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.add('updateDMGMChannelHeader', updateDMGMChannelHeader);
|
||||||
|
|
||||||
function checkRunLDAPSync(): ChainableT<any> {
|
function checkRunLDAPSync(): ChainableT<any> {
|
||||||
return cy.apiGetLDAPSync().then((response) => {
|
return cy.apiGetLDAPSync().then((response) => {
|
||||||
const jobs = response.body;
|
const jobs = response.body;
|
||||||
@@ -787,6 +807,12 @@ declare global {
|
|||||||
*/
|
*/
|
||||||
updateChannelHeader(text: string): ChainableT<void>;
|
updateChannelHeader(text: string): ChainableT<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update DM or GM channel header
|
||||||
|
* @param {String} text - Text to set the header to
|
||||||
|
*/
|
||||||
|
updateDMGMChannelHeader(text: string): ChainableT<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate to system console-PluginManagement from profile settings
|
* Navigate to system console-PluginManagement from profile settings
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
// Purpose of this file to exists is only required until channel header dropdown is migrated to new menus
|
|
||||||
import type {ComponentProps} from 'react';
|
|
||||||
import React, {memo} from 'react';
|
|
||||||
import {useIntl} from 'react-intl';
|
|
||||||
import {useSelector} from 'react-redux';
|
|
||||||
|
|
||||||
import {
|
|
||||||
LinkVariantIcon,
|
|
||||||
PaperclipIcon,
|
|
||||||
} from '@mattermost/compass-icons/components';
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
|
||||||
|
|
||||||
import {getChannelBookmarks} from 'mattermost-redux/selectors/entities/channel_bookmarks';
|
|
||||||
|
|
||||||
import {useBookmarkAddActions} from 'components/channel_bookmarks/channel_bookmarks_menu';
|
|
||||||
import {MAX_BOOKMARKS_PER_CHANNEL, useCanUploadFiles, useChannelBookmarkPermission} from 'components/channel_bookmarks/utils';
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
channel: Channel;
|
|
||||||
inHeaderDropdown?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ChannelBookmarksSubmenu = (props: Props) => {
|
|
||||||
const {formatMessage} = useIntl();
|
|
||||||
|
|
||||||
const {handleCreateLink, handleCreateFile} = useBookmarkAddActions(props.channel.id);
|
|
||||||
const canAdd = useChannelBookmarkPermission(props.channel.id, 'add');
|
|
||||||
const canUploadFiles = useCanUploadFiles();
|
|
||||||
const limitReached = useSelector((state: GlobalState) => {
|
|
||||||
const bookmarks = getChannelBookmarks(state, props.channel.id);
|
|
||||||
return bookmarks && Object.keys(bookmarks).length >= MAX_BOOKMARKS_PER_CHANNEL;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!canAdd || limitReached) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const items: ComponentProps<typeof Menu.ItemSubMenu>['subMenu'] = [
|
|
||||||
{
|
|
||||||
id: 'channelBookmarksAddLink',
|
|
||||||
icon: <LinkVariantIcon size={16}/>,
|
|
||||||
direction: 'right',
|
|
||||||
text: formatMessage({id: 'channel_bookmarks.addLink', defaultMessage: 'Add a link'}),
|
|
||||||
action: handleCreateLink,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (canUploadFiles) {
|
|
||||||
items.push({
|
|
||||||
id: 'channelBookmarksAttachFile',
|
|
||||||
icon: <PaperclipIcon size={16}/>,
|
|
||||||
direction: 'right',
|
|
||||||
text: formatMessage({id: 'channel_bookmarks.attachFile', defaultMessage: 'Attach a file'}),
|
|
||||||
action: handleCreateFile,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu.ItemSubMenu
|
|
||||||
id={`channel-menu-${props.channel.id}-bookmarks`}
|
|
||||||
subMenu={items}
|
|
||||||
text={formatMessage({id: 'sidebar_left.sidebar_channel_menu.bookmarks', defaultMessage: 'Bookmarks Bar'})}
|
|
||||||
direction={'right'}
|
|
||||||
styleSelectableItem={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(ChannelBookmarksSubmenu);
|
|
||||||
@@ -1,27 +1,19 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import type {ReactNode} from 'react';
|
import type {ReactNode} from 'react';
|
||||||
import React, {memo, useState, useRef, useEffect} from 'react';
|
import React, {memo} from 'react';
|
||||||
import {useIntl} from 'react-intl';
|
|
||||||
import {useSelector} from 'react-redux';
|
import {useSelector} from 'react-redux';
|
||||||
|
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
import {Client4} from 'mattermost-redux/client';
|
import {Client4} from 'mattermost-redux/client';
|
||||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
|
||||||
|
|
||||||
import {getIsRhsOpen} from 'selectors/rhs';
|
|
||||||
|
|
||||||
import {ChannelHeaderDropdown} from 'components/channel_header_dropdown';
|
|
||||||
import ProfilePicture from 'components/profile_picture';
|
import ProfilePicture from 'components/profile_picture';
|
||||||
import SharedChannelIndicator from 'components/shared_channel_indicator';
|
import SharedChannelIndicator from 'components/shared_channel_indicator';
|
||||||
import ArchiveIcon from 'components/widgets/icons/archive_icon';
|
import ArchiveIcon from 'components/widgets/icons/archive_icon';
|
||||||
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
|
|
||||||
import BotTag from 'components/widgets/tag/bot_tag';
|
import BotTag from 'components/widgets/tag/bot_tag';
|
||||||
import WithTooltip from 'components/with_tooltip';
|
|
||||||
|
|
||||||
import {Constants} from 'utils/constants';
|
import {Constants} from 'utils/constants';
|
||||||
|
|
||||||
@@ -29,6 +21,8 @@ import ChannelHeaderTitleDirect from './channel_header_title_direct';
|
|||||||
import ChannelHeaderTitleFavorite from './channel_header_title_favorite';
|
import ChannelHeaderTitleFavorite from './channel_header_title_favorite';
|
||||||
import ChannelHeaderTitleGroup from './channel_header_title_group';
|
import ChannelHeaderTitleGroup from './channel_header_title_group';
|
||||||
|
|
||||||
|
import ChannelHeaderMenu from '../channel_header_menu/channel_header_menu';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
dmUser?: UserProfile;
|
dmUser?: UserProfile;
|
||||||
gmMembers?: UserProfile[];
|
gmMembers?: UserProfile[];
|
||||||
@@ -38,45 +32,22 @@ const ChannelHeaderTitle = ({
|
|||||||
dmUser,
|
dmUser,
|
||||||
gmMembers,
|
gmMembers,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [titleMenuOpen, setTitleMenuOpen] = useState(false);
|
|
||||||
const [showTooltip, setShowTooltip] = useState(false);
|
|
||||||
const intl = useIntl();
|
|
||||||
const channel = useSelector(getCurrentChannel);
|
const channel = useSelector(getCurrentChannel);
|
||||||
const currentUser = useSelector(getCurrentUser);
|
|
||||||
|
|
||||||
const headerItemRef = useRef<HTMLElement | null>(null);
|
|
||||||
const isRHSOpen = useSelector(getIsRhsOpen);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
enableToolTipIfNeeded();
|
|
||||||
|
|
||||||
// Re-check on window resize
|
|
||||||
const handleResize = () => enableToolTipIfNeeded();
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
|
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
|
||||||
}, [channel, gmMembers, dmUser, isRHSOpen]);
|
|
||||||
|
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const enableToolTipIfNeeded = () => {
|
|
||||||
const element = headerItemRef.current;
|
|
||||||
const isTooltip = element && element.offsetWidth < element.scrollWidth;
|
|
||||||
setShowTooltip(isTooltip as boolean);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isDirect = (channel.type === Constants.DM_CHANNEL);
|
const isDirect = (channel.type === Constants.DM_CHANNEL);
|
||||||
const isGroup = (channel.type === Constants.GM_CHANNEL);
|
const isGroup = (channel.type === Constants.GM_CHANNEL);
|
||||||
const channelIsArchived = channel.delete_at !== 0;
|
const channelIsArchived = channel.delete_at !== 0;
|
||||||
|
|
||||||
let archivedIcon: React.ReactNode = null;
|
let archivedIcon;
|
||||||
if (channelIsArchived) {
|
if (channelIsArchived) {
|
||||||
archivedIcon = <ArchiveIcon className='icon icon__archive icon channel-header-archived-icon svg-text-color'/>;
|
archivedIcon = <ArchiveIcon className='icon icon__archive icon channel-header-archived-icon svg-text-color'/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sharedIcon = null;
|
let sharedIcon;
|
||||||
if (channel.shared) {
|
if (channel.shared) {
|
||||||
sharedIcon = (
|
sharedIcon = (
|
||||||
<SharedChannelIndicator
|
<SharedChannelIndicator
|
||||||
@@ -118,20 +89,6 @@ const ChannelHeaderTitle = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const personalChannelHeaderAriaLabel = intl.formatMessage({
|
|
||||||
id: 'channel_header.directchannel',
|
|
||||||
defaultMessage: '{displayName} (you) Channel Menu',
|
|
||||||
}, {
|
|
||||||
displayName: channel.display_name,
|
|
||||||
});
|
|
||||||
|
|
||||||
const othersChannelHeaderAriaLabel = intl.formatMessage({
|
|
||||||
id: 'channel_header.otherchannel',
|
|
||||||
defaultMessage: '{displayName} Channel Menu',
|
|
||||||
}, {
|
|
||||||
displayName: channel.display_name,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='channel-header__top'>
|
<div className='channel-header__top'>
|
||||||
<ChannelHeaderTitleFavorite/>
|
<ChannelHeaderTitleFavorite/>
|
||||||
@@ -142,60 +99,12 @@ const ChannelHeaderTitle = ({
|
|||||||
status={channel.status}
|
status={channel.status}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<MenuWrapper onToggle={setTitleMenuOpen}>
|
<ChannelHeaderMenu
|
||||||
<div
|
dmUser={dmUser}
|
||||||
id='channelHeaderDropdownButton'
|
gmMembers={gmMembers}
|
||||||
>
|
sharedIcon={sharedIcon}
|
||||||
<button
|
archivedIcon={archivedIcon}
|
||||||
id='channelHeaderMenuButton'
|
/>
|
||||||
className={classNames('channel-header__trigger style--none', {active: titleMenuOpen})}
|
|
||||||
aria-label={
|
|
||||||
(isDirect && currentUser.id === dmUser?.id) ? personalChannelHeaderAriaLabel.toLowerCase() : othersChannelHeaderAriaLabel.toLowerCase()
|
|
||||||
}
|
|
||||||
aria-expanded={titleMenuOpen}
|
|
||||||
aria-controls='channelHeaderDropdownMenu'
|
|
||||||
>
|
|
||||||
{showTooltip ? (
|
|
||||||
<WithTooltip
|
|
||||||
title={channelTitle as string}
|
|
||||||
>
|
|
||||||
<strong
|
|
||||||
id='channelHeaderTitle'
|
|
||||||
className='heading'
|
|
||||||
ref={headerItemRef}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{archivedIcon}
|
|
||||||
{channelTitle}
|
|
||||||
{sharedIcon}
|
|
||||||
</span>
|
|
||||||
</strong>
|
|
||||||
</WithTooltip>
|
|
||||||
) : (
|
|
||||||
<strong
|
|
||||||
id='channelHeaderTitle'
|
|
||||||
className='heading'
|
|
||||||
ref={headerItemRef}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{archivedIcon}
|
|
||||||
{channelTitle}
|
|
||||||
{sharedIcon}
|
|
||||||
</span>
|
|
||||||
</strong>
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
id='channelHeaderDropdownIcon'
|
|
||||||
className='icon icon-chevron-down header-dropdown-chevron-icon'
|
|
||||||
aria-hidden='true'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<ChannelHeaderDropdown
|
|
||||||
ariaLabel={
|
|
||||||
(isDirect && currentUser.id === dmUser?.id) ? personalChannelHeaderAriaLabel.toLowerCase() : othersChannelHeaderAriaLabel.toLowerCase()}
|
|
||||||
/>
|
|
||||||
</MenuWrapper>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -1,43 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {shallow} from 'enzyme';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import {TestHelper} from 'utils/test_helper';
|
|
||||||
|
|
||||||
import ChannelHeaderDropdown from './channel_header_dropdown_items';
|
|
||||||
import type {Props} from './channel_header_dropdown_items';
|
|
||||||
|
|
||||||
describe('components/ChannelHeaderDropdown', () => {
|
|
||||||
const defaultProps = {
|
|
||||||
user: TestHelper.getUserMock({id: 'test-user-id'}),
|
|
||||||
channel: TestHelper.getChannelMock({id: 'test-channel-id'}),
|
|
||||||
isDefault: false,
|
|
||||||
isFavorite: false,
|
|
||||||
isReadonly: false,
|
|
||||||
isMuted: false,
|
|
||||||
isArchived: false,
|
|
||||||
isMobile: false,
|
|
||||||
penultimateViewedChannelName: 'test-channel',
|
|
||||||
pluginMenuItems: [],
|
|
||||||
isLicensedForLDAPGroups: false,
|
|
||||||
isChannelBookmarksEnabled: false,
|
|
||||||
};
|
|
||||||
test('should match snapshot with no plugin items', () => {
|
|
||||||
const wrapper = shallow(<ChannelHeaderDropdown {...defaultProps}/>);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should match snapshot with plugins', () => {
|
|
||||||
const props: Props = {
|
|
||||||
...defaultProps,
|
|
||||||
pluginMenuItems: [
|
|
||||||
{id: 'plugin-1', pluginId: 'playbooks', action: jest.fn(), text: 'plugin-1-text', shouldRender: () => true},
|
|
||||||
{id: 'plugin-2', pluginId: 'playbooks', action: jest.fn(), text: 'plugin-2-text', shouldRender: () => true},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const wrapper = shallow(<ChannelHeaderDropdown {...props}/>);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React, {memo} from 'react';
|
|
||||||
|
|
||||||
import {ChannelHeaderDropdownItems} from 'components/channel_header_dropdown';
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
const ChannelHeaderDropdown = ({ariaLabel}: {
|
|
||||||
ariaLabel: string;
|
|
||||||
}) =>
|
|
||||||
(
|
|
||||||
<Menu
|
|
||||||
id='channelHeaderDropdownMenu'
|
|
||||||
ariaLabel={ariaLabel}
|
|
||||||
>
|
|
||||||
<ChannelHeaderDropdownItems isMobile={false}/>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default memo(ChannelHeaderDropdown);
|
|
||||||
@@ -1,355 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
|
||||||
|
|
||||||
import {Permissions} from 'mattermost-redux/constants';
|
|
||||||
import {isGuest} from 'mattermost-redux/utils/user_utils';
|
|
||||||
|
|
||||||
import AddGroupsToChannelModal from 'components/add_groups_to_channel_modal';
|
|
||||||
import ChannelBookmarksSubmenu from 'components/channel_bookmarks_sub_menu';
|
|
||||||
import ChannelGroupsManageModal from 'components/channel_groups_manage_modal';
|
|
||||||
import ChannelInviteModal from 'components/channel_invite_modal';
|
|
||||||
import ChannelMoveToSubMenuOld from 'components/channel_move_to_sub_menu_old';
|
|
||||||
import ChannelNotificationsModal from 'components/channel_notifications_modal';
|
|
||||||
import ConvertChannelModal from 'components/convert_channel_modal';
|
|
||||||
import ConvertGmToChannelModal from 'components/convert_gm_to_channel_modal';
|
|
||||||
import DeleteChannelModal from 'components/delete_channel_modal';
|
|
||||||
import EditChannelHeaderModal from 'components/edit_channel_header_modal';
|
|
||||||
import EditChannelPurposeModal from 'components/edit_channel_purpose_modal';
|
|
||||||
import MoreDirectChannels from 'components/more_direct_channels';
|
|
||||||
import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate';
|
|
||||||
import RenameChannelModal from 'components/rename_channel_modal';
|
|
||||||
import UnarchiveChannelModal from 'components/unarchive_channel_modal';
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
import MobileChannelHeaderPlug from 'plugins/mobile_channel_header_plug';
|
|
||||||
import {Constants, ModalIdentifiers} from 'utils/constants';
|
|
||||||
import {localizeMessage} from 'utils/utils';
|
|
||||||
|
|
||||||
import type {ChannelHeaderAction} from 'types/store/plugins';
|
|
||||||
|
|
||||||
import MenuItemCloseChannel from './menu_items/close_channel';
|
|
||||||
import MenuItemCloseMessage from './menu_items/close_message';
|
|
||||||
import MenuItemLeaveChannel from './menu_items/leave_channel';
|
|
||||||
import MenuItemOpenMembersRHS from './menu_items/open_members_rhs';
|
|
||||||
import MenuItemToggleFavoriteChannel from './menu_items/toggle_favorite_channel';
|
|
||||||
import MenuItemToggleInfo from './menu_items/toggle_info';
|
|
||||||
import MenuItemToggleMuteChannel from './menu_items/toggle_mute_channel';
|
|
||||||
import MenuItemViewPinnedPosts from './menu_items/view_pinned_posts';
|
|
||||||
|
|
||||||
export type Props = {
|
|
||||||
user: UserProfile;
|
|
||||||
channel?: Channel;
|
|
||||||
isDefault: boolean;
|
|
||||||
isFavorite: boolean;
|
|
||||||
isReadonly: boolean;
|
|
||||||
isMuted: boolean;
|
|
||||||
isArchived: boolean;
|
|
||||||
isMobile: boolean;
|
|
||||||
penultimateViewedChannelName: string;
|
|
||||||
pluginMenuItems: ChannelHeaderAction[];
|
|
||||||
isLicensedForLDAPGroups: boolean;
|
|
||||||
isChannelBookmarksEnabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ChannelHeaderDropdown extends React.PureComponent<Props> {
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
user,
|
|
||||||
channel,
|
|
||||||
isDefault,
|
|
||||||
isFavorite,
|
|
||||||
isMuted,
|
|
||||||
isReadonly,
|
|
||||||
isArchived,
|
|
||||||
isMobile,
|
|
||||||
penultimateViewedChannelName,
|
|
||||||
isLicensedForLDAPGroups,
|
|
||||||
isChannelBookmarksEnabled,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (!channel) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPrivate = channel.type === Constants.PRIVATE_CHANNEL;
|
|
||||||
const isGroupConstrained = channel.group_constrained === true;
|
|
||||||
const channelMembersPermission = isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS : Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS;
|
|
||||||
const channelPropertiesPermission = isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_PROPERTIES : Permissions.MANAGE_PUBLIC_CHANNEL_PROPERTIES;
|
|
||||||
const channelDeletePermission = isPrivate ? Permissions.DELETE_PRIVATE_CHANNEL : Permissions.DELETE_PUBLIC_CHANNEL;
|
|
||||||
const channelUnarchivePermission = Permissions.MANAGE_TEAM;
|
|
||||||
|
|
||||||
let divider;
|
|
||||||
if (isMobile) {
|
|
||||||
divider = (
|
|
||||||
<li className='MenuGroup mobile-menu-divider'>
|
|
||||||
<hr/>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pluginItems = this.props.pluginMenuItems.map((item) => {
|
|
||||||
return (
|
|
||||||
<Menu.ItemAction
|
|
||||||
id={item.id + '_pluginmenuitem'}
|
|
||||||
key={item.id + '_pluginmenuitem'}
|
|
||||||
onClick={() => {
|
|
||||||
if (item.action) {
|
|
||||||
item.action(channel.id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
text={item.text}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<MenuItemToggleInfo
|
|
||||||
show={channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL}
|
|
||||||
channel={channel}
|
|
||||||
/>
|
|
||||||
{/* Remove when this components is migrated to new menus */}
|
|
||||||
<ChannelMoveToSubMenuOld
|
|
||||||
channel={channel}
|
|
||||||
openUp={false}
|
|
||||||
inHeaderDropdown={true}
|
|
||||||
/>
|
|
||||||
<Menu.Group divider={divider}>
|
|
||||||
<MenuItemToggleFavoriteChannel
|
|
||||||
show={isMobile}
|
|
||||||
channel={channel}
|
|
||||||
isFavorite={isFavorite}
|
|
||||||
/>
|
|
||||||
<MenuItemViewPinnedPosts
|
|
||||||
show={isMobile}
|
|
||||||
channel={channel}
|
|
||||||
/>
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='channelNotificationPreferences'
|
|
||||||
show={channel.type !== Constants.DM_CHANNEL && !isArchived}
|
|
||||||
modalId={ModalIdentifiers.CHANNEL_NOTIFICATIONS}
|
|
||||||
dialogType={ChannelNotificationsModal}
|
|
||||||
dialogProps={{
|
|
||||||
channel,
|
|
||||||
currentUser: user,
|
|
||||||
focusOriginElement: 'channelHeaderMenuButton',
|
|
||||||
}}
|
|
||||||
text={localizeMessage({id: 'navbar.preferences', defaultMessage: 'Notification Preferences'})}
|
|
||||||
/>
|
|
||||||
<MenuItemToggleMuteChannel
|
|
||||||
id='channelToggleMuteChannel'
|
|
||||||
user={user}
|
|
||||||
channel={channel}
|
|
||||||
isMuted={isMuted}
|
|
||||||
/>
|
|
||||||
</Menu.Group>
|
|
||||||
|
|
||||||
<Menu.Group divider={divider}>
|
|
||||||
<ChannelPermissionGate
|
|
||||||
channelId={channel.id}
|
|
||||||
teamId={channel.team_id}
|
|
||||||
permissions={[channelMembersPermission]}
|
|
||||||
>
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='channelInviteMembers'
|
|
||||||
show={channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL && !isArchived && !isDefault && !isGroupConstrained}
|
|
||||||
modalId={ModalIdentifiers.CHANNEL_INVITE}
|
|
||||||
dialogType={ChannelInviteModal}
|
|
||||||
dialogProps={{channel}}
|
|
||||||
text={localizeMessage({id: 'navbar.addMembers', defaultMessage: 'Add Members'})}
|
|
||||||
/>
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='channelAddMembers'
|
|
||||||
show={channel.type === Constants.GM_CHANNEL && !isArchived && !isGroupConstrained}
|
|
||||||
modalId={ModalIdentifiers.CREATE_DM_CHANNEL}
|
|
||||||
dialogType={MoreDirectChannels}
|
|
||||||
dialogProps={{isExistingChannel: true, focusOriginElement: 'channelHeaderMenuButton'}}
|
|
||||||
text={localizeMessage({id: 'navbar.addMembers', defaultMessage: 'Add Members'})}
|
|
||||||
/>
|
|
||||||
</ChannelPermissionGate>
|
|
||||||
<MenuItemOpenMembersRHS
|
|
||||||
id='channelViewMembers'
|
|
||||||
channel={channel}
|
|
||||||
show={channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL && (isArchived || isDefault)}
|
|
||||||
text={localizeMessage({id: 'channel_header.viewMembers', defaultMessage: 'View Members'})}
|
|
||||||
/>
|
|
||||||
<ChannelPermissionGate
|
|
||||||
channelId={channel.id}
|
|
||||||
teamId={channel.team_id}
|
|
||||||
permissions={[channelMembersPermission]}
|
|
||||||
>
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='channelAddGroups'
|
|
||||||
show={channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL && !isArchived && !isDefault && isGroupConstrained && isLicensedForLDAPGroups}
|
|
||||||
modalId={ModalIdentifiers.ADD_GROUPS_TO_CHANNEL}
|
|
||||||
dialogType={AddGroupsToChannelModal}
|
|
||||||
text={localizeMessage({id: 'navbar.addGroups', defaultMessage: 'Add Groups'})}
|
|
||||||
/>
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='channelManageGroups'
|
|
||||||
show={channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL && !isArchived && !isDefault && isGroupConstrained && isLicensedForLDAPGroups}
|
|
||||||
modalId={ModalIdentifiers.MANAGE_CHANNEL_GROUPS}
|
|
||||||
dialogType={ChannelGroupsManageModal}
|
|
||||||
dialogProps={{channelID: channel.id}}
|
|
||||||
text={localizeMessage({id: 'navbar_dropdown.manageGroups', defaultMessage: 'Manage Groups'})}
|
|
||||||
/>
|
|
||||||
<MenuItemOpenMembersRHS
|
|
||||||
id='channelManageMembers'
|
|
||||||
channel={channel}
|
|
||||||
show={channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL && !isArchived && !isDefault}
|
|
||||||
text={localizeMessage({id: 'channel_header.manageMembers', defaultMessage: 'Manage Members'})}
|
|
||||||
editMembers={!isArchived}
|
|
||||||
/>
|
|
||||||
</ChannelPermissionGate>
|
|
||||||
<ChannelPermissionGate
|
|
||||||
channelId={channel.id}
|
|
||||||
teamId={channel.team_id}
|
|
||||||
permissions={[channelMembersPermission]}
|
|
||||||
invert={true}
|
|
||||||
>
|
|
||||||
<MenuItemOpenMembersRHS
|
|
||||||
id='channelViewMembers'
|
|
||||||
channel={channel}
|
|
||||||
show={channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL && !isArchived && !isDefault}
|
|
||||||
text={localizeMessage({id: 'channel_header.viewMembers', defaultMessage: 'View Members'})}
|
|
||||||
/>
|
|
||||||
</ChannelPermissionGate>
|
|
||||||
</Menu.Group>
|
|
||||||
|
|
||||||
<Menu.Group divider={divider}>
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='channelEditHeader'
|
|
||||||
show={(channel.type === Constants.DM_CHANNEL || channel.type === Constants.GM_CHANNEL) && !isArchived && !isReadonly}
|
|
||||||
modalId={ModalIdentifiers.EDIT_CHANNEL_HEADER}
|
|
||||||
dialogType={EditChannelHeaderModal}
|
|
||||||
dialogProps={{channel}}
|
|
||||||
text={localizeMessage({id: 'channel_header.setConversationHeader', defaultMessage: 'Edit Conversation Header'})}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='convertGMPrivateChannel'
|
|
||||||
show={channel.type === Constants.GM_CHANNEL && !isArchived && !isReadonly && !isGuest(user.roles)}
|
|
||||||
modalId={ModalIdentifiers.CONVERT_GM_TO_CHANNEL}
|
|
||||||
dialogType={ConvertGmToChannelModal}
|
|
||||||
dialogProps={{channel}}
|
|
||||||
text={localizeMessage({id: 'sidebar_left.sidebar_channel_menu_convert_to_channel', defaultMessage: 'Convert to Private Channel'})}
|
|
||||||
/>
|
|
||||||
</Menu.Group>
|
|
||||||
|
|
||||||
<Menu.Group divider={divider}>
|
|
||||||
{isChannelBookmarksEnabled && <ChannelBookmarksSubmenu channel={channel}/>}
|
|
||||||
<ChannelPermissionGate
|
|
||||||
channelId={channel.id}
|
|
||||||
teamId={channel.team_id}
|
|
||||||
permissions={[channelPropertiesPermission]}
|
|
||||||
>
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='channelEditHeader'
|
|
||||||
show={channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL && !isArchived && !isReadonly}
|
|
||||||
modalId={ModalIdentifiers.EDIT_CHANNEL_HEADER}
|
|
||||||
dialogType={EditChannelHeaderModal}
|
|
||||||
dialogProps={{channel}}
|
|
||||||
text={localizeMessage({id: 'channel_header.setHeader', defaultMessage: 'Edit Channel Header'})}
|
|
||||||
/>
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='channelEditPurpose'
|
|
||||||
show={!isArchived && !isReadonly && channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL}
|
|
||||||
modalId={ModalIdentifiers.EDIT_CHANNEL_PURPOSE}
|
|
||||||
dialogType={EditChannelPurposeModal}
|
|
||||||
dialogProps={{channel}}
|
|
||||||
text={localizeMessage({id: 'channel_header.setPurpose', defaultMessage: 'Edit Channel Purpose'})}
|
|
||||||
/>
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='channelRename'
|
|
||||||
show={!isArchived && channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL}
|
|
||||||
modalId={ModalIdentifiers.RENAME_CHANNEL}
|
|
||||||
dialogType={RenameChannelModal}
|
|
||||||
dialogProps={{channel}}
|
|
||||||
text={localizeMessage({id: 'channel_header.rename', defaultMessage: 'Rename Channel'})}
|
|
||||||
/>
|
|
||||||
</ChannelPermissionGate>
|
|
||||||
<ChannelPermissionGate
|
|
||||||
channelId={channel.id}
|
|
||||||
teamId={channel.team_id}
|
|
||||||
permissions={[Permissions.CONVERT_PUBLIC_CHANNEL_TO_PRIVATE]}
|
|
||||||
>
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='channelConvertToPrivate'
|
|
||||||
show={!isArchived && !isDefault && channel.type === Constants.OPEN_CHANNEL}
|
|
||||||
modalId={ModalIdentifiers.CONVERT_CHANNEL}
|
|
||||||
dialogType={ConvertChannelModal}
|
|
||||||
dialogProps={{
|
|
||||||
channelId: channel.id,
|
|
||||||
channelDisplayName: channel.display_name,
|
|
||||||
}}
|
|
||||||
text={localizeMessage({id: 'channel_header.convert', defaultMessage: 'Convert to Private Channel'})}
|
|
||||||
/>
|
|
||||||
</ChannelPermissionGate>
|
|
||||||
<MenuItemLeaveChannel
|
|
||||||
id='channelLeaveChannel'
|
|
||||||
channel={channel}
|
|
||||||
isDefault={isDefault}
|
|
||||||
isGuestUser={isGuest(user.roles)}
|
|
||||||
/>
|
|
||||||
<ChannelPermissionGate
|
|
||||||
channelId={channel.id}
|
|
||||||
teamId={channel.team_id}
|
|
||||||
permissions={[channelDeletePermission]}
|
|
||||||
>
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='channelArchiveChannel'
|
|
||||||
show={!isArchived && !isDefault && channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL}
|
|
||||||
modalId={ModalIdentifiers.DELETE_CHANNEL}
|
|
||||||
className='MenuItem__dangerous'
|
|
||||||
dialogType={DeleteChannelModal}
|
|
||||||
dialogProps={{
|
|
||||||
channel,
|
|
||||||
penultimateViewedChannelName,
|
|
||||||
}}
|
|
||||||
text={localizeMessage({id: 'channel_header.delete', defaultMessage: 'Archive Channel'})}
|
|
||||||
/>
|
|
||||||
</ChannelPermissionGate>
|
|
||||||
{isMobile &&
|
|
||||||
<MobileChannelHeaderPlug
|
|
||||||
channel={channel}
|
|
||||||
isDropdown={true}
|
|
||||||
/>}
|
|
||||||
<MenuItemCloseMessage
|
|
||||||
id='channelCloseMessage'
|
|
||||||
channel={channel}
|
|
||||||
currentUser={user}
|
|
||||||
/>
|
|
||||||
<MenuItemCloseChannel
|
|
||||||
isArchived={isArchived}
|
|
||||||
/>
|
|
||||||
</Menu.Group>
|
|
||||||
<Menu.Group>
|
|
||||||
{pluginItems}
|
|
||||||
</Menu.Group>
|
|
||||||
<Menu.Group divider={divider}>
|
|
||||||
<ChannelPermissionGate
|
|
||||||
channelId={channel.id}
|
|
||||||
teamId={channel.team_id}
|
|
||||||
permissions={[channelUnarchivePermission]}
|
|
||||||
>
|
|
||||||
<Menu.ItemToggleModalRedux
|
|
||||||
id='channelUnarchiveChannel'
|
|
||||||
show={isArchived && !isDefault && channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL}
|
|
||||||
modalId={ModalIdentifiers.UNARCHIVE_CHANNEL}
|
|
||||||
dialogType={UnarchiveChannelModal}
|
|
||||||
dialogProps={{
|
|
||||||
channel,
|
|
||||||
}}
|
|
||||||
text={localizeMessage({id: 'channel_header.unarchive', defaultMessage: 'Unarchive Channel'})}
|
|
||||||
/>
|
|
||||||
</ChannelPermissionGate>
|
|
||||||
</Menu.Group>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {connect} from 'react-redux';
|
|
||||||
|
|
||||||
import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
|
||||||
import {
|
|
||||||
getCurrentChannel,
|
|
||||||
isCurrentChannelDefault,
|
|
||||||
isCurrentChannelFavorite,
|
|
||||||
isCurrentChannelMuted,
|
|
||||||
isCurrentChannelArchived,
|
|
||||||
getRedirectChannelNameForTeam,
|
|
||||||
} from 'mattermost-redux/selectors/entities/channels';
|
|
||||||
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
|
||||||
import {
|
|
||||||
getUser,
|
|
||||||
getCurrentUser,
|
|
||||||
getUserStatuses,
|
|
||||||
getCurrentUserId,
|
|
||||||
} from 'mattermost-redux/selectors/entities/users';
|
|
||||||
|
|
||||||
import {getPenultimateViewedChannelName} from 'selectors/local_storage';
|
|
||||||
import {getChannelHeaderMenuPluginComponents} from 'selectors/plugins';
|
|
||||||
|
|
||||||
import {getIsChannelBookmarksEnabled} from 'components/channel_bookmarks/utils';
|
|
||||||
|
|
||||||
import {Constants} from 'utils/constants';
|
|
||||||
import * as Utils from 'utils/utils';
|
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
|
||||||
|
|
||||||
import Desktop from './channel_header_dropdown';
|
|
||||||
import Items from './channel_header_dropdown_items';
|
|
||||||
import Mobile from './mobile_channel_header_dropdown';
|
|
||||||
|
|
||||||
const getTeammateId = createSelector(
|
|
||||||
'getTeammateId',
|
|
||||||
getCurrentChannel,
|
|
||||||
getCurrentUserId,
|
|
||||||
(channel, currentUserId) => {
|
|
||||||
if (channel?.type !== Constants.DM_CHANNEL) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Utils.getUserIdFromChannelId(channel.name, currentUserId);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const getTeammateStatus = createSelector(
|
|
||||||
'getTeammateStatus',
|
|
||||||
getUserStatuses,
|
|
||||||
getTeammateId,
|
|
||||||
(userStatuses, teammateId) => {
|
|
||||||
if (!teammateId) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return userStatuses[teammateId];
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const mapStateToProps = (state: GlobalState) => ({
|
|
||||||
user: getCurrentUser(state),
|
|
||||||
channel: getCurrentChannel(state),
|
|
||||||
isDefault: isCurrentChannelDefault(state),
|
|
||||||
isFavorite: isCurrentChannelFavorite(state),
|
|
||||||
isMuted: isCurrentChannelMuted(state),
|
|
||||||
isReadonly: false,
|
|
||||||
isArchived: isCurrentChannelArchived(state),
|
|
||||||
penultimateViewedChannelName: getPenultimateViewedChannelName(state) || getRedirectChannelNameForTeam(state, getCurrentTeamId(state)),
|
|
||||||
pluginMenuItems: getChannelHeaderMenuPluginComponents(state),
|
|
||||||
isLicensedForLDAPGroups: state.entities.general.license.LDAPGroups === 'true',
|
|
||||||
isChannelBookmarksEnabled: getIsChannelBookmarksEnabled(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mobileMapStateToProps = (state: GlobalState) => {
|
|
||||||
const user = getCurrentUser(state);
|
|
||||||
const channel = getCurrentChannel(state);
|
|
||||||
const teammateId = getTeammateId(state);
|
|
||||||
|
|
||||||
let teammateIsBot = false;
|
|
||||||
let displayName = '';
|
|
||||||
if (teammateId) {
|
|
||||||
const teammate = getUser(state, teammateId);
|
|
||||||
teammateIsBot = teammate && teammate.is_bot;
|
|
||||||
displayName = Utils.getDisplayNameByUser(state, teammate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
user,
|
|
||||||
channel,
|
|
||||||
teammateId,
|
|
||||||
teammateIsBot,
|
|
||||||
teammateStatus: getTeammateStatus(state),
|
|
||||||
displayName,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ChannelHeaderDropdown = Desktop;
|
|
||||||
export const ChannelHeaderDropdownItems = connect(mapStateToProps)(Items);
|
|
||||||
export const MobileChannelHeaderDropdown = connect(mobileMapStateToProps)(Mobile);
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`components/ChannelHeaderDropdown/MenuItem.CloseChannel shoud be hidden if the channel is not archived 1`] = `
|
|
||||||
<MenuItemAction
|
|
||||||
onClick={[MockFunction]}
|
|
||||||
show={false}
|
|
||||||
text="Close Channel"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components/ChannelHeaderDropdown/MenuItem.CloseChannel should match snapshot 1`] = `
|
|
||||||
<MenuItemAction
|
|
||||||
onClick={[MockFunction]}
|
|
||||||
show={true}
|
|
||||||
text="Close Channel"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {shallow} from 'enzyme';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
import CloseChannel from './close_channel';
|
|
||||||
|
|
||||||
describe('components/ChannelHeaderDropdown/MenuItem.CloseChannel', () => {
|
|
||||||
const baseProps = {
|
|
||||||
isArchived: true,
|
|
||||||
actions: {
|
|
||||||
goToLastViewedChannel: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should match snapshot', () => {
|
|
||||||
const wrapper = shallow(<CloseChannel {...baseProps}/>);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shoud be hidden if the channel is not archived', () => {
|
|
||||||
const props = {
|
|
||||||
...baseProps,
|
|
||||||
isArchived: false,
|
|
||||||
};
|
|
||||||
const wrapper = shallow(<CloseChannel {...props}/>);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should runs goToLastViewedChannel function on click', () => {
|
|
||||||
const props = {
|
|
||||||
...baseProps,
|
|
||||||
actions: {
|
|
||||||
...baseProps.actions,
|
|
||||||
goToLastViewedChannel: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const wrapper = shallow(<CloseChannel {...props}/>);
|
|
||||||
wrapper.find(Menu.ItemAction).simulate('click');
|
|
||||||
expect(props.actions.goToLastViewedChannel).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import {useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isArchived: boolean;
|
|
||||||
actions: {
|
|
||||||
goToLastViewedChannel: () => void;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const CloseChannel = ({
|
|
||||||
isArchived,
|
|
||||||
actions,
|
|
||||||
}: Props): JSX.Element => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu.ItemAction
|
|
||||||
show={isArchived}
|
|
||||||
onClick={actions.goToLastViewedChannel}
|
|
||||||
text={intl.formatMessage({
|
|
||||||
id: 'center_panel.archived.closeChannel',
|
|
||||||
defaultMessage: 'Close Channel',
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default React.memo(CloseChannel);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {connect} from 'react-redux';
|
|
||||||
import {bindActionCreators} from 'redux';
|
|
||||||
import type {Dispatch} from 'redux';
|
|
||||||
|
|
||||||
import {goToLastViewedChannel} from 'actions/views/channel';
|
|
||||||
|
|
||||||
import CloseChannel from './close_channel';
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|
||||||
actions: bindActionCreators({
|
|
||||||
goToLastViewedChannel,
|
|
||||||
}, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(CloseChannel);
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`components/ChannelHeaderDropdown/MenuItem.CloseMessage should match snapshot for DM Channel 1`] = `
|
|
||||||
<MenuItemAction
|
|
||||||
onClick={[Function]}
|
|
||||||
show={true}
|
|
||||||
text="Close Direct Message"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components/ChannelHeaderDropdown/MenuItem.CloseMessage should match snapshot for GM Channel 1`] = `
|
|
||||||
<MenuItemAction
|
|
||||||
onClick={[Function]}
|
|
||||||
show={true}
|
|
||||||
text="Close Group Message"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {shallow} from 'enzyme';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import type {ChannelType} from '@mattermost/types/channels';
|
|
||||||
import type {TeamType} from '@mattermost/types/teams';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
import {Constants} from 'utils/constants';
|
|
||||||
import {TestHelper} from 'utils/test_helper';
|
|
||||||
|
|
||||||
import CloseMessage from './close_message';
|
|
||||||
|
|
||||||
describe('components/ChannelHeaderDropdown/MenuItem.CloseMessage', () => {
|
|
||||||
const baseProps = {
|
|
||||||
currentUser: TestHelper.getUserMock(),
|
|
||||||
redirectChannel: 'test-default-channel',
|
|
||||||
currentTeam: TestHelper.getTeamMock({
|
|
||||||
id: 'team_id',
|
|
||||||
name: 'test-team',
|
|
||||||
display_name: 'Test team display name',
|
|
||||||
description: 'Test team description',
|
|
||||||
type: 'team-type' as TeamType,
|
|
||||||
}),
|
|
||||||
actions: {
|
|
||||||
savePreferences: jest.fn(() => Promise.resolve()),
|
|
||||||
leaveDirectChannel: jest.fn(() => Promise.resolve()),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const groupChannel = TestHelper.getChannelMock({
|
|
||||||
id: 'channel_id',
|
|
||||||
type: Constants.GM_CHANNEL as ChannelType,
|
|
||||||
});
|
|
||||||
|
|
||||||
const directChannel = TestHelper.getChannelMock({
|
|
||||||
id: 'channel_id',
|
|
||||||
type: Constants.DM_CHANNEL as ChannelType,
|
|
||||||
teammate_id: 'teammate-id',
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should match snapshot for DM Channel', () => {
|
|
||||||
const props = {...baseProps, channel: directChannel};
|
|
||||||
const wrapper = shallow(<CloseMessage {...props}/>);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should match snapshot for GM Channel', () => {
|
|
||||||
const props = {...baseProps, channel: groupChannel};
|
|
||||||
const wrapper = shallow(<CloseMessage {...props}/>);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should run savePreferences function on click for DM', () => {
|
|
||||||
const props = {...baseProps, channel: directChannel};
|
|
||||||
const wrapper = shallow(<CloseMessage {...props}/>);
|
|
||||||
wrapper.find(Menu.ItemAction).simulate('click', {
|
|
||||||
preventDefault: jest.fn(),
|
|
||||||
});
|
|
||||||
expect(props.actions.savePreferences).toBeCalledWith(props.currentUser.id, [{user_id: props.currentUser.id, category: Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, name: props.channel.teammate_id, value: 'false'}]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should run savePreferences function on click for GM', () => {
|
|
||||||
const props = {...baseProps, channel: groupChannel};
|
|
||||||
const wrapper = shallow(<CloseMessage {...props}/>);
|
|
||||||
wrapper.find(Menu.ItemAction).simulate('click', {
|
|
||||||
preventDefault: jest.fn(),
|
|
||||||
});
|
|
||||||
expect(props.actions.savePreferences).toBeCalledWith(props.currentUser.id, [{user_id: props.currentUser.id, category: Constants.Preferences.CATEGORY_GROUP_CHANNEL_SHOW, name: props.channel.id, value: 'false'}]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
|
||||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
|
||||||
import type {Team} from '@mattermost/types/teams';
|
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
import {getHistory} from 'utils/browser_history';
|
|
||||||
import {Constants} from 'utils/constants';
|
|
||||||
import {localizeMessage} from 'utils/utils';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object with info about currentUser
|
|
||||||
*/
|
|
||||||
currentUser: UserProfile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object with info about currentTeam
|
|
||||||
*/
|
|
||||||
currentTeam?: Team;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* String with info about redirect
|
|
||||||
*/
|
|
||||||
redirectChannel: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object with info about channel
|
|
||||||
*/
|
|
||||||
channel: Channel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use for test selector
|
|
||||||
*/
|
|
||||||
id?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object with action creators
|
|
||||||
*/
|
|
||||||
actions: {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action creator to update user preferences
|
|
||||||
*/
|
|
||||||
savePreferences: (userId: string, preferences: PreferenceType[]) => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action creator to leave DM/GM
|
|
||||||
*/
|
|
||||||
leaveDirectChannel: (channelName: string) => void;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class CloseMessage extends React.PureComponent<Props> {
|
|
||||||
handleClose = (e: React.MouseEvent): void => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const {
|
|
||||||
channel,
|
|
||||||
currentUser,
|
|
||||||
currentTeam,
|
|
||||||
redirectChannel,
|
|
||||||
actions: {
|
|
||||||
savePreferences,
|
|
||||||
leaveDirectChannel,
|
|
||||||
},
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
let name: string;
|
|
||||||
let category;
|
|
||||||
if (channel.type === Constants.DM_CHANNEL) {
|
|
||||||
category = Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW;
|
|
||||||
name = channel.teammate_id!;
|
|
||||||
} else {
|
|
||||||
category = Constants.Preferences.CATEGORY_GROUP_CHANNEL_SHOW;
|
|
||||||
name = channel.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
leaveDirectChannel(channel.name);
|
|
||||||
savePreferences(currentUser.id, [{user_id: currentUser.id, category, name, value: 'false'}]);
|
|
||||||
|
|
||||||
if (currentTeam) {
|
|
||||||
getHistory().push(`/${currentTeam.name}/channels/${redirectChannel}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
const {id, channel} = this.props;
|
|
||||||
|
|
||||||
let text;
|
|
||||||
if (channel.type === Constants.DM_CHANNEL) {
|
|
||||||
text = localizeMessage({id: 'center_panel.direct.closeDirectMessage', defaultMessage: 'Close Direct Message'});
|
|
||||||
} else if (channel.type === Constants.GM_CHANNEL) {
|
|
||||||
text = localizeMessage({id: 'center_panel.direct.closeGroupMessage', defaultMessage: 'Close Group Message'});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu.ItemAction
|
|
||||||
id={id}
|
|
||||||
show={channel.type === Constants.DM_CHANNEL || channel.type === Constants.GM_CHANNEL}
|
|
||||||
onClick={this.handleClose}
|
|
||||||
text={text}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {connect} from 'react-redux';
|
|
||||||
import {bindActionCreators} from 'redux';
|
|
||||||
import type {Dispatch} from 'redux';
|
|
||||||
|
|
||||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
|
||||||
import {getRedirectChannelNameForTeam} from 'mattermost-redux/selectors/entities/channels';
|
|
||||||
import {getCurrentTeam, getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
|
||||||
|
|
||||||
import {leaveDirectChannel} from 'actions/views/channel';
|
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
|
||||||
|
|
||||||
import CloseMessage from './close_message';
|
|
||||||
|
|
||||||
const mapStateToProps = (state: GlobalState) => {
|
|
||||||
return {
|
|
||||||
currentTeam: getCurrentTeam(state),
|
|
||||||
redirectChannel: getRedirectChannelNameForTeam(state, getCurrentTeamId(state)),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|
||||||
actions: bindActionCreators({savePreferences, leaveDirectChannel}, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(CloseMessage);
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`components/ChannelHeaderDropdown/MenuItem.LeaveChannel should be hidden if the channel is default channel 1`] = `
|
|
||||||
<MenuItemAction
|
|
||||||
isDangerous={true}
|
|
||||||
onClick={[Function]}
|
|
||||||
show={false}
|
|
||||||
text="Leave Channel"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components/ChannelHeaderDropdown/MenuItem.LeaveChannel should be hidden if the channel type is DM or GM 1`] = `
|
|
||||||
<MenuItemAction
|
|
||||||
isDangerous={true}
|
|
||||||
onClick={[Function]}
|
|
||||||
show={false}
|
|
||||||
text="Leave Channel"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components/ChannelHeaderDropdown/MenuItem.LeaveChannel should be hidden if the channel type is DM or GM 2`] = `
|
|
||||||
<MenuItemAction
|
|
||||||
isDangerous={true}
|
|
||||||
onClick={[Function]}
|
|
||||||
show={false}
|
|
||||||
text="Leave Channel"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components/ChannelHeaderDropdown/MenuItem.LeaveChannel should match snapshot 1`] = `
|
|
||||||
<MenuItemAction
|
|
||||||
isDangerous={true}
|
|
||||||
onClick={[Function]}
|
|
||||||
show={true}
|
|
||||||
text="Leave Channel"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {connect} from 'react-redux';
|
|
||||||
import type {ConnectedProps} from 'react-redux';
|
|
||||||
import {bindActionCreators} from 'redux';
|
|
||||||
import type {Dispatch} from 'redux';
|
|
||||||
|
|
||||||
import {leaveChannel} from 'actions/views/channel';
|
|
||||||
import {openModal} from 'actions/views/modals';
|
|
||||||
|
|
||||||
import LeaveChannel from './leave_channel';
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch: Dispatch) {
|
|
||||||
return {
|
|
||||||
actions: bindActionCreators({
|
|
||||||
leaveChannel,
|
|
||||||
openModal,
|
|
||||||
}, dispatch),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const connector = connect(null, mapDispatchToProps);
|
|
||||||
|
|
||||||
export type PropsFromRedux = ConnectedProps<typeof connector>;
|
|
||||||
|
|
||||||
export default connector(LeaveChannel);
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {shallow} from 'enzyme';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
import {ModalIdentifiers} from 'utils/constants';
|
|
||||||
import {TestHelper} from 'utils/test_helper';
|
|
||||||
|
|
||||||
import LeaveChannel from './leave_channel';
|
|
||||||
|
|
||||||
describe('components/ChannelHeaderDropdown/MenuItem.LeaveChannel', () => {
|
|
||||||
const baseProps = {
|
|
||||||
channel: TestHelper.getChannelMock({
|
|
||||||
id: 'channel_id',
|
|
||||||
type: 'O',
|
|
||||||
}),
|
|
||||||
isGuestUser: false,
|
|
||||||
isDefault: false,
|
|
||||||
actions: {
|
|
||||||
leaveChannel: jest.fn(),
|
|
||||||
openModal: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should match snapshot', () => {
|
|
||||||
const wrapper = shallow(<LeaveChannel {...baseProps}/>);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be hidden if the channel is default channel', () => {
|
|
||||||
const props = {
|
|
||||||
...baseProps,
|
|
||||||
isDefault: true,
|
|
||||||
};
|
|
||||||
const wrapper = shallow(<LeaveChannel {...props}/>);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be hidden if the channel type is DM or GM', () => {
|
|
||||||
const props = {
|
|
||||||
...baseProps,
|
|
||||||
channel: {...baseProps.channel},
|
|
||||||
};
|
|
||||||
const makeWrapper = () => shallow(<LeaveChannel {...props}/>);
|
|
||||||
|
|
||||||
props.channel.type = 'D';
|
|
||||||
expect(makeWrapper()).toMatchSnapshot();
|
|
||||||
|
|
||||||
props.channel.type = 'G';
|
|
||||||
expect(makeWrapper()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should runs leaveChannel function on click only if the channel is not private', () => {
|
|
||||||
const props = {
|
|
||||||
...baseProps,
|
|
||||||
channel: {...baseProps.channel},
|
|
||||||
actions: {...baseProps.actions},
|
|
||||||
};
|
|
||||||
const wrapper = shallow(<LeaveChannel {...props}/>);
|
|
||||||
|
|
||||||
wrapper.find(Menu.ItemAction).simulate('click', {
|
|
||||||
preventDefault: jest.fn(),
|
|
||||||
});
|
|
||||||
expect(props.actions.leaveChannel).toHaveBeenCalledWith(props.channel.id);
|
|
||||||
expect(props.actions.openModal).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
props.channel.type = 'P';
|
|
||||||
props.actions.leaveChannel = jest.fn();
|
|
||||||
wrapper.find(Menu.ItemAction).simulate('click', {
|
|
||||||
preventDefault: jest.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(props.actions.leaveChannel).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
expect(props.actions.openModal).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL,
|
|
||||||
dialogProps: {
|
|
||||||
channel: props.channel,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React, {memo, useCallback} from 'react';
|
|
||||||
import {useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
|
||||||
|
|
||||||
import LeaveChannelModal from 'components/leave_channel_modal';
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
import {Constants, ModalIdentifiers} from 'utils/constants';
|
|
||||||
|
|
||||||
import type {PropsFromRedux} from './index';
|
|
||||||
|
|
||||||
type Props = PropsFromRedux & {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object with info about user
|
|
||||||
*/
|
|
||||||
channel: Channel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Boolean whether the channel is default
|
|
||||||
*/
|
|
||||||
isDefault: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Boolean whether the user is a guest or no
|
|
||||||
*/
|
|
||||||
isGuestUser: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use for test selector
|
|
||||||
*/
|
|
||||||
id?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const LeaveChannel = ({
|
|
||||||
isDefault = true,
|
|
||||||
isGuestUser = false,
|
|
||||||
channel,
|
|
||||||
actions: {
|
|
||||||
leaveChannel,
|
|
||||||
openModal,
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
}: Props) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const handleLeave = useCallback((e: Event) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (channel.type === Constants.PRIVATE_CHANNEL) {
|
|
||||||
openModal({
|
|
||||||
modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL,
|
|
||||||
dialogType: LeaveChannelModal,
|
|
||||||
dialogProps: {
|
|
||||||
channel,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
leaveChannel(channel.id);
|
|
||||||
}
|
|
||||||
}, [channel, leaveChannel, openModal]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu.ItemAction
|
|
||||||
id={id}
|
|
||||||
show={(!isDefault || isGuestUser) && channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL}
|
|
||||||
onClick={handleLeave}
|
|
||||||
text={intl.formatMessage({id: 'channel_header.leave', defaultMessage: 'Leave Channel'})}
|
|
||||||
isDangerous={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(LeaveChannel);
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {connect} from 'react-redux';
|
|
||||||
import {bindActionCreators} from 'redux';
|
|
||||||
import type {Dispatch} from 'redux';
|
|
||||||
|
|
||||||
import {showChannelMembers} from 'actions/views/rhs';
|
|
||||||
import {getIsRhsOpen, getRhsState} from 'selectors/rhs';
|
|
||||||
|
|
||||||
import {RHSStates} from 'utils/constants';
|
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
|
||||||
|
|
||||||
import OpenChannelMembersRHS from './open_members_rhs';
|
|
||||||
|
|
||||||
const mapStateToProps = (state: GlobalState) => ({
|
|
||||||
rhsOpen: getIsRhsOpen(state) && getRhsState(state) === RHSStates.CHANNEL_MEMBERS,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|
||||||
actions: bindActionCreators({
|
|
||||||
showChannelMembers,
|
|
||||||
}, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(OpenChannelMembersRHS);
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
type Action = {
|
|
||||||
showChannelMembers: (channelId: string, editMembers: boolean) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type OwnProps = {
|
|
||||||
channel: Channel;
|
|
||||||
show: boolean;
|
|
||||||
id: string;
|
|
||||||
editMembers?: boolean;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
rhsOpen: boolean;
|
|
||||||
actions: Action;
|
|
||||||
} & OwnProps;
|
|
||||||
|
|
||||||
const ToggleChannelMembersRHS = ({
|
|
||||||
show,
|
|
||||||
id,
|
|
||||||
channel,
|
|
||||||
rhsOpen,
|
|
||||||
text,
|
|
||||||
editMembers = false,
|
|
||||||
actions,
|
|
||||||
}: Props) => {
|
|
||||||
const openRHSIfNotOpen = () => {
|
|
||||||
if (rhsOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
actions.showChannelMembers(channel.id, editMembers);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu.ItemAction
|
|
||||||
show={show}
|
|
||||||
id={id}
|
|
||||||
onClick={openRHSIfNotOpen}
|
|
||||||
text={text}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ToggleChannelMembersRHS;
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`components/ChannelHeaderDropdown/MenuItem.ToggleFavoriteChannel should match snapshot for favorite channel 1`] = `
|
|
||||||
<MenuItemAction
|
|
||||||
onClick={[Function]}
|
|
||||||
show={true}
|
|
||||||
text="Remove from Favorites"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`components/ChannelHeaderDropdown/MenuItem.ToggleFavoriteChannel should match snapshot for not favorite channel 1`] = `
|
|
||||||
<MenuItemAction
|
|
||||||
onClick={[Function]}
|
|
||||||
show={true}
|
|
||||||
text="Add to Favorites"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {connect} from 'react-redux';
|
|
||||||
import {bindActionCreators} from 'redux';
|
|
||||||
import type {Dispatch} from 'redux';
|
|
||||||
|
|
||||||
import {favoriteChannel, unfavoriteChannel} from 'mattermost-redux/actions/channels';
|
|
||||||
|
|
||||||
import ToggleFavoriteChannel from './toggle_favorite_channel';
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|
||||||
actions: bindActionCreators({
|
|
||||||
favoriteChannel,
|
|
||||||
unfavoriteChannel,
|
|
||||||
}, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(ToggleFavoriteChannel);
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {shallow} from 'enzyme';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import type {ChannelType} from '@mattermost/types/channels';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
import ToggleFavoriteChannel from './toggle_favorite_channel';
|
|
||||||
|
|
||||||
describe('components/ChannelHeaderDropdown/MenuItem.ToggleFavoriteChannel', () => {
|
|
||||||
const baseProps = {
|
|
||||||
channel: {
|
|
||||||
id: 'channel_id',
|
|
||||||
display_name: 'channel_display_name',
|
|
||||||
create_at: 0,
|
|
||||||
update_at: 0,
|
|
||||||
delete_at: 0,
|
|
||||||
team_id: '',
|
|
||||||
type: 'O' as ChannelType,
|
|
||||||
name: '',
|
|
||||||
header: '',
|
|
||||||
purpose: '',
|
|
||||||
last_post_at: 0,
|
|
||||||
last_root_post_at: 0,
|
|
||||||
creator_id: '',
|
|
||||||
scheme_id: '',
|
|
||||||
group_constrained: false,
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
favoriteChannel: jest.fn(),
|
|
||||||
unfavoriteChannel: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const propsForFavorite = {
|
|
||||||
...baseProps,
|
|
||||||
isFavorite: true,
|
|
||||||
actions: {
|
|
||||||
favoriteChannel: jest.fn(),
|
|
||||||
unfavoriteChannel: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const propsForNotFavorite = {
|
|
||||||
...baseProps,
|
|
||||||
isFavorite: false,
|
|
||||||
actions: {
|
|
||||||
favoriteChannel: jest.fn(),
|
|
||||||
unfavoriteChannel: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should match snapshot for favorite channel', () => {
|
|
||||||
const wrapper = shallow(<ToggleFavoriteChannel {...propsForFavorite}/>);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should runs unfavoriteChannel function for favorite channel', () => {
|
|
||||||
const wrapper = shallow(<ToggleFavoriteChannel {...propsForFavorite}/>);
|
|
||||||
|
|
||||||
wrapper.find(Menu.ItemAction).simulate('click', {
|
|
||||||
preventDefault: jest.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(propsForFavorite.actions.unfavoriteChannel).toHaveBeenCalledWith(propsForFavorite.channel.id);
|
|
||||||
expect(propsForFavorite.actions.favoriteChannel).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should match snapshot for not favorite channel', () => {
|
|
||||||
const wrapper = shallow(<ToggleFavoriteChannel {...propsForNotFavorite}/>);
|
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should runs favoriteChannel function for not favorite channel', () => {
|
|
||||||
const wrapper = shallow(<ToggleFavoriteChannel {...propsForNotFavorite}/>);
|
|
||||||
|
|
||||||
wrapper.find(Menu.ItemAction).simulate('click', {
|
|
||||||
preventDefault: jest.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(propsForNotFavorite.actions.favoriteChannel).toHaveBeenCalledWith(propsForFavorite.channel.id);
|
|
||||||
expect(propsForNotFavorite.actions.unfavoriteChannel).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React, {memo, useCallback} from 'react';
|
|
||||||
import type {MouseEvent} from 'react';
|
|
||||||
import {useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
type Action = {
|
|
||||||
favoriteChannel: (channelId: string) => void;
|
|
||||||
unfavoriteChannel: (channelId: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
show?: boolean;
|
|
||||||
channel: Channel;
|
|
||||||
isFavorite: boolean;
|
|
||||||
actions: Action;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ToggleFavoriteChannel = ({
|
|
||||||
show = true,
|
|
||||||
isFavorite,
|
|
||||||
actions: {
|
|
||||||
favoriteChannel,
|
|
||||||
unfavoriteChannel,
|
|
||||||
},
|
|
||||||
channel,
|
|
||||||
}: Props) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const toggleFavoriteChannel = useCallback((channelId: string) => {
|
|
||||||
return isFavorite ? unfavoriteChannel(channelId) : favoriteChannel(channelId);
|
|
||||||
}, [isFavorite, favoriteChannel, unfavoriteChannel]);
|
|
||||||
|
|
||||||
const handleClick = useCallback((e: MouseEvent<HTMLButtonElement>): void => {
|
|
||||||
e.preventDefault();
|
|
||||||
toggleFavoriteChannel(channel.id);
|
|
||||||
}, [channel.id, toggleFavoriteChannel]);
|
|
||||||
|
|
||||||
let text;
|
|
||||||
if (isFavorite) {
|
|
||||||
text = intl.formatMessage({id: 'channelHeader.removeFromFavorites', defaultMessage: 'Remove from Favorites'});
|
|
||||||
} else {
|
|
||||||
text = intl.formatMessage({id: 'channelHeader.addToFavorites', defaultMessage: 'Add to Favorites'});
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Menu.ItemAction
|
|
||||||
show={show}
|
|
||||||
onClick={handleClick}
|
|
||||||
text={text}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(ToggleFavoriteChannel);
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {connect} from 'react-redux';
|
|
||||||
import {bindActionCreators} from 'redux';
|
|
||||||
import type {Dispatch} from 'redux';
|
|
||||||
|
|
||||||
import {closeRightHandSide, showChannelInfo} from 'actions/views/rhs';
|
|
||||||
import {getIsRhsOpen, getRhsState} from 'selectors/rhs';
|
|
||||||
|
|
||||||
import {RHSStates} from 'utils/constants';
|
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
|
||||||
|
|
||||||
import ToggleInfo from './toggle_info';
|
|
||||||
|
|
||||||
const mapStateToProps = (state: GlobalState) => ({
|
|
||||||
rhsOpen: getIsRhsOpen(state) && getRhsState(state) === RHSStates.CHANNEL_INFO,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|
||||||
actions: bindActionCreators({
|
|
||||||
closeRightHandSide,
|
|
||||||
showChannelInfo,
|
|
||||||
}, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ToggleInfo);
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import {useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
type Action = {
|
|
||||||
closeRightHandSide: () => void;
|
|
||||||
showChannelInfo: (channelId: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
show: boolean;
|
|
||||||
channel: Channel;
|
|
||||||
rhsOpen: boolean;
|
|
||||||
actions: Action;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ToggleInfo = ({show, channel, rhsOpen, actions}: Props) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const toggleRHS = () => {
|
|
||||||
if (rhsOpen) {
|
|
||||||
actions.closeRightHandSide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
actions.showChannelInfo(channel.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
let text;
|
|
||||||
if (rhsOpen) {
|
|
||||||
text = intl.formatMessage({id: 'channelHeader.hideInfo', defaultMessage: 'Close Info'});
|
|
||||||
} else {
|
|
||||||
text = intl.formatMessage({id: 'channelHeader.viewInfo', defaultMessage: 'View Info'});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu.ItemAction
|
|
||||||
show={show}
|
|
||||||
onClick={toggleRHS}
|
|
||||||
text={text}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ToggleInfo;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`components/ChannelHeaderDropdown/MenuItemToggleMuteChannel should match snapshot 1`] = `
|
|
||||||
<MenuItemAction
|
|
||||||
onClick={[Function]}
|
|
||||||
show={true}
|
|
||||||
text="Mute Channel"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {connect} from 'react-redux';
|
|
||||||
import {bindActionCreators} from 'redux';
|
|
||||||
import type {Dispatch} from 'redux';
|
|
||||||
|
|
||||||
import {updateChannelNotifyProps} from 'mattermost-redux/actions/channels';
|
|
||||||
|
|
||||||
import MenuItemToggleMuteChannel from './toggle_mute_channel';
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|
||||||
actions: bindActionCreators({
|
|
||||||
updateChannelNotifyProps,
|
|
||||||
}, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(MenuItemToggleMuteChannel);
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {shallow} from 'enzyme';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
import MenuItemAction from 'components/widgets/menu/menu_items/menu_item_action';
|
|
||||||
|
|
||||||
import {Constants, NotificationLevels} from 'utils/constants';
|
|
||||||
|
|
||||||
import MenuItemToggleMuteChannel from './toggle_mute_channel';
|
|
||||||
|
|
||||||
describe('components/ChannelHeaderDropdown/MenuItemToggleMuteChannel', () => {
|
|
||||||
const baseProps = {
|
|
||||||
user: {
|
|
||||||
id: 'user_id',
|
|
||||||
} as UserProfile,
|
|
||||||
channel: {
|
|
||||||
id: 'channel_id',
|
|
||||||
type: 'O',
|
|
||||||
} as Channel,
|
|
||||||
isMuted: false,
|
|
||||||
actions: {
|
|
||||||
updateChannelNotifyProps: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should match snapshot', () => {
|
|
||||||
const wrapper = shallow(<MenuItemToggleMuteChannel {...baseProps}/>);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should unmute channel on click the channel was muted', () => {
|
|
||||||
const props = {
|
|
||||||
...baseProps,
|
|
||||||
isMuted: true,
|
|
||||||
actions: {
|
|
||||||
updateChannelNotifyProps: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const wrapper = shallow(<MenuItemToggleMuteChannel {...props}/>);
|
|
||||||
|
|
||||||
wrapper.find(Menu.ItemAction).simulate('click');
|
|
||||||
|
|
||||||
expect(props.actions.updateChannelNotifyProps).toBeCalledWith(
|
|
||||||
props.user.id,
|
|
||||||
props.channel.id,
|
|
||||||
{mark_unread: NotificationLevels.ALL},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should mute channel on click the channel was unmuted', () => {
|
|
||||||
const props = {
|
|
||||||
...baseProps,
|
|
||||||
isMuted: false,
|
|
||||||
actions: {
|
|
||||||
updateChannelNotifyProps: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const wrapper = shallow(<MenuItemToggleMuteChannel {...props}/>);
|
|
||||||
|
|
||||||
wrapper.find(Menu.ItemAction).simulate('click');
|
|
||||||
|
|
||||||
expect(props.actions.updateChannelNotifyProps).toBeCalledWith(
|
|
||||||
props.user.id,
|
|
||||||
props.channel.id,
|
|
||||||
{mark_unread: NotificationLevels.MENTION},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show Mute Channel to all channel types except DM_CHANNEL and GM_CHANNEL', () => {
|
|
||||||
[
|
|
||||||
Constants.OPEN_CHANNEL,
|
|
||||||
Constants.PRIVATE_CHANNEL,
|
|
||||||
Constants.ARCHIVED_CHANNEL,
|
|
||||||
].forEach((channelType) => {
|
|
||||||
const channel = {
|
|
||||||
id: 'channel_id',
|
|
||||||
type: channelType,
|
|
||||||
} as Channel;
|
|
||||||
|
|
||||||
const wrapper = shallow(
|
|
||||||
<MenuItemToggleMuteChannel
|
|
||||||
{...baseProps}
|
|
||||||
channel={channel}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
expect(wrapper.find(MenuItemAction).props().show).toEqual(true);
|
|
||||||
expect(wrapper.find(MenuItemAction).props().text).toEqual('Mute Channel');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('should show Mute Conversation to channel types DM_CHANNEL and GM_CHANNEL', () => {
|
|
||||||
[
|
|
||||||
Constants.DM_CHANNEL,
|
|
||||||
Constants.GM_CHANNEL,
|
|
||||||
].forEach((channelType) => {
|
|
||||||
const channel = {
|
|
||||||
id: 'channel_id',
|
|
||||||
type: channelType,
|
|
||||||
} as Channel;
|
|
||||||
|
|
||||||
const wrapper = shallow(
|
|
||||||
<MenuItemToggleMuteChannel
|
|
||||||
{...baseProps}
|
|
||||||
channel={channel}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
expect(wrapper.find(MenuItemAction).props().show).toEqual(true);
|
|
||||||
expect(wrapper.find(MenuItemAction).props().text).toEqual('Mute Conversation');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React, {useCallback} from 'react';
|
|
||||||
import {useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import type {Channel, ChannelNotifyProps} from '@mattermost/types/channels';
|
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
import {Constants, NotificationLevels} from 'utils/constants';
|
|
||||||
|
|
||||||
export type Actions = {
|
|
||||||
updateChannelNotifyProps(userId: string, channelId: string, props: Partial<ChannelNotifyProps>): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object with info about the current user
|
|
||||||
*/
|
|
||||||
user: UserProfile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object with info about the current channel
|
|
||||||
*/
|
|
||||||
channel: Channel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Boolean whether the current channel is muted
|
|
||||||
*/
|
|
||||||
isMuted: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use for test selector
|
|
||||||
*/
|
|
||||||
id?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object with action creators
|
|
||||||
*/
|
|
||||||
actions: Actions;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function MenuItemToggleMuteChannel({
|
|
||||||
id,
|
|
||||||
isMuted,
|
|
||||||
channel,
|
|
||||||
user,
|
|
||||||
actions,
|
|
||||||
}: Props) {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
|
||||||
actions.updateChannelNotifyProps(user.id, channel.id, {
|
|
||||||
mark_unread: (isMuted ? NotificationLevels.ALL : NotificationLevels.MENTION) as 'all' | 'mention',
|
|
||||||
});
|
|
||||||
}, [actions, isMuted, user.id, channel.id]);
|
|
||||||
|
|
||||||
let text;
|
|
||||||
if (channel.type === Constants.DM_CHANNEL || channel.type === Constants.GM_CHANNEL) {
|
|
||||||
text = isMuted ?
|
|
||||||
intl.formatMessage({id: 'channel_header.unmuteConversation', defaultMessage: 'Unmute Conversation'}) :
|
|
||||||
intl.formatMessage({id: 'channel_header.muteConversation', defaultMessage: 'Mute Conversation'});
|
|
||||||
} else {
|
|
||||||
text = isMuted ?
|
|
||||||
intl.formatMessage({id: 'channel_header.unmute', defaultMessage: 'Unmute Channel'}) :
|
|
||||||
intl.formatMessage({id: 'channel_header.mute', defaultMessage: 'Mute Channel'});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu.ItemAction
|
|
||||||
id={id}
|
|
||||||
onClick={handleClick}
|
|
||||||
text={text}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`components/ChannelHeaderDropdown/MenuItem.ViewPinnedPosts should match snapshot 1`] = `
|
|
||||||
<MenuItemAction
|
|
||||||
onClick={[Function]}
|
|
||||||
show={true}
|
|
||||||
text="View Pinned Posts"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {connect} from 'react-redux';
|
|
||||||
import {bindActionCreators} from 'redux';
|
|
||||||
import type {Dispatch} from 'redux';
|
|
||||||
|
|
||||||
import {closeRightHandSide, showPinnedPosts} from 'actions/views/rhs';
|
|
||||||
import {getRhsState} from 'selectors/rhs';
|
|
||||||
|
|
||||||
import {RHSStates} from 'utils/constants';
|
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
|
||||||
|
|
||||||
import ViewPinnedPosts from './view_pinned_posts';
|
|
||||||
|
|
||||||
const mapStateToProps = (state: GlobalState) => ({
|
|
||||||
hasPinnedPosts: getRhsState(state) === RHSStates.PIN,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|
||||||
actions: bindActionCreators({
|
|
||||||
closeRightHandSide,
|
|
||||||
showPinnedPosts,
|
|
||||||
}, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ViewPinnedPosts);
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {shallow} from 'enzyme';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
import ViewPinnedPosts from './view_pinned_posts';
|
|
||||||
|
|
||||||
describe('components/ChannelHeaderDropdown/MenuItem.ViewPinnedPosts', () => {
|
|
||||||
const baseProps = {
|
|
||||||
channel: {
|
|
||||||
id: 'channel_id',
|
|
||||||
},
|
|
||||||
hasPinnedPosts: true,
|
|
||||||
actions: {
|
|
||||||
closeRightHandSide: jest.fn(),
|
|
||||||
showPinnedPosts: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should match snapshot', () => {
|
|
||||||
const wrapper = shallow(<ViewPinnedPosts {...baseProps}/>);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should runs closeRightHandSide function if has any pinned posts', () => {
|
|
||||||
const wrapper = shallow(<ViewPinnedPosts {...baseProps}/>);
|
|
||||||
|
|
||||||
wrapper.find(Menu.ItemAction).simulate('click', {
|
|
||||||
preventDefault: jest.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(baseProps.actions.closeRightHandSide).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should runs showPinnedPosts function if has not pinned posts', () => {
|
|
||||||
const props = {
|
|
||||||
...baseProps,
|
|
||||||
hasPinnedPosts: false,
|
|
||||||
};
|
|
||||||
const wrapper = shallow(<ViewPinnedPosts {...props}/>);
|
|
||||||
|
|
||||||
wrapper.find(Menu.ItemAction).simulate('click', {
|
|
||||||
preventDefault: jest.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(baseProps.actions.showPinnedPosts).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React, {useCallback, memo} from 'react';
|
|
||||||
import type {MouseEvent} from 'react';
|
|
||||||
import {useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
show?: boolean;
|
|
||||||
channel: any;
|
|
||||||
hasPinnedPosts: boolean;
|
|
||||||
actions: {
|
|
||||||
closeRightHandSide: () => void;
|
|
||||||
showPinnedPosts: (id: any) => void;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ViewPinnedPosts = ({
|
|
||||||
channel,
|
|
||||||
hasPinnedPosts,
|
|
||||||
actions: {
|
|
||||||
closeRightHandSide,
|
|
||||||
showPinnedPosts,
|
|
||||||
},
|
|
||||||
show,
|
|
||||||
}: Props) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const handleClick = useCallback((e: MouseEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (hasPinnedPosts) {
|
|
||||||
closeRightHandSide();
|
|
||||||
} else {
|
|
||||||
showPinnedPosts(channel.id);
|
|
||||||
}
|
|
||||||
}, [channel.id, closeRightHandSide, showPinnedPosts, hasPinnedPosts]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu.ItemAction
|
|
||||||
show={show}
|
|
||||||
onClick={handleClick}
|
|
||||||
text={intl.formatMessage({id: 'navbar.viewPinnedPosts', defaultMessage: 'View Pinned Posts'})}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(ViewPinnedPosts);
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React, {memo} from 'react';
|
|
||||||
import {FormattedMessage, useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
|
||||||
|
|
||||||
import {ChannelHeaderDropdownItems} from 'components/channel_header_dropdown';
|
|
||||||
import StatusIcon from 'components/status_icon';
|
|
||||||
import Menu from 'components/widgets/menu/menu';
|
|
||||||
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
|
|
||||||
|
|
||||||
import {Constants} from 'utils/constants';
|
|
||||||
|
|
||||||
import MobileChannelHeaderDropdownAnimation from './mobile_channel_header_dropdown_animation';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
user: UserProfile;
|
|
||||||
channel?: Channel;
|
|
||||||
teammateId: string | null;
|
|
||||||
teammateIsBot?: boolean;
|
|
||||||
teammateStatus?: string;
|
|
||||||
displayName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MobileChannelHeaderDropdown = ({
|
|
||||||
user,
|
|
||||||
channel,
|
|
||||||
teammateId,
|
|
||||||
displayName,
|
|
||||||
teammateIsBot,
|
|
||||||
teammateStatus,
|
|
||||||
}: Props) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const getChannelTitle = () => {
|
|
||||||
if (!channel) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channel.type === Constants.DM_CHANNEL) {
|
|
||||||
if (user.id === teammateId) {
|
|
||||||
return (
|
|
||||||
<FormattedMessage
|
|
||||||
id='channel_header.directchannel.you'
|
|
||||||
defaultMessage='{displayname} (you)'
|
|
||||||
values={{displayname: displayName}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
return channel.display_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
let dmHeaderIconStatus;
|
|
||||||
|
|
||||||
if (!teammateIsBot) {
|
|
||||||
dmHeaderIconStatus = (
|
|
||||||
<StatusIcon status={teammateStatus}/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MenuWrapper animationComponent={MobileChannelHeaderDropdownAnimation}>
|
|
||||||
<a>
|
|
||||||
<span className='heading'>
|
|
||||||
{dmHeaderIconStatus}
|
|
||||||
{getChannelTitle()}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className='fa fa-angle-down header-dropdown__icon'
|
|
||||||
title={intl.formatMessage({id: 'generic_icons.dropdown', defaultMessage: 'Dropdown Icon'})}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<Menu ariaLabel={intl.formatMessage({id: 'channel_header.menuAriaLabel', defaultMessage: 'Channel Menu'})}>
|
|
||||||
<ChannelHeaderDropdownItems isMobile={true}/>
|
|
||||||
<div className='Menu__close visible-xs-block'>
|
|
||||||
{'×'}
|
|
||||||
</div>
|
|
||||||
</Menu>
|
|
||||||
</MenuWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(MobileChannelHeaderDropdown);
|
|
||||||
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import type {ReactNode} from 'react';
|
|
||||||
import {CSSTransition} from 'react-transition-group';
|
|
||||||
|
|
||||||
const ANIMATION_DURATION = 350;
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children?: ReactNode;
|
|
||||||
show: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const timeout = {
|
|
||||||
enter: ANIMATION_DURATION,
|
|
||||||
exit: ANIMATION_DURATION,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MobileChannelHeaderDropdownAnimation = ({show, children}: Props) => {
|
|
||||||
return (
|
|
||||||
<CSSTransition
|
|
||||||
in={show}
|
|
||||||
classNames='mobile-channel-header-dropdown'
|
|
||||||
enter={true}
|
|
||||||
exit={true}
|
|
||||||
mountOnEnter={true}
|
|
||||||
unmountOnExit={true}
|
|
||||||
timeout={timeout}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</CSSTransition>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default React.memo(MobileChannelHeaderDropdownAnimation);
|
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import type {ReactNode} from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import {useIntl} from 'react-intl';
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
|
|
||||||
|
import ChevronDownIcon from '@mattermost/compass-icons/components/chevron-down';
|
||||||
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getCurrentChannel,
|
||||||
|
isCurrentChannelDefault,
|
||||||
|
isCurrentChannelFavorite,
|
||||||
|
isCurrentChannelMuted,
|
||||||
|
} from 'mattermost-redux/selectors/entities/channels';
|
||||||
|
import {getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
import {
|
||||||
|
getCurrentUser,
|
||||||
|
} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
|
||||||
|
import {getChannelHeaderMenuPluginComponents} from 'selectors/plugins';
|
||||||
|
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
import {Constants} from 'utils/constants';
|
||||||
|
|
||||||
|
import ChannelDirectMenu from './channel_header_menu_items/channel_header_direct_menu';
|
||||||
|
import ChannelGroupMenu from './channel_header_menu_items/channel_header_group_menu';
|
||||||
|
import ChannelHeaderMobileMenu from './channel_header_menu_items/channel_header_mobile_menu';
|
||||||
|
import ChannelPublicPrivateMenu from './channel_header_menu_items/channel_header_public_private_menu';
|
||||||
|
|
||||||
|
import ChannelHeaderTitleDirect from '../channel_header/channel_header_title_direct';
|
||||||
|
import ChannelHeaderTitleGroup from '../channel_header/channel_header_title_group';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
dmUser?: UserProfile;
|
||||||
|
gmMembers?: UserProfile[];
|
||||||
|
archivedIcon?: JSX.Element;
|
||||||
|
sharedIcon?: JSX.Element;
|
||||||
|
isMobile?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ChannelHeaderMenu({dmUser, gmMembers, isMobile, archivedIcon, sharedIcon}: Props): JSX.Element | null {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const user = useSelector(getCurrentUser);
|
||||||
|
const channel = useSelector(getCurrentChannel);
|
||||||
|
const isDefault = useSelector(isCurrentChannelDefault);
|
||||||
|
const isFavorite = useSelector(isCurrentChannelFavorite);
|
||||||
|
const isMuted = useSelector(isCurrentChannelMuted);
|
||||||
|
const isLicensedForLDAPGroups = useSelector(getLicense).LDAPGroups === 'true';
|
||||||
|
const pluginMenuItems = useSelector(getChannelHeaderMenuPluginComponents);
|
||||||
|
|
||||||
|
const isReadonly = false;
|
||||||
|
|
||||||
|
if (!channel) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDirect = (channel.type === Constants.DM_CHANNEL);
|
||||||
|
const isGroup = (channel.type === Constants.GM_CHANNEL);
|
||||||
|
|
||||||
|
let channelTitle: ReactNode = channel.display_name;
|
||||||
|
let ariaLabel = intl.formatMessage({
|
||||||
|
id: 'channel_header.otherchannel',
|
||||||
|
defaultMessage: '{displayName} Channel Menu',
|
||||||
|
}, {
|
||||||
|
displayName: channel.display_name,
|
||||||
|
});
|
||||||
|
if (isDirect && dmUser) {
|
||||||
|
channelTitle = <ChannelHeaderTitleDirect dmUser={dmUser}/>;
|
||||||
|
if (user.id === dmUser.id) {
|
||||||
|
ariaLabel = intl.formatMessage({
|
||||||
|
id: 'channel_header.directchannel',
|
||||||
|
defaultMessage: '{displayName} (you) Channel Menu',
|
||||||
|
}, {
|
||||||
|
displayName: channel.display_name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (isGroup) {
|
||||||
|
channelTitle = <ChannelHeaderTitleGroup gmMembers={gmMembers}/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginItems = pluginMenuItems.map((item) => {
|
||||||
|
const handlePluginItemClick = () => {
|
||||||
|
if (item.action) {
|
||||||
|
item.action(channel.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
id={item.id + '_pluginmenuitem'}
|
||||||
|
key={item.id + '_pluginmenuitem'}
|
||||||
|
onClick={handlePluginItemClick}
|
||||||
|
labels={<span>{item.text}</span>}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu.Container
|
||||||
|
menuButtonTooltip={{
|
||||||
|
text: channelTitle as string,
|
||||||
|
}}
|
||||||
|
menuButton={{
|
||||||
|
id: 'channelHeaderDropdownButton',
|
||||||
|
class: classNames('channel-header__trigger style--none'),
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
{archivedIcon}
|
||||||
|
<strong
|
||||||
|
id='channelHeaderTitle'
|
||||||
|
className='heading'
|
||||||
|
>
|
||||||
|
{channelTitle as string}
|
||||||
|
</strong>
|
||||||
|
{sharedIcon}
|
||||||
|
<ChevronDownIcon size={16}/>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
'aria-label': ariaLabel.toLowerCase(),
|
||||||
|
}}
|
||||||
|
menu={{
|
||||||
|
id: 'channelHeaderDropdownMenu',
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
horizontal: 'left',
|
||||||
|
vertical: 'top',
|
||||||
|
}}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isDirect && (
|
||||||
|
<ChannelDirectMenu
|
||||||
|
channel={channel}
|
||||||
|
user={user}
|
||||||
|
isMuted={isMuted}
|
||||||
|
pluginItems={pluginItems}
|
||||||
|
isFavorite={isFavorite}
|
||||||
|
isMobile={isMobile || false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isGroup && (
|
||||||
|
<ChannelGroupMenu
|
||||||
|
channel={channel}
|
||||||
|
user={user}
|
||||||
|
isMuted={isMuted}
|
||||||
|
pluginItems={pluginItems}
|
||||||
|
isFavorite={isFavorite}
|
||||||
|
isMobile={isMobile || false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(!isDirect && !isGroup) && (
|
||||||
|
<ChannelPublicPrivateMenu
|
||||||
|
channel={channel}
|
||||||
|
user={user}
|
||||||
|
isMuted={isMuted}
|
||||||
|
pluginItems={pluginItems}
|
||||||
|
isFavorite={isFavorite}
|
||||||
|
isMobile={isMobile || false}
|
||||||
|
isDefault={isDefault}
|
||||||
|
isReadonly={isReadonly}
|
||||||
|
isLicensedForLDAPGroups={isLicensedForLDAPGroups}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ChannelHeaderMobileMenu
|
||||||
|
isMobile={isMobile || false}
|
||||||
|
pluginItems={pluginItems}
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
</Menu.Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import type {ReactNode} from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {CogOutlineIcon} from '@mattermost/compass-icons/components';
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
|
import ChannelMoveToSubMenu from 'components/channel_move_to_sub_menu';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
import CloseMessage from '../menu_items/close_message';
|
||||||
|
import EditConversationHeader from '../menu_items/edit_conversation_header';
|
||||||
|
import MenuItemPluginItems from '../menu_items/plugins_submenu';
|
||||||
|
import MenuItemToggleFavoriteChannel from '../menu_items/toggle_favorite_channel';
|
||||||
|
import MenuItemToggleInfo from '../menu_items/toggle_info';
|
||||||
|
import MenuItemToggleMuteChannel from '../menu_items/toggle_mute_channel';
|
||||||
|
import MenuItemViewPinnedPosts from '../menu_items/view_pinned_posts';
|
||||||
|
|
||||||
|
interface Props extends Menu.FirstMenuItemProps {
|
||||||
|
channel: Channel;
|
||||||
|
user: UserProfile;
|
||||||
|
isMuted: boolean;
|
||||||
|
isMobile: boolean;
|
||||||
|
isFavorite: boolean;
|
||||||
|
pluginItems: ReactNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChannelHeaderDirectMenu = ({channel, user, isMuted, isMobile, isFavorite, pluginItems, ...rest}: Props) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItemToggleInfo
|
||||||
|
channel={channel}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
<MenuItemToggleMuteChannel
|
||||||
|
userID={user.id}
|
||||||
|
channel={channel}
|
||||||
|
isMuted={isMuted}
|
||||||
|
/>
|
||||||
|
{isMobile && (
|
||||||
|
<>
|
||||||
|
<MenuItemToggleFavoriteChannel
|
||||||
|
channelID={channel.id}
|
||||||
|
isFavorite={isFavorite}
|
||||||
|
/>
|
||||||
|
<MenuItemViewPinnedPosts
|
||||||
|
channelID={channel.id}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<EditConversationHeader
|
||||||
|
leadingElement={<CogOutlineIcon size='18px'/>}
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
<Menu.Separator/>
|
||||||
|
<ChannelMoveToSubMenu
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
{!isMobile && (
|
||||||
|
<MenuItemPluginItems pluginItems={pluginItems}/>
|
||||||
|
)}
|
||||||
|
<Menu.Separator/>
|
||||||
|
<CloseMessage
|
||||||
|
currentUserID={user.id}
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelHeaderDirectMenu;
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import type {ReactNode} from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage, useIntl} from 'react-intl';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChevronRightIcon,
|
||||||
|
CogOutlineIcon,
|
||||||
|
} from '@mattermost/compass-icons/components';
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
|
import {Permissions} from 'mattermost-redux/constants';
|
||||||
|
import {isGuest} from 'mattermost-redux/utils/user_utils';
|
||||||
|
|
||||||
|
import ChannelMoveToSubMenu from 'components/channel_move_to_sub_menu';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate';
|
||||||
|
|
||||||
|
import CloseMessage from '../menu_items/close_message';
|
||||||
|
import MenuItemConvertToPrivate from '../menu_items/convert_gm_to_private';
|
||||||
|
import EditConversationHeader from '../menu_items/edit_conversation_header';
|
||||||
|
import MenuItemNotification from '../menu_items/notification';
|
||||||
|
import MenuItemOpenMembersRHS from '../menu_items/open_members_rhs';
|
||||||
|
import MenuItemPluginItems from '../menu_items/plugins_submenu';
|
||||||
|
import MenuItemToggleFavoriteChannel from '../menu_items/toggle_favorite_channel';
|
||||||
|
import MenuItemToggleInfo from '../menu_items/toggle_info';
|
||||||
|
import MenuItemToggleMuteChannel from '../menu_items/toggle_mute_channel';
|
||||||
|
import MenuItemViewPinnedPosts from '../menu_items/view_pinned_posts';
|
||||||
|
|
||||||
|
interface Props extends Menu.FirstMenuItemProps {
|
||||||
|
channel: Channel;
|
||||||
|
user: UserProfile;
|
||||||
|
isMuted: boolean;
|
||||||
|
isMobile: boolean;
|
||||||
|
isFavorite: boolean;
|
||||||
|
pluginItems: ReactNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChannelHeaderGroupMenu = ({channel, user, isMuted, isMobile, isFavorite, pluginItems, ...rest}: Props) => {
|
||||||
|
const isGroupConstrained = channel?.group_constrained === true;
|
||||||
|
const isArchived = channel.delete_at !== 0;
|
||||||
|
const {formatMessage} = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItemToggleInfo
|
||||||
|
channel={channel}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
<MenuItemToggleMuteChannel
|
||||||
|
userID={user.id}
|
||||||
|
channel={channel}
|
||||||
|
isMuted={isMuted}
|
||||||
|
/>
|
||||||
|
{isMobile && (
|
||||||
|
<>
|
||||||
|
<MenuItemToggleFavoriteChannel
|
||||||
|
channelID={channel.id}
|
||||||
|
isFavorite={isFavorite}
|
||||||
|
/>
|
||||||
|
<MenuItemViewPinnedPosts
|
||||||
|
channelID={channel.id}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!isArchived && (
|
||||||
|
<MenuItemNotification
|
||||||
|
user={user}
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(isArchived && isGroupConstrained && isGuest(user.roles)) && (
|
||||||
|
<EditConversationHeader
|
||||||
|
leadingElement={<CogOutlineIcon size='18px'/>}
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(!isArchived && !isGroupConstrained && !isGuest(user.roles)) && (
|
||||||
|
<Menu.SubMenu
|
||||||
|
id={'channelSettings'}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.settings'
|
||||||
|
defaultMessage='Settings'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
leadingElement={<CogOutlineIcon size={18}/>}
|
||||||
|
trailingElements={<ChevronRightIcon size={16}/>}
|
||||||
|
menuId={'channelSettings-menu'}
|
||||||
|
menuAriaLabel={formatMessage({id: 'channel_header.settings', defaultMessage: 'Settings'})}
|
||||||
|
>
|
||||||
|
<EditConversationHeader
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
<MenuItemConvertToPrivate
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
)}
|
||||||
|
<Menu.Separator/>
|
||||||
|
{(!isArchived && !isGroupConstrained) && (
|
||||||
|
<ChannelPermissionGate
|
||||||
|
channelId={channel.id}
|
||||||
|
teamId={channel.team_id}
|
||||||
|
permissions={[Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS]}
|
||||||
|
>
|
||||||
|
<MenuItemOpenMembersRHS
|
||||||
|
id='channelMembers'
|
||||||
|
channel={channel}
|
||||||
|
text={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.members'
|
||||||
|
defaultMessage='Members'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Menu.Separator/>
|
||||||
|
</ChannelPermissionGate>
|
||||||
|
)}
|
||||||
|
<ChannelMoveToSubMenu channel={channel}/>
|
||||||
|
{!isMobile && (
|
||||||
|
<MenuItemPluginItems pluginItems={pluginItems}/>
|
||||||
|
)}
|
||||||
|
<Menu.Separator/>
|
||||||
|
<CloseMessage
|
||||||
|
currentUserID={user.id}
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelHeaderGroupMenu;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import type {ReactNode} from 'react';
|
||||||
|
import React, {memo} from 'react';
|
||||||
|
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
import MobileChannelHeaderPlugins from '../menu_items/mobile_channel_header_plugins';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isMobile: boolean;
|
||||||
|
channel: Channel;
|
||||||
|
pluginItems: ReactNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChannelHeaderMobileMenu = (props: Props): JSX.Element => {
|
||||||
|
if (!props.isMobile) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MobileChannelHeaderPlugins
|
||||||
|
channel={props.channel}
|
||||||
|
isDropdown={true}
|
||||||
|
/>
|
||||||
|
<Menu.Separator/>
|
||||||
|
{props.pluginItems}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ChannelHeaderMobileMenu);
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import type {ReactNode} from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
|
import {Permissions} from 'mattermost-redux/constants';
|
||||||
|
import {isGuest} from 'mattermost-redux/utils/user_utils';
|
||||||
|
|
||||||
|
import ChannelMoveToSubMenu from 'components/channel_move_to_sub_menu';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate';
|
||||||
|
|
||||||
|
import {Constants} from 'utils/constants';
|
||||||
|
|
||||||
|
import MenuItemArchiveChannel from '../menu_items/archive_channel';
|
||||||
|
import MenuItemChannelBookmarks from '../menu_items/channel_bookmarks_submenu';
|
||||||
|
import MenuItemChannelSettings from '../menu_items/channel_settings_submenu';
|
||||||
|
import MenuItemCloseChannel from '../menu_items/close_channel';
|
||||||
|
import MenuItemGroupsMenuItems from '../menu_items/groups';
|
||||||
|
import MenuItemLeaveChannel from '../menu_items/leave_channel';
|
||||||
|
import MenuItemNotification from '../menu_items/notification';
|
||||||
|
import MenuItemOpenMembersRHS from '../menu_items/open_members_rhs';
|
||||||
|
import MenuItemPluginItems from '../menu_items/plugins_submenu';
|
||||||
|
import MenuItemToggleFavoriteChannel from '../menu_items/toggle_favorite_channel';
|
||||||
|
import MenuItemToggleInfo from '../menu_items/toggle_info';
|
||||||
|
import MenuItemToggleMuteChannel from '../menu_items/toggle_mute_channel';
|
||||||
|
import MenuItemUnarchiveChannel from '../menu_items/unarchive_channel';
|
||||||
|
import MenuItemViewPinnedPosts from '../menu_items/view_pinned_posts';
|
||||||
|
|
||||||
|
interface Props extends Menu.FirstMenuItemProps {
|
||||||
|
channel: Channel;
|
||||||
|
user: UserProfile;
|
||||||
|
isMuted: boolean;
|
||||||
|
isReadonly: boolean;
|
||||||
|
isDefault: boolean;
|
||||||
|
isMobile: boolean;
|
||||||
|
isFavorite: boolean;
|
||||||
|
isLicensedForLDAPGroups: boolean;
|
||||||
|
pluginItems: ReactNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChannelHeaderPublicMenu = ({channel, user, isMuted, isReadonly, isDefault, isMobile, isFavorite, isLicensedForLDAPGroups, pluginItems, ...rest}: Props) => {
|
||||||
|
const isGroupConstrained = channel?.group_constrained === true;
|
||||||
|
const isArchived = channel.delete_at !== 0;
|
||||||
|
const isPrivate = channel?.type === Constants.PRIVATE_CHANNEL;
|
||||||
|
|
||||||
|
const channelMembersPermission = isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS : Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS;
|
||||||
|
const channelDeletePermission = isPrivate ? Permissions.DELETE_PRIVATE_CHANNEL : Permissions.DELETE_PUBLIC_CHANNEL;
|
||||||
|
const channelUnarchivePermission = Permissions.MANAGE_TEAM;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItemToggleInfo
|
||||||
|
channel={channel}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
<MenuItemToggleMuteChannel
|
||||||
|
userID={user.id}
|
||||||
|
channel={channel}
|
||||||
|
isMuted={isMuted}
|
||||||
|
/>
|
||||||
|
{!isArchived && (
|
||||||
|
<>
|
||||||
|
<MenuItemNotification
|
||||||
|
user={user}
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
<MenuItemChannelSettings
|
||||||
|
isReadonly={isReadonly}
|
||||||
|
isDefault={isDefault}
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
<MenuItemChannelBookmarks
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Menu.Separator/>
|
||||||
|
{isMobile && (
|
||||||
|
<>
|
||||||
|
<MenuItemToggleFavoriteChannel
|
||||||
|
channelID={channel.id}
|
||||||
|
isFavorite={isFavorite}
|
||||||
|
/>
|
||||||
|
<MenuItemViewPinnedPosts
|
||||||
|
channelID={channel.id}
|
||||||
|
/>
|
||||||
|
<Menu.Separator/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(isArchived || isDefault) && (
|
||||||
|
<MenuItemOpenMembersRHS
|
||||||
|
id='channelMembers'
|
||||||
|
channel={channel}
|
||||||
|
text={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.members'
|
||||||
|
defaultMessage='Members'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isArchived && !isDefault && (
|
||||||
|
<>
|
||||||
|
<ChannelPermissionGate
|
||||||
|
channelId={channel.id}
|
||||||
|
teamId={channel.team_id}
|
||||||
|
permissions={[channelMembersPermission]}
|
||||||
|
>
|
||||||
|
{isGroupConstrained && isLicensedForLDAPGroups && (
|
||||||
|
<MenuItemGroupsMenuItems
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<MenuItemOpenMembersRHS
|
||||||
|
id='channelMembers'
|
||||||
|
channel={channel}
|
||||||
|
text={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.members'
|
||||||
|
defaultMessage='Members'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ChannelPermissionGate>
|
||||||
|
|
||||||
|
<ChannelPermissionGate
|
||||||
|
channelId={channel.id}
|
||||||
|
teamId={channel.team_id}
|
||||||
|
permissions={[channelMembersPermission]}
|
||||||
|
invert={true}
|
||||||
|
>
|
||||||
|
<MenuItemOpenMembersRHS
|
||||||
|
id='channelMembers'
|
||||||
|
channel={channel}
|
||||||
|
text={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.members'
|
||||||
|
defaultMessage='Members'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ChannelPermissionGate>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Menu.Separator/>
|
||||||
|
<ChannelMoveToSubMenu channel={channel}/>
|
||||||
|
{!isMobile && (
|
||||||
|
<MenuItemPluginItems pluginItems={pluginItems}/>
|
||||||
|
)}
|
||||||
|
{!isDefault && (
|
||||||
|
<Menu.Separator/>
|
||||||
|
)}
|
||||||
|
{!isDefault && !isGuest(user.roles) && (
|
||||||
|
<MenuItemLeaveChannel
|
||||||
|
id='channelLeaveChannel'
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isArchived && (
|
||||||
|
<MenuItemCloseChannel/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isArchived && !isDefault && (
|
||||||
|
<ChannelPermissionGate
|
||||||
|
channelId={channel.id}
|
||||||
|
teamId={channel.team_id}
|
||||||
|
permissions={[channelDeletePermission]}
|
||||||
|
>
|
||||||
|
<MenuItemArchiveChannel
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
</ChannelPermissionGate>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isArchived && !isDefault && (
|
||||||
|
<ChannelPermissionGate
|
||||||
|
channelId={channel.id}
|
||||||
|
teamId={channel.team_id}
|
||||||
|
permissions={[channelUnarchivePermission]}
|
||||||
|
>
|
||||||
|
<MenuItemUnarchiveChannel
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
</ChannelPermissionGate>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelHeaderPublicMenu;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import * as modalActions from 'actions/views/modals';
|
||||||
|
|
||||||
|
import ChannelInviteModal from 'components/channel_invite_modal';
|
||||||
|
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import AddChannelMembers from './add_channel_members';
|
||||||
|
|
||||||
|
describe('components/ChannelHeaderMenu/MenuItems/AddChannelMembers', () => {
|
||||||
|
const channel = TestHelper.getChannelMock({header: 'Test Header'});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(modalActions, 'openModal');
|
||||||
|
|
||||||
|
// Mock useDispatch to return our custom dispatch function
|
||||||
|
jest.spyOn(require('react-redux'), 'useDispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<AddChannelMembers
|
||||||
|
channel={channel}
|
||||||
|
/>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Add Members');
|
||||||
|
expect(menuItem).toBeInTheDocument(); // Check if text "Add Members" renders
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dispatches openModal action on click', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<AddChannelMembers
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Add Members');
|
||||||
|
expect(menuItem).toBeInTheDocument(); // Check if text "Add Members" renders
|
||||||
|
fireEvent.click(menuItem); // Simulate click on the menu item
|
||||||
|
|
||||||
|
expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledWith({
|
||||||
|
modalId: ModalIdentifiers.CHANNEL_INVITE,
|
||||||
|
dialogType: ChannelInviteModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import {AccountPlusOutlineIcon} from '@mattermost/compass-icons/components';
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {openModal} from 'actions/views/modals';
|
||||||
|
|
||||||
|
import ChannelInviteModal from 'components/channel_invite_modal';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
channel: Channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddChannelMembers = ({channel}: Props): JSX.Element => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleAddMembers = () => {
|
||||||
|
dispatch(openModal({
|
||||||
|
modalId: ModalIdentifiers.CHANNEL_INVITE,
|
||||||
|
dialogType: ChannelInviteModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
id='channelAddMembers'
|
||||||
|
leadingElement={<AccountPlusOutlineIcon size='18px'/>}
|
||||||
|
onClick={handleAddMembers}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='navbar.addMembers'
|
||||||
|
defaultMessage='Add Members'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(AddChannelMembers);
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import * as modalActions from 'actions/views/modals';
|
||||||
|
|
||||||
|
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||||
|
import MoreDirectChannels from 'components/more_direct_channels';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
|
import AddGroupMembers from './add_group_members';
|
||||||
|
|
||||||
|
describe('components/ChannelHeaderMenu/MenuItems/AddGroupMembers', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(modalActions, 'openModal');
|
||||||
|
|
||||||
|
// Mock useDispatch to return our custom dispatch function
|
||||||
|
jest.spyOn(require('react-redux'), 'useDispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<AddGroupMembers/>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Add Members');
|
||||||
|
expect(menuItem).toBeInTheDocument(); // Check if text "Add Members" renders
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dispatches openModal action on click', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<AddGroupMembers/>
|
||||||
|
</WithTestMenuContext>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Add Members');
|
||||||
|
expect(menuItem).toBeInTheDocument(); // Check if text "Add Members" renders
|
||||||
|
fireEvent.click(menuItem); // Simulate click on the menu item
|
||||||
|
|
||||||
|
expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledWith({
|
||||||
|
modalId: ModalIdentifiers.CREATE_DM_CHANNEL,
|
||||||
|
dialogType: MoreDirectChannels,
|
||||||
|
dialogProps: {
|
||||||
|
focusOriginElement: 'channelHeaderDropdownButton',
|
||||||
|
isExistingChannel: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import {AccountMultipleOutlineIcon} from '@mattermost/compass-icons/components';
|
||||||
|
|
||||||
|
import {openModal} from 'actions/views/modals';
|
||||||
|
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
import MoreDirectChannels from 'components/more_direct_channels';
|
||||||
|
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
|
const AddGroupMembers = (): JSX.Element => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleAddGroupMembers = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.CREATE_DM_CHANNEL,
|
||||||
|
dialogType: MoreDirectChannels,
|
||||||
|
dialogProps: {isExistingChannel: true, focusOriginElement: 'channelHeaderDropdownButton'},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
id='channelAddMembers'
|
||||||
|
leadingElement={<AccountMultipleOutlineIcon size='18px'/>}
|
||||||
|
onClick={handleAddGroupMembers}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='navbar.addMembers'
|
||||||
|
defaultMessage='Add Members'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(AddGroupMembers);
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import * as modalActions from 'actions/views/modals';
|
||||||
|
import LocalStorageStore from 'stores/local_storage_store';
|
||||||
|
|
||||||
|
import DeleteChannelModal from 'components/delete_channel_modal';
|
||||||
|
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import ArchiveChannel from './archive_channel';
|
||||||
|
|
||||||
|
describe('components/ChannelHeaderMenu/MenuItems/ArchiveChannel', () => {
|
||||||
|
const initialState = {
|
||||||
|
entities: {
|
||||||
|
channels: {
|
||||||
|
currentChannelId: 'current_channel_id',
|
||||||
|
channels: {
|
||||||
|
current_channel_id: TestHelper.getChannelMock({
|
||||||
|
id: 'current_channel_id',
|
||||||
|
name: 'default-name',
|
||||||
|
display_name: 'Default',
|
||||||
|
delete_at: 0,
|
||||||
|
type: 'O',
|
||||||
|
team_id: 'team_id',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
currentTeamId: 'team-id',
|
||||||
|
teams: {
|
||||||
|
'team-id': {
|
||||||
|
id: 'team_id',
|
||||||
|
name: 'team-1',
|
||||||
|
display_name: 'Team 1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
myMembers: {
|
||||||
|
'team-id': {roles: 'team_role'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
currentUserId: 'current_user_id',
|
||||||
|
profiles: {
|
||||||
|
current_user_id: {
|
||||||
|
locale: 'en',
|
||||||
|
roles: 'system_role'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
LocalStorageStore.setPenultimateChannelName('current_user_id', 'team-id', 'current_channel_id');
|
||||||
|
|
||||||
|
const channel = TestHelper.getChannelMock({header: 'Test Header'});
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(modalActions, 'openModal');
|
||||||
|
|
||||||
|
// Mock useDispatch to return our custom dispatch function
|
||||||
|
jest.spyOn(require('react-redux'), 'useDispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<ArchiveChannel channel={channel}/>, initialState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Archive Channel');
|
||||||
|
expect(menuItem).toBeInTheDocument(); // Check if text "Add Members" renders
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dispatches openModal action on click with default channel', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<ArchiveChannel channel={channel}/>
|
||||||
|
</WithTestMenuContext>, initialState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Archive Channel');
|
||||||
|
expect(menuItem).toBeInTheDocument(); // Check if text "Add Members" renders
|
||||||
|
fireEvent.click(menuItem); // Simulate click on the menu item
|
||||||
|
|
||||||
|
expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledWith({
|
||||||
|
modalId: ModalIdentifiers.DELETE_CHANNEL,
|
||||||
|
dialogType: DeleteChannelModal,
|
||||||
|
dialogProps: {
|
||||||
|
channel,
|
||||||
|
penultimateViewedChannelName: 'current_channel_id',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {memo} from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import {useDispatch, useSelector} from 'react-redux';
|
||||||
|
|
||||||
|
import {ArchiveOutlineIcon} from '@mattermost/compass-icons/components';
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {getRedirectChannelNameForCurrentTeam} from 'mattermost-redux/selectors/entities/channels';
|
||||||
|
|
||||||
|
import {openModal} from 'actions/views/modals';
|
||||||
|
import {getPenultimateViewedChannelName} from 'selectors/local_storage';
|
||||||
|
|
||||||
|
import DeleteChannelModal from 'components/delete_channel_modal';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
channel: Channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArchiveChannel = ({
|
||||||
|
channel,
|
||||||
|
}: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const redirectChannelName = useSelector(getRedirectChannelNameForCurrentTeam);
|
||||||
|
const penultimateViewedChannelName = useSelector(getPenultimateViewedChannelName) || redirectChannelName;
|
||||||
|
|
||||||
|
const handleArchiveChannel = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.DELETE_CHANNEL,
|
||||||
|
dialogType: DeleteChannelModal,
|
||||||
|
dialogProps: {
|
||||||
|
channel,
|
||||||
|
penultimateViewedChannelName,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
id='channelArchiveChannel'
|
||||||
|
leadingElement={<ArchiveOutlineIcon size={18}/>}
|
||||||
|
onClick={handleArchiveChannel}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.delete'
|
||||||
|
defaultMessage='Archive Channel'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
isDestructive={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ArchiveChannel);
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
// Purpose of this file to exists is only required until channel header dropdown is migrated to new menus
|
||||||
|
import React, {memo} from 'react';
|
||||||
|
import {FormattedMessage, useIntl} from 'react-intl';
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChevronRightIcon,
|
||||||
|
LinkVariantIcon,
|
||||||
|
PaperclipIcon,
|
||||||
|
BookmarkOutlineIcon,
|
||||||
|
} from '@mattermost/compass-icons/components';
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {getChannelBookmarks} from 'mattermost-redux/selectors/entities/channel_bookmarks';
|
||||||
|
|
||||||
|
import {useBookmarkAddActions} from 'components/channel_bookmarks/channel_bookmarks_menu';
|
||||||
|
import {MAX_BOOKMARKS_PER_CHANNEL, useCanUploadFiles, useChannelBookmarkPermission} from 'components/channel_bookmarks/utils';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
channel: Channel;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChannelBookmarksSubmenu = (props: Props) => {
|
||||||
|
const {formatMessage} = useIntl();
|
||||||
|
|
||||||
|
const {handleCreateLink, handleCreateFile} = useBookmarkAddActions(props.channel.id);
|
||||||
|
const canAdd = useChannelBookmarkPermission(props.channel.id, 'add');
|
||||||
|
const canUploadFiles = useCanUploadFiles();
|
||||||
|
|
||||||
|
useSelector((state: GlobalState) => {
|
||||||
|
const bookmarks = getChannelBookmarks(state, props.channel.id);
|
||||||
|
return bookmarks && Object.keys(bookmarks).length >= MAX_BOOKMARKS_PER_CHANNEL;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!canAdd) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Menu.SubMenu
|
||||||
|
id={`channel-menu-${props.channel.id}-bookmarks`}
|
||||||
|
leadingElement={<BookmarkOutlineIcon size={18}/>}
|
||||||
|
labels={(
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_menu.bookmarks'
|
||||||
|
defaultMessage='Bookmarks Bar'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
trailingElements={(
|
||||||
|
<ChevronRightIcon size={16}/>
|
||||||
|
)}
|
||||||
|
menuId={`channel-menu-${props.channel.id}-menu`}
|
||||||
|
menuAriaLabel={formatMessage({id: 'channel_menu.bookmarks', defaultMessage: 'Bookmarks Bar'})}
|
||||||
|
>
|
||||||
|
<Menu.Item
|
||||||
|
id={`channel-menu-${props.channel.id}-bookmarks-link`}
|
||||||
|
leadingElement={<LinkVariantIcon size={18}/>}
|
||||||
|
labels={(
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_menu.bookmarks.addLink'
|
||||||
|
defaultMessage='Add a link'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
onClick={() => handleCreateLink()}
|
||||||
|
/>
|
||||||
|
{canUploadFiles && (
|
||||||
|
<Menu.Item
|
||||||
|
id={`channel-menu-${props.channel.id}-bookmarks-file`}
|
||||||
|
leadingElement={<PaperclipIcon size={18}/>}
|
||||||
|
labels={(
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_menu.bookmarks.addFile'
|
||||||
|
defaultMessage='Attach a file'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
onClick={() => handleCreateFile()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Menu.SubMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ChannelBookmarksSubmenu);
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {memo} from 'react';
|
||||||
|
import {FormattedMessage, useIntl} from 'react-intl';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChevronRightIcon,
|
||||||
|
CogOutlineIcon,
|
||||||
|
} from '@mattermost/compass-icons/components';
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {Permissions} from 'mattermost-redux/constants';
|
||||||
|
|
||||||
|
import {openModal} from 'actions/views/modals';
|
||||||
|
|
||||||
|
import ConvertChannelModal from 'components/convert_channel_modal';
|
||||||
|
import EditChannelHeaderModal from 'components/edit_channel_header_modal';
|
||||||
|
import EditChannelPurposeModal from 'components/edit_channel_purpose_modal';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate';
|
||||||
|
import RenameChannelModal from 'components/rename_channel_modal';
|
||||||
|
|
||||||
|
import {Constants, ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
channel: Channel;
|
||||||
|
isReadonly: boolean;
|
||||||
|
isDefault: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChannelSettingsSubmenu = ({channel, isReadonly, isDefault}: Props): JSX.Element => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const {formatMessage} = useIntl();
|
||||||
|
const channelPropertiesPermission = channel.type === Constants.PRIVATE_CHANNEL ? Permissions.MANAGE_PRIVATE_CHANNEL_PROPERTIES : Permissions.MANAGE_PUBLIC_CHANNEL_PROPERTIES;
|
||||||
|
const handleRenameChannel = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.RENAME_CHANNEL,
|
||||||
|
dialogType: RenameChannelModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditHeader = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.EDIT_CHANNEL_HEADER,
|
||||||
|
dialogType: EditChannelHeaderModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditPurpose = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.EDIT_CHANNEL_PURPOSE,
|
||||||
|
dialogType: EditChannelPurposeModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConvertToPrivate = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.CONVERT_CHANNEL,
|
||||||
|
dialogType: ConvertChannelModal,
|
||||||
|
dialogProps: {
|
||||||
|
channelId: channel.id,
|
||||||
|
channelDisplayName: channel.display_name,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu.SubMenu
|
||||||
|
id={'channelSettings'}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channelSettings'
|
||||||
|
defaultMessage='Channel Settings'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
leadingElement={<CogOutlineIcon size={18}/>}
|
||||||
|
trailingElements={<ChevronRightIcon size={16}/>}
|
||||||
|
menuId={'channelSettings-menu'}
|
||||||
|
menuAriaLabel={formatMessage({id: 'channelSettings', defaultMessage: 'Channel Settings'})}
|
||||||
|
>
|
||||||
|
{!isReadonly && (
|
||||||
|
<Menu.Item
|
||||||
|
id='channelRename'
|
||||||
|
onClick={handleRenameChannel}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.rename'
|
||||||
|
defaultMessage='Rename Channel'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isReadonly && (
|
||||||
|
<ChannelPermissionGate
|
||||||
|
channelId={channel.id}
|
||||||
|
teamId={channel.team_id}
|
||||||
|
permissions={[channelPropertiesPermission]}
|
||||||
|
>
|
||||||
|
<Menu.Item
|
||||||
|
id='channelEditHeader'
|
||||||
|
onClick={handleEditHeader}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.setHeader'
|
||||||
|
defaultMessage='Edit Channel Header'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ChannelPermissionGate>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isReadonly && (
|
||||||
|
<ChannelPermissionGate
|
||||||
|
channelId={channel.id}
|
||||||
|
teamId={channel.team_id}
|
||||||
|
permissions={[channelPropertiesPermission]}
|
||||||
|
>
|
||||||
|
<Menu.Item
|
||||||
|
id='channelEditPurpose'
|
||||||
|
onClick={handleEditPurpose}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.setPurpose'
|
||||||
|
defaultMessage='Edit Channel Purpose'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ChannelPermissionGate>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isDefault && channel.type === Constants.OPEN_CHANNEL && (
|
||||||
|
<ChannelPermissionGate
|
||||||
|
channelId={channel.id}
|
||||||
|
teamId={channel.team_id}
|
||||||
|
permissions={[Permissions.CONVERT_PUBLIC_CHANNEL_TO_PRIVATE]}
|
||||||
|
>
|
||||||
|
<Menu.Item
|
||||||
|
id='channelConvertToPrivate'
|
||||||
|
onClick={handleConvertToPrivate}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.convert'
|
||||||
|
defaultMessage='Convert to Private Channel'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ChannelPermissionGate>
|
||||||
|
)}
|
||||||
|
</Menu.SubMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ChannelSettingsSubmenu);
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import * as channelActions from 'actions/views/channel';
|
||||||
|
|
||||||
|
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
|
||||||
|
|
||||||
|
import CloseChannel from './close_channel';
|
||||||
|
|
||||||
|
describe('components/ChannelHeaderMenu/MenuItems/CloseChannel', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(channelActions, 'goToLastViewedChannel');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly, handle click event', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<CloseChannel/>
|
||||||
|
</WithTestMenuContext>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Close Channel');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItem); // Simulate click on the menu item
|
||||||
|
expect(channelActions.goToLastViewedChannel).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
|
import {goToLastViewedChannel} from 'actions/views/channel';
|
||||||
|
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
interface Props extends Menu.FirstMenuItemProps {}
|
||||||
|
|
||||||
|
const CloseChannel = ({...rest}: Props): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
onClick={goToLastViewedChannel}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='center_panel.archived.closeChannel'
|
||||||
|
defaultMessage='Close Channel'
|
||||||
|
/>}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default React.memo(CloseChannel);
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import type {ChannelType} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import * as preferences from 'mattermost-redux/actions/preferences';
|
||||||
|
|
||||||
|
import * as channelActions from 'actions/views/channel';
|
||||||
|
|
||||||
|
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
|
||||||
|
import {Constants} from 'utils/constants';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import CloseMessage from './close_message';
|
||||||
|
|
||||||
|
describe('components/ChannelHeaderMenu/MenuItems/CloseMessage', () => {
|
||||||
|
const initialState = {
|
||||||
|
entities: {
|
||||||
|
channels: {
|
||||||
|
currentChannelId: 'current_channel_id',
|
||||||
|
channels: {
|
||||||
|
current_channel_id: TestHelper.getChannelMock({
|
||||||
|
id: 'current_channel_id',
|
||||||
|
name: 'default-name',
|
||||||
|
display_name: 'Default',
|
||||||
|
delete_at: 0,
|
||||||
|
type: 'O',
|
||||||
|
team_id: 'team_id',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
currentTeamId: 'team-id',
|
||||||
|
teams: {
|
||||||
|
'team-id': {
|
||||||
|
id: 'team_id',
|
||||||
|
name: 'team-1',
|
||||||
|
display_name: 'Team 1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
myMembers: {
|
||||||
|
'team-id': {roles: 'team_role'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
currentUserId: 'current_user_id',
|
||||||
|
profiles: {
|
||||||
|
current_user_id: {
|
||||||
|
locale: 'en',
|
||||||
|
roles: 'system_role',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupChannel = TestHelper.getChannelMock({
|
||||||
|
id: 'channel_id',
|
||||||
|
name: 'groupChannel',
|
||||||
|
type: Constants.GM_CHANNEL as ChannelType,
|
||||||
|
});
|
||||||
|
|
||||||
|
const directChannel = TestHelper.getChannelMock({
|
||||||
|
id: 'channel_id',
|
||||||
|
type: Constants.DM_CHANNEL as ChannelType,
|
||||||
|
teammate_id: 'teammate-id',
|
||||||
|
name: 'directChannel',
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(channelActions, 'leaveDirectChannel');
|
||||||
|
jest.spyOn(preferences, 'savePreferences');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly for group channel', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<CloseMessage
|
||||||
|
currentUserID='current_user_id'
|
||||||
|
channel={groupChannel}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, initialState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Close Group Message');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItem); // Simulate click on the menu item
|
||||||
|
expect(channelActions.leaveDirectChannel).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(channelActions.leaveDirectChannel).toHaveBeenCalledWith(groupChannel.name);
|
||||||
|
|
||||||
|
expect(preferences.savePreferences).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(preferences.savePreferences).toHaveBeenCalledWith(
|
||||||
|
'current_user_id',
|
||||||
|
[{user_id: 'current_user_id', category: Constants.Preferences.CATEGORY_GROUP_CHANNEL_SHOW, name: groupChannel.id, value: 'false'}],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly for direct channel', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<CloseMessage
|
||||||
|
currentUserID='current_user_id'
|
||||||
|
channel={directChannel}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, initialState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Close Direct Message');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItem); // Simulate click on the menu item
|
||||||
|
expect(channelActions.leaveDirectChannel).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(channelActions.leaveDirectChannel).toHaveBeenCalledWith(directChannel.name);
|
||||||
|
|
||||||
|
expect(preferences.savePreferences).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(preferences.savePreferences).toHaveBeenCalledWith(
|
||||||
|
'current_user_id',
|
||||||
|
[{user_id: 'current_user_id', category: Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, name: directChannel.teammate_id, value: 'false'}],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import {useDispatch, useSelector} from 'react-redux';
|
||||||
|
|
||||||
|
import {CloseIcon} from '@mattermost/compass-icons/components';
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||||
|
import {getRedirectChannelNameForCurrentTeam} from 'mattermost-redux/selectors/entities/channels';
|
||||||
|
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||||
|
|
||||||
|
import {leaveDirectChannel} from 'actions/views/channel';
|
||||||
|
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
import {getHistory} from 'utils/browser_history';
|
||||||
|
import {Constants} from 'utils/constants';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
currentUserID: string;
|
||||||
|
channel: Channel;
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CloseMessage(props: Props) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const currentTeam = useSelector(getCurrentTeam);
|
||||||
|
const redirectChannel = useSelector(getRedirectChannelNameForCurrentTeam);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
const {
|
||||||
|
channel,
|
||||||
|
currentUserID,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
let name: string;
|
||||||
|
let category;
|
||||||
|
if (channel.type === Constants.DM_CHANNEL) {
|
||||||
|
category = Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW;
|
||||||
|
name = channel.teammate_id!;
|
||||||
|
} else {
|
||||||
|
category = Constants.Preferences.CATEGORY_GROUP_CHANNEL_SHOW;
|
||||||
|
name = channel.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(leaveDirectChannel(channel.name));
|
||||||
|
dispatch(savePreferences(currentUserID, [{user_id: currentUserID, category, name, value: 'false'}]));
|
||||||
|
|
||||||
|
if (currentTeam) {
|
||||||
|
getHistory().push(`/${currentTeam.name}/channels/${redirectChannel}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const {id, channel} = props;
|
||||||
|
|
||||||
|
// DM
|
||||||
|
let text = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='center_panel.direct.closeDirectMessage'
|
||||||
|
defaultMessage='Close Direct Message'
|
||||||
|
/>);
|
||||||
|
if (channel.type === Constants.GM_CHANNEL) {
|
||||||
|
text = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='center_panel.direct.closeGroupMessage'
|
||||||
|
defaultMessage='Close Group Message'
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
id={id}
|
||||||
|
leadingElement={<CloseIcon size='18px'/>}
|
||||||
|
onClick={handleClose}
|
||||||
|
labels={text}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import * as modalActions from 'actions/views/modals';
|
||||||
|
|
||||||
|
import ConvertGmToChannelModal from 'components/convert_gm_to_channel_modal';
|
||||||
|
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import ConvertGMtoPrivate from './convert_gm_to_private';
|
||||||
|
|
||||||
|
describe('components/ChannelHeaderMenu/MenuItems/ConvertGMtoPrivate', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(modalActions, 'openModal');
|
||||||
|
|
||||||
|
// Mock useDispatch to return our custom dispatch function
|
||||||
|
jest.spyOn(require('react-redux'), 'useDispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
const channel = TestHelper.getChannelMock();
|
||||||
|
|
||||||
|
test('renders the component correctly, handle click event', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<ConvertGMtoPrivate channel={channel}/>
|
||||||
|
</WithTestMenuContext>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Convert to Private Channel');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItem); // Simulate click on the menu item
|
||||||
|
expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledWith({
|
||||||
|
modalId: ModalIdentifiers.CONVERT_GM_TO_CHANNEL,
|
||||||
|
dialogType: ConvertGmToChannelModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {openModal} from 'actions/views/modals';
|
||||||
|
|
||||||
|
import ConvertGmToChannelModal from 'components/convert_gm_to_channel_modal';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
channel: Channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConvertGMtoPrivate = ({channel}: Props): JSX.Element => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const handleConvertToPrivate = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.CONVERT_GM_TO_CHANNEL,
|
||||||
|
dialogType: ConvertGmToChannelModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
id='convertGMPrivateChannel'
|
||||||
|
onClick={handleConvertToPrivate}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='sidebar_left.sidebar_channel_menu_convert_to_channel'
|
||||||
|
defaultMessage='Convert to Private Channel'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(ConvertGMtoPrivate);
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import * as modalActions from 'actions/views/modals';
|
||||||
|
|
||||||
|
import ConvertChannelModal from 'components/convert_channel_modal';
|
||||||
|
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import ConvertPublictoPrivate from './convert_public_to_private';
|
||||||
|
|
||||||
|
describe('components/ChannelHeaderMenu/MenuItems/ConvertPublicToPrivate', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(modalActions, 'openModal');
|
||||||
|
|
||||||
|
// Mock useDispatch to return our custom dispatch function
|
||||||
|
jest.spyOn(require('react-redux'), 'useDispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
const channel = TestHelper.getChannelMock();
|
||||||
|
|
||||||
|
test('renders the component correctly, handle click event', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<ConvertPublictoPrivate channel={channel}/>
|
||||||
|
</WithTestMenuContext>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Convert to Private Channel');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItem); // Simulate click on the menu item
|
||||||
|
expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledWith({
|
||||||
|
modalId: ModalIdentifiers.CONVERT_CHANNEL,
|
||||||
|
dialogType: ConvertChannelModal,
|
||||||
|
dialogProps: {
|
||||||
|
channelId: channel.id,
|
||||||
|
channelDisplayName: channel.display_name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {openModal} from 'actions/views/modals';
|
||||||
|
|
||||||
|
import ConvertChannelModal from 'components/convert_channel_modal';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
channel: Channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConvertPublictoPrivate = ({channel}: Props): JSX.Element => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const handleConvertToPrivate = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.CONVERT_CHANNEL,
|
||||||
|
dialogType: ConvertChannelModal,
|
||||||
|
dialogProps: {
|
||||||
|
channelId: channel.id,
|
||||||
|
channelDisplayName: channel.display_name,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
id='channelConvertToPrivate'
|
||||||
|
onClick={handleConvertToPrivate}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.convert'
|
||||||
|
defaultMessage='Convert to Private Channel'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(ConvertPublictoPrivate);
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import * as modalActions from 'actions/views/modals';
|
||||||
|
|
||||||
|
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||||
|
import RenameChannelModal from 'components/rename_channel_modal';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import EditChannelSettings from './edit_channel_settings';
|
||||||
|
|
||||||
|
describe('components/ChannelHeaderMenu/MenuItems/EditChannelSettings', () => {
|
||||||
|
const channel = TestHelper.getChannelMock();
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(modalActions, 'openModal');
|
||||||
|
jest.spyOn(require('react-redux'), 'useDispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly, handle click event', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<EditChannelSettings
|
||||||
|
channel={channel}
|
||||||
|
isReadonly={false}
|
||||||
|
isDefault={false}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Rename Channel');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItem); // Simulate click on the menu item
|
||||||
|
expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledWith({
|
||||||
|
modalId: ModalIdentifiers.RENAME_CHANNEL,
|
||||||
|
dialogType: RenameChannelModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {Permissions} from 'mattermost-redux/constants';
|
||||||
|
|
||||||
|
import {openModal} from 'actions/views/modals';
|
||||||
|
|
||||||
|
import EditChannelHeaderModal from 'components/edit_channel_header_modal';
|
||||||
|
import EditChannelPurposeModal from 'components/edit_channel_purpose_modal';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate';
|
||||||
|
import RenameChannelModal from 'components/rename_channel_modal';
|
||||||
|
|
||||||
|
import {Constants, ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
|
import MenuItemConvertToPrivate from './convert_public_to_private';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
channel: Channel;
|
||||||
|
isReadonly: boolean;
|
||||||
|
isDefault: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditChannelSettings = ({channel, isReadonly, isDefault}: Props): JSX.Element => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const channelPropertiesPermission = channel.type === Constants.PRIVATE_CHANNEL ? Permissions.MANAGE_PRIVATE_CHANNEL_PROPERTIES : Permissions.MANAGE_PUBLIC_CHANNEL_PROPERTIES;
|
||||||
|
|
||||||
|
const handleRenameChannel = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.RENAME_CHANNEL,
|
||||||
|
dialogType: RenameChannelModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditHeader = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.EDIT_CHANNEL_HEADER,
|
||||||
|
dialogType: EditChannelHeaderModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditPurpose = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.EDIT_CHANNEL_PURPOSE,
|
||||||
|
dialogType: EditChannelPurposeModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!isReadonly && (
|
||||||
|
<>
|
||||||
|
<Menu.Item
|
||||||
|
id='channelRename'
|
||||||
|
onClick={handleRenameChannel}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.rename'
|
||||||
|
defaultMessage='Rename Channel'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ChannelPermissionGate
|
||||||
|
channelId={channel.id}
|
||||||
|
teamId={channel.team_id}
|
||||||
|
permissions={[channelPropertiesPermission]}
|
||||||
|
>
|
||||||
|
<Menu.Item
|
||||||
|
id='channelEditHeader'
|
||||||
|
onClick={handleEditHeader}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.setHeader'
|
||||||
|
defaultMessage='Edit Channel Header'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Menu.Item
|
||||||
|
id='channelEditPurpose'
|
||||||
|
onClick={handleEditPurpose}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.setPurpose'
|
||||||
|
defaultMessage='Edit Channel Purpose'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ChannelPermissionGate>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isDefault && channel.type === Constants.OPEN_CHANNEL && (
|
||||||
|
<ChannelPermissionGate
|
||||||
|
channelId={channel.id}
|
||||||
|
teamId={channel.team_id}
|
||||||
|
permissions={[Permissions.CONVERT_PUBLIC_CHANNEL_TO_PRIVATE]}
|
||||||
|
>
|
||||||
|
<MenuItemConvertToPrivate
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
</ChannelPermissionGate>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(EditChannelSettings);
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import * as modalActions from 'actions/views/modals';
|
||||||
|
|
||||||
|
import EditChannelHeaderModal from 'components/edit_channel_header_modal';
|
||||||
|
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import EditConversationHeader from './edit_conversation_header';
|
||||||
|
|
||||||
|
describe('components/ChannelHeaderMenu/MenuItems/EditConversationHeader', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(modalActions, 'openModal');
|
||||||
|
|
||||||
|
// Mock useDispatch to return our custom dispatch function
|
||||||
|
jest.spyOn(require('react-redux'), 'useDispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
const channel = TestHelper.getChannelMock();
|
||||||
|
|
||||||
|
test('renders the component correctly, handle click event', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<EditConversationHeader channel={channel}/>
|
||||||
|
</WithTestMenuContext>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Edit Header');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItem); // Simulate click on the menu item
|
||||||
|
expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledWith({
|
||||||
|
modalId: ModalIdentifiers.EDIT_CHANNEL_HEADER,
|
||||||
|
dialogType: EditChannelHeaderModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {openModal} from 'actions/views/modals';
|
||||||
|
|
||||||
|
import EditChannelHeaderModal from 'components/edit_channel_header_modal';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
channel: Channel;
|
||||||
|
leadingElement?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditConversationHeader = ({channel, leadingElement}: Props): JSX.Element => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const handleEditHeader = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.EDIT_CHANNEL_HEADER,
|
||||||
|
dialogType: EditChannelHeaderModal,
|
||||||
|
dialogProps: {channel},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
id='channelEditHeader'
|
||||||
|
leadingElement={leadingElement}
|
||||||
|
onClick={handleEditHeader}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.setConversationHeader'
|
||||||
|
defaultMessage='Edit Header'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(EditConversationHeader);
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import * as modalActions from 'actions/views/modals';
|
||||||
|
|
||||||
|
import AddGroupsToChannelModal from 'components/add_groups_to_channel_modal';
|
||||||
|
import ChannelGroupsManageModal from 'components/channel_groups_manage_modal';
|
||||||
|
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import Groups from './groups';
|
||||||
|
|
||||||
|
describe('components/ChannelHeaderMenu/MenuItems/Groups', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(modalActions, 'openModal');
|
||||||
|
|
||||||
|
// Mock useDispatch to return our custom dispatch function
|
||||||
|
jest.spyOn(require('react-redux'), 'useDispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
const channel = TestHelper.getChannelMock();
|
||||||
|
|
||||||
|
test('renders the component correctly, handle click event for add groups', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<Groups channel={channel}/>
|
||||||
|
</WithTestMenuContext>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Add Groups');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItem); // Simulate click on the menu item
|
||||||
|
expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledWith({
|
||||||
|
modalId: ModalIdentifiers.ADD_GROUPS_TO_CHANNEL,
|
||||||
|
dialogType: AddGroupsToChannelModal,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly, handle click event for manage groups', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<Groups channel={channel}/>
|
||||||
|
</WithTestMenuContext>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItemMG = screen.getByText('Manage Groups');
|
||||||
|
expect(menuItemMG).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItemMG); // Simulate click on the menu item
|
||||||
|
expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledWith({
|
||||||
|
modalId: ModalIdentifiers.MANAGE_CHANNEL_GROUPS,
|
||||||
|
dialogType: ChannelGroupsManageModal,
|
||||||
|
dialogProps: {channelID: channel.id},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import {AccountMultipleOutlineIcon, AccountMultiplePlusOutlineIcon} from '@mattermost/compass-icons/components';
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {openModal} from 'actions/views/modals';
|
||||||
|
|
||||||
|
import AddGroupsToChannelModal from 'components/add_groups_to_channel_modal';
|
||||||
|
import ChannelGroupsManageModal from 'components/channel_groups_manage_modal';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
|
interface Props extends Menu.FirstMenuItemProps {
|
||||||
|
channel: Channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Groups = ({channel, ...rest}: Props): JSX.Element => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const handleAddGroups = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.ADD_GROUPS_TO_CHANNEL,
|
||||||
|
dialogType: AddGroupsToChannelModal,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleManageGroups = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.MANAGE_CHANNEL_GROUPS,
|
||||||
|
dialogType: ChannelGroupsManageModal,
|
||||||
|
dialogProps: {channelID: channel.id},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Menu.Item
|
||||||
|
id='channelAddGroups'
|
||||||
|
leadingElement={<AccountMultiplePlusOutlineIcon size='18px'/>}
|
||||||
|
onClick={handleAddGroups}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='navbar.addGroups'
|
||||||
|
defaultMessage='Add Groups'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
<Menu.Item
|
||||||
|
id='channelManageGroups'
|
||||||
|
leadingElement={<AccountMultipleOutlineIcon size='18px'/>}
|
||||||
|
onClick={handleManageGroups}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='navbar_dropdown.manageGroups'
|
||||||
|
defaultMessage='Manage Groups'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(Groups);
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import * as channelActions from 'actions/views/channel';
|
||||||
|
import * as modalActions from 'actions/views/modals';
|
||||||
|
|
||||||
|
import LeaveChannelModal from 'components/leave_channel_modal';
|
||||||
|
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
|
||||||
|
import {ModalIdentifiers} from 'utils/constants';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import LeaveChannel from './leave_channel';
|
||||||
|
|
||||||
|
describe('components/ChannelHeaderMenu/MenuItems/LeaveChannelTest', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(modalActions, 'openModal');
|
||||||
|
jest.spyOn(channelActions, 'leaveChannel');
|
||||||
|
|
||||||
|
// Mock useDispatch to return our custom dispatch function
|
||||||
|
jest.spyOn(require('react-redux'), 'useDispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly, handle click event correctly for Public Channel', () => {
|
||||||
|
const channel = TestHelper.getChannelMock();
|
||||||
|
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<LeaveChannel channel={channel}/>
|
||||||
|
</WithTestMenuContext>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('Leave Channel');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItem); // Simulate click on the menu item
|
||||||
|
expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(channelActions.leaveChannel).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(channelActions.leaveChannel).toHaveBeenCalledWith(channel.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly, handle click event for manage groups', () => {
|
||||||
|
const channel = TestHelper.getChannelMock({type: 'P'});
|
||||||
|
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<LeaveChannel channel={channel}/>
|
||||||
|
</WithTestMenuContext>, {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItemMG = screen.getByText('Leave Channel');
|
||||||
|
expect(menuItemMG).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItemMG); // Simulate click on the menu item
|
||||||
|
expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(modalActions.openModal).toHaveBeenCalledWith({
|
||||||
|
modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL,
|
||||||
|
dialogType: LeaveChannelModal,
|
||||||
|
dialogProps: {
|
||||||
|
channel,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {memo} from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import {LogoutVariantIcon} from '@mattermost/compass-icons/components';
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {leaveChannel} from 'actions/views/channel';
|
||||||
|
import {openModal} from 'actions/views/modals';
|
||||||
|
|
||||||
|
import LeaveChannelModal from 'components/leave_channel_modal';
|
||||||
|
import * as Menu from 'components/menu';
|
||||||
|
|
||||||
|
import {Constants, ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
|
// import type {PropsFromRedux} from './index';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
channel: Channel;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LeaveChannel = ({
|
||||||
|
channel,
|
||||||
|
id,
|
||||||
|
}: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const handleLeave = () => {
|
||||||
|
if (channel.type === Constants.PRIVATE_CHANNEL) {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL,
|
||||||
|
dialogType: LeaveChannelModal,
|
||||||
|
dialogProps: {
|
||||||
|
channel,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
dispatch(leaveChannel(channel.id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu.Item
|
||||||
|
id={id}
|
||||||
|
leadingElement={<LogoutVariantIcon size='18px'/>}
|
||||||
|
onClick={handleLeave}
|
||||||
|
labels={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_header.leave'
|
||||||
|
defaultMessage='Leave Channel'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
isDestructive={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(LeaveChannel);
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
import React from 'react';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import {AppBindingLocations, AppCallResponseTypes} from 'mattermost-redux/constants/apps';
|
||||||
|
|
||||||
|
import * as appsActions from 'actions/apps';
|
||||||
|
import * as channelActions from 'actions/views/channel';
|
||||||
|
import * as modalActions from 'actions/views/modals';
|
||||||
|
|
||||||
|
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, fireEvent, waitFor} from 'tests/react_testing_utils';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import MobileChannelHeaderPlugins from './mobile_channel_header_plugins';
|
||||||
|
|
||||||
|
describe('components/ChannelHeaderMenu/MenuItems/MobileChannelHeaderPlugins, with no extended components', () => {
|
||||||
|
jest.mock('actions/apps', () => ({
|
||||||
|
...jest.requireActual('actions/apps'),
|
||||||
|
handleBindingClick: jest.fn(),
|
||||||
|
}));
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(modalActions, 'openModal');
|
||||||
|
jest.spyOn(channelActions, 'leaveChannel');
|
||||||
|
|
||||||
|
// jest.spyOn(appsActions, 'handleBindingClick');
|
||||||
|
jest.spyOn(appsActions, 'openAppsModal');
|
||||||
|
jest.spyOn(appsActions, 'postEphemeralCallResponseForChannel');
|
||||||
|
|
||||||
|
// Mock useDispatch to return our custom dispatch function
|
||||||
|
jest.spyOn(require('react-redux'), 'useDispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const channel = TestHelper.getChannelMock();
|
||||||
|
const action = jest.fn();
|
||||||
|
const pluginState = {
|
||||||
|
plugins: {
|
||||||
|
components: {
|
||||||
|
MobileChannelHeaderButton: [
|
||||||
|
{
|
||||||
|
id: 'someid',
|
||||||
|
pluginId: 'pluginid',
|
||||||
|
icon: <i className='fa fa-anchor'/>,
|
||||||
|
action,
|
||||||
|
dropdownText: 'some dropdown text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const bindingState = {
|
||||||
|
entities: {
|
||||||
|
apps: {
|
||||||
|
main: {
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
app_id: 'appid',
|
||||||
|
location: AppBindingLocations.CHANNEL_HEADER_ICON,
|
||||||
|
icon: 'http://test.com/icon.png',
|
||||||
|
label: 'Label',
|
||||||
|
hint: 'Hint',
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
app_id: 'app1',
|
||||||
|
location: 'channel-header-1',
|
||||||
|
label: 'App 1 Channel Header',
|
||||||
|
form: {
|
||||||
|
submit: {
|
||||||
|
path: '/call/path',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
general: {
|
||||||
|
config: {
|
||||||
|
FeatureFlagAppsEnabled: 'true',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test('renders the component correctly', () => {
|
||||||
|
const {container} = renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<MobileChannelHeaderPlugins
|
||||||
|
channel={channel}
|
||||||
|
isDropdown={true}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, {},
|
||||||
|
);
|
||||||
|
expect(container.firstChild).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly, with one extended component, and handle click event', () => {
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<MobileChannelHeaderPlugins
|
||||||
|
channel={channel}
|
||||||
|
isDropdown={true}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, pluginState,
|
||||||
|
);
|
||||||
|
const menuItem = screen.getByText('some dropdown text');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
fireEvent.click(menuItem);
|
||||||
|
expect(action).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly, with two extended component', () => {
|
||||||
|
const testState = {
|
||||||
|
...pluginState,
|
||||||
|
plugins: {
|
||||||
|
...pluginState.plugins,
|
||||||
|
components: {
|
||||||
|
...pluginState.plugins.components,
|
||||||
|
MobileChannelHeaderButton: [
|
||||||
|
...pluginState.plugins.components.MobileChannelHeaderButton,
|
||||||
|
{
|
||||||
|
id: 'someid2',
|
||||||
|
pluginId: 'pluginid2',
|
||||||
|
icon: <i className='fa fa-anchor'/>,
|
||||||
|
action: jest.fn(),
|
||||||
|
dropdownText: 'some other dropdown text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<MobileChannelHeaderPlugins
|
||||||
|
channel={channel}
|
||||||
|
isDropdown={true}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, testState,
|
||||||
|
);
|
||||||
|
const menuItem = screen.getByText('some dropdown text');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
const menuItem2 = screen.getByText('some other dropdown text');
|
||||||
|
expect(menuItem2).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly, with two extended bindings', () => {
|
||||||
|
const testState = cloneDeep(bindingState);
|
||||||
|
testState.entities.apps.main.bindings[0].bindings.push({
|
||||||
|
app_id: 'app2',
|
||||||
|
location: 'channel-header-2',
|
||||||
|
label: 'App 2 Channel Header',
|
||||||
|
form: {
|
||||||
|
submit: {
|
||||||
|
path: '/call/path',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<MobileChannelHeaderPlugins
|
||||||
|
channel={channel}
|
||||||
|
isDropdown={true}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, testState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('App 1 Channel Header');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
const menuItem2 = screen.getByText('App 2 Channel Header');
|
||||||
|
expect(menuItem2).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Processes handleBinding, returns AppCallResponseTypes.OK', async () => {
|
||||||
|
jest.spyOn(appsActions, 'handleBindingClick').mockReturnValueOnce(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: {
|
||||||
|
type: AppCallResponseTypes.OK,
|
||||||
|
text: 'hello',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<MobileChannelHeaderPlugins
|
||||||
|
channel={channel}
|
||||||
|
isDropdown={true}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, bindingState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('App 1 Channel Header');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItem);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(appsActions.handleBindingClick).toHaveBeenCalledTimes(1);
|
||||||
|
expect(appsActions.postEphemeralCallResponseForChannel).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Processes handleBinding, returns AppCallResponseTypes.Form', async () => {
|
||||||
|
jest.spyOn(appsActions, 'handleBindingClick').mockReturnValueOnce(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: {
|
||||||
|
type: AppCallResponseTypes.FORM,
|
||||||
|
form: {
|
||||||
|
submit: {
|
||||||
|
path: '/call/path',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<MobileChannelHeaderPlugins
|
||||||
|
channel={channel}
|
||||||
|
isDropdown={true}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, bindingState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('App 1 Channel Header');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItem);
|
||||||
|
await waitFor(() => {
|
||||||
|
// expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(appsActions.handleBindingClick).toHaveBeenCalledTimes(1);
|
||||||
|
expect(appsActions.openAppsModal).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Processes handleBinding, returns Error', async () => {
|
||||||
|
jest.spyOn(appsActions, 'handleBindingClick').mockReturnValueOnce(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
error: {
|
||||||
|
type: AppCallResponseTypes.ERROR,
|
||||||
|
text: 'Error returned from method',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<MobileChannelHeaderPlugins
|
||||||
|
channel={channel}
|
||||||
|
isDropdown={true}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, bindingState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuItem = screen.getByText('App 1 Channel Header');
|
||||||
|
expect(menuItem).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(menuItem);
|
||||||
|
await waitFor(() => {
|
||||||
|
// expect(useDispatch).toHaveBeenCalledTimes(1); // Ensure dispatch was called
|
||||||
|
expect(appsActions.handleBindingClick).toHaveBeenCalledTimes(1);
|
||||||
|
expect(appsActions.postEphemeralCallResponseForChannel).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly, with one extended component, isDropDown false', () => {
|
||||||
|
const action = jest.fn();
|
||||||
|
const pluginState = {
|
||||||
|
plugins: {
|
||||||
|
components: {
|
||||||
|
MobileChannelHeaderButton: [
|
||||||
|
{
|
||||||
|
id: 'someid',
|
||||||
|
pluginId: 'pluginid',
|
||||||
|
icon: <i className='fa fa-anchor'/>,
|
||||||
|
action,
|
||||||
|
dropdownText: 'some dropdown text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<MobileChannelHeaderPlugins
|
||||||
|
channel={channel}
|
||||||
|
isDropdown={false}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, pluginState,
|
||||||
|
);
|
||||||
|
const button = screen.getByRole('button');
|
||||||
|
expect(button).toBeInTheDocument();
|
||||||
|
fireEvent.click(button);
|
||||||
|
expect(action).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the component correctly, with one extended appbinding, isDropDown false', async () => {
|
||||||
|
jest.spyOn(appsActions, 'handleBindingClick').mockReturnValueOnce(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: {
|
||||||
|
type: AppCallResponseTypes.OK,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<MobileChannelHeaderPlugins
|
||||||
|
channel={channel}
|
||||||
|
isDropdown={false}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, bindingState,
|
||||||
|
);
|
||||||
|
const button = screen.getByRole('button');
|
||||||
|
expect(button).toBeInTheDocument();
|
||||||
|
fireEvent.click(button);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(appsActions.handleBindingClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders noting if multiple appbindings or components isDropDown false', () => {
|
||||||
|
const pluginState = {
|
||||||
|
plugins: {
|
||||||
|
components: {
|
||||||
|
MobileChannelHeaderButton: [
|
||||||
|
{
|
||||||
|
id: 'someid',
|
||||||
|
pluginId: 'pluginid',
|
||||||
|
icon: <i className='fa fa-anchor'/>,
|
||||||
|
action: jest.fn(),
|
||||||
|
dropdownText: 'some dropdown text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const bothState = {
|
||||||
|
...bindingState,
|
||||||
|
...pluginState,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithContext(
|
||||||
|
<WithTestMenuContext>
|
||||||
|
<MobileChannelHeaderPlugins
|
||||||
|
channel={channel}
|
||||||
|
isDropdown={false}
|
||||||
|
/>
|
||||||
|
</WithTestMenuContext>, bothState,
|
||||||
|
);
|
||||||
|
const button = screen.queryByRole('button');
|
||||||
|
expect(button).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Ссылка в новой задаче
Block a user