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();
|
||||
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Add Members');
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// * Verify that the modal is open
|
||||
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;
|
||||
cy.get('#channelLeaveChannel').should('be.visible');
|
||||
|
||||
// * Rename menu option should be visible;
|
||||
cy.get('#channelRename').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');
|
||||
// * Channel Settings submenu should be visible;
|
||||
cy.get('#channelSettings').should('be.visible');
|
||||
|
||||
// * Archive channel menu option should be visible;
|
||||
cy.get('#channelArchiveChannel').should('be.visible');
|
||||
|
||||
// * Add members menu option should be visible;
|
||||
cy.get('#channelInviteMembers').should('be.visible');
|
||||
// * Members menu option should be visible;
|
||||
cy.get('#channelMembers').should('be.visible');
|
||||
|
||||
// * Notification preferences option should be visible;
|
||||
cy.get('#channelNotificationPreferences').should('be.visible');
|
||||
|
||||
// # Close the channel dropdown menu
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
// # Close the channel dropdown menu using keyboard escape
|
||||
cy.get('body').type('{esc}{esc}');
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// * Rename menu option should not be visible;
|
||||
cy.get('#channelRename').should('not.exist');
|
||||
|
||||
// * 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');
|
||||
// # click on channel drop-down menu
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// * Archive channel menu option should not be visible;
|
||||
cy.get('#channelArchiveChannel').should('not.exist');
|
||||
|
||||
// * Add members menu option should not be visible;
|
||||
cy.get('#channelInviteMembers').should('not.exist');
|
||||
// * Channel Settings submenu should be visible;
|
||||
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;
|
||||
cy.get('#channelNotificationPreferences').should('not.exist');
|
||||
|
||||
// # Close the channel dropdown menu
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
// # Close the channel dropdown menu using keyboard escape
|
||||
cy.get('body').type('{esc}{esc}');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,14 +28,11 @@ describe('Archive channel members spec', () => {
|
||||
// # click on channel drop-down menu
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// * View members menu option should not be visible;
|
||||
cy.get('#channelViewMembers').should('not.exist');
|
||||
|
||||
// * Manage members menu option should be visible;
|
||||
cy.get('#channelManageMembers').should('be.visible');
|
||||
// * Members menu option should be visible;
|
||||
cy.get('#channelMembers').should('be.visible');
|
||||
|
||||
// # Close the channel dropdown menu
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.get('body').type('{esc}{esc}');
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
@@ -43,11 +40,8 @@ describe('Archive channel members spec', () => {
|
||||
// # click on channel drop-down menu
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// * Manage members menu option should not be visible;
|
||||
cy.get('#channelManageMembers').should('not.exist');
|
||||
|
||||
// # 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
|
||||
cy.uiGetRHS().findByText('Manage').should('not.exist');
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('Channels', () => {
|
||||
cy.get('#channelHeaderTitle').should('contain', testChannel.display_name).click();
|
||||
|
||||
// * Verify that the menu is opened
|
||||
cy.get('.Menu__content').should('be.visible').within(() => {
|
||||
cy.get('#channelHeaderDropdownMenu').should('be.visible').within(() => {
|
||||
// # Archive the channel
|
||||
cy.findByText('Archive Channel').should('be.visible').click();
|
||||
});
|
||||
|
||||
@@ -30,9 +30,10 @@ describe('Channel Settings - Channel Header', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # 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
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||
cy.findByText('Edit Channel Header').click();
|
||||
|
||||
// # Type something in the header edit box
|
||||
|
||||
@@ -205,15 +205,12 @@ describe('Channel members RHS', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
cy.uiOpenChannelMenu('Manage Members');
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
|
||||
// * RHS Container should be open in edit mode
|
||||
cy.get('#rhsContainer').then((rhsContainer) => {
|
||||
cy.wrap(rhsContainer).findByText('Members').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
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
cy.uiOpenChannelMenu('View Members');
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
|
||||
// * RHS Container should be open in edit mode
|
||||
ensureChannelMembersRHSExists(testChannel);
|
||||
|
||||
@@ -38,7 +38,8 @@ describe('Channel Settings', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # 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();
|
||||
|
||||
// # Try to enter existing URL and save
|
||||
@@ -63,7 +64,7 @@ describe('Channel Settings', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Go to channel dropdown > Mute channel
|
||||
cy.get('#channelHeaderDropdownIcon').click();
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.get('#channelHeaderDropdownMenu').should('exist').
|
||||
findByText('Mute Channel').should('be.visible').click();
|
||||
|
||||
@@ -79,7 +80,7 @@ describe('Channel Settings', () => {
|
||||
get('a').should('have.class', 'muted');
|
||||
|
||||
// # Click Unmute channel while menu is open
|
||||
cy.get('#channelHeaderDropdownIcon').click();
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.get('#channelHeaderDropdownMenu').should('exist').
|
||||
findByText('Unmute Channel').should('be.visible').click();
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('Close direct messages', () => {
|
||||
it('Through channel header dropdown menu', () => {
|
||||
createAndVisitDMChannel([testUser.id, otherUser.id]).then((channel) => {
|
||||
// # 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();
|
||||
|
||||
verifyChannelWasProperlyClosed(channel.name);
|
||||
@@ -104,7 +104,7 @@ describe('Close group messages', () => {
|
||||
it('Through channel header dropdown menu', () => {
|
||||
createAndVisitGMChannel([otherUser1, otherUser2]).then((channel) => {
|
||||
// # 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();
|
||||
|
||||
verifyChannelWasProperlyClosed(channel.name);
|
||||
|
||||
@@ -41,14 +41,20 @@ describe('Channels', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # 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
|
||||
cy.get('#channelConvertToPrivate').should('be.visible').click();
|
||||
cy.findByTestId('convertChannelConfirm').should('be.visible').click();
|
||||
|
||||
// # 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
|
||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
||||
@@ -66,7 +72,10 @@ describe('Channels', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # 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
|
||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
||||
@@ -91,7 +100,10 @@ describe('Channels', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # 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
|
||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
||||
@@ -109,14 +121,20 @@ describe('Channels', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # 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
|
||||
cy.get('#channelConvertToPrivate').should('be.visible').click();
|
||||
cy.findByTestId('convertChannelConfirm').should('be.visible').click();
|
||||
|
||||
// # 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
|
||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
||||
|
||||
@@ -60,6 +60,7 @@ describe('Group Message Conversion To Private Channel', () => {
|
||||
|
||||
// Open the GM conversion dialog
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByRole('menuitem', {name: 'Settings'}).trigger('mouseover');
|
||||
cy.findByText('Convert to Private Channel').click();
|
||||
|
||||
// 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
|
||||
cy.visit(`/${testTeam1.name}/messages/${gm2.name}`);
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByRole('menuitem', {name: 'Settings'}).trigger('mouseover');
|
||||
cy.findByText('Convert to Private Channel').click();
|
||||
cy.get('.GenericModal__button.delete.disabled').wait(2000);
|
||||
|
||||
@@ -141,6 +143,7 @@ describe('Group Message Conversion To Private Channel', () => {
|
||||
// Open the GM
|
||||
cy.visit(`/${testTeam1.name}/messages/${gm3.name}`);
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByRole('menuitem', {name: 'Settings'}).trigger('mouseover');
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ describe('Leave and Archive channel actions display as destructive', () => {
|
||||
cy.findByText('View Info').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
|
||||
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
|
||||
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
|
||||
cy.get('#channelNotificationPreferences').should('be.visible');
|
||||
|
||||
// * Mute Channel menu option 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
|
||||
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
|
||||
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
|
||||
cy.get('#channelRename').should('be.visible');
|
||||
|
||||
// * Archive Channel menu option should be visible and have a background-color (destructive)
|
||||
cy.get('#channelArchiveChannel').should('be.visible').children().focus().should('have.css', 'background-color', 'rgb(210, 75, 78)');
|
||||
// * Channel Settings close menu option
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseout');
|
||||
|
||||
// * Leave Channel menu option should be visible and hav a background-color (destructive)
|
||||
cy.get('#channelLeaveChannel').should('be.visible').children().focus().should('have.css', 'background-color', 'rgb(210, 75, 78)');
|
||||
// * Archive Channel menu option should be visible and have a background-color (destructive)
|
||||
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 ', () => {
|
||||
|
||||
@@ -60,49 +60,52 @@ describe('Channel Settings', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T859_1 Single User: Usernames are links, open profile popovers', () => {
|
||||
// # Create and visit new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
// it('MM-T859_1 Single User: Usernames are links, open profile popovers', () => {
|
||||
// // # Create and visit new channel
|
||||
// cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
// cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Add users to channel
|
||||
addNumberOfUsersToChannel(1, false);
|
||||
// // # Add users to channel
|
||||
// addNumberOfUsersToChannel(1, false);
|
||||
|
||||
cy.getLastPostId().then((id) => {
|
||||
// * The system message should contain 'added to the channel by you'
|
||||
cy.get(`#postMessageText_${id}`).should('contain', 'added to the channel by you');
|
||||
// cy.getLastPostId().then((id) => {
|
||||
// // * The system message should contain 'added to the channel by you'
|
||||
// cy.get(`#postMessageText_${id}`).should('contain', 'added to the channel by you');
|
||||
|
||||
// # Verify username link
|
||||
verifyMentionedUserAndProfilePopover(id);
|
||||
});
|
||||
});
|
||||
});
|
||||
// // # Verify username link
|
||||
// verifyMentionedUserAndProfilePopover(id);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
it('MM-T859_2 Combined Users: Usernames are links, open profile popovers', () => {
|
||||
// # Create and visit new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
// it('MM-T859_2 Combined Users: Usernames are links, open profile popovers', () => {
|
||||
// // # Create and visit new channel
|
||||
// cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
// cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
addNumberOfUsersToChannel(3, false);
|
||||
// addNumberOfUsersToChannel(3, false);
|
||||
|
||||
cy.getLastPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).should('contain', '2 others were added to the channel by you');
|
||||
// cy.getLastPostId().then((id) => {
|
||||
// cy.get(`#postMessageText_${id}`).should('contain', '2 others were added to the channel by you');
|
||||
|
||||
// # Click "2 others" to expand more users
|
||||
cy.get(`#post_${id}`).find('.markdown__paragraph-inline').siblings('a').first().click().then(() => {
|
||||
// # Verify each username link
|
||||
verifyMentionedUserAndProfilePopover(id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
// // # Click "2 others" to expand more users
|
||||
// cy.get(`#post_${id}`).find('.markdown__paragraph-inline').siblings('a').first().click().then(() => {
|
||||
// // # Verify each username link
|
||||
// verifyMentionedUserAndProfilePopover(id);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
it('MM-T856_1 Add existing users to public channel from drop-down > Add Members', () => {
|
||||
// # Visit the add users channel
|
||||
cy.visit(`/${testTeam.name}/channels/${addedUsersChannel.name}`);
|
||||
|
||||
// # 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');
|
||||
|
||||
// # Type into the input box to search for a user
|
||||
@@ -160,7 +163,10 @@ describe('Channel Settings', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # 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');
|
||||
|
||||
// # 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}`);
|
||||
|
||||
// # 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');
|
||||
@@ -230,7 +237,8 @@ describe('Channel Settings', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # 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');
|
||||
@@ -275,7 +283,8 @@ describe('Channel Settings', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # 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');
|
||||
@@ -324,7 +333,8 @@ describe('Channel Settings', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # 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');
|
||||
@@ -369,7 +379,8 @@ function verifyMentionedUserAndProfilePopover(postId: string) {
|
||||
|
||||
function addNumberOfUsersToChannel(num = 1, allowExisting = false) {
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Add Members');
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
cy.uiGetButton('Add').click();
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// * Assert that modal appears
|
||||
|
||||
@@ -35,7 +35,10 @@ describe('Channel routing', () => {
|
||||
cy.uiCreateChannel({name: 'Test__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();
|
||||
|
||||
// # 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
|
||||
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"
|
||||
cy.findByText('Rename Channel').click();
|
||||
@@ -113,7 +119,10 @@ describe('Channel routing', () => {
|
||||
cy.get('#channelHeaderTitle').contains(`${firstWord} ${secondWord}`);
|
||||
|
||||
// # 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"
|
||||
cy.findByText('Rename Channel').click();
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Open Channel Members Dialog
|
||||
cy.get('#channelHeaderDropdownIcon').click();
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Manage Members').click().wait(TIMEOUTS.FIVE_SEC);
|
||||
|
||||
// * Verify the accessibility support in Manage Members Dialog
|
||||
@@ -118,7 +118,7 @@ function verifyMainMenuModal(menuItem: string, modalName?: string) {
|
||||
}
|
||||
|
||||
function verifyChannelMenuModal(menuItem: string, modalName?: string) {
|
||||
cy.get('#channelHeaderDropdownIcon').click();
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
verifyModal(menuItem, modalName);
|
||||
}
|
||||
|
||||
|
||||
@@ -149,8 +149,8 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Open Add Members Dialog
|
||||
cy.get('#channelHeaderDropdownIcon').click();
|
||||
cy.findByText('Add Members').click();
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// * Verify the accessibility support in Add people Dialog
|
||||
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', () => {
|
||||
// # Open Channel Members RHS
|
||||
cy.get('#channelHeaderDropdownIcon').click();
|
||||
cy.get('#channelManageMembers').click().wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.get('#channelMembers').click().wait(TIMEOUTS.HALF_SEC);
|
||||
cy.uiGetRHS().findByTestId(`memberline-${guestUser.id}`).within(($el) => {
|
||||
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);
|
||||
|
||||
// * Verify dropdown opens
|
||||
cy.get('#channelHeaderDropdownMenu .Menu__content.dropdown-menu').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');
|
||||
cy.get('#channelHeaderDropdownMenu').should('be.visible');
|
||||
|
||||
// * Verify `View Members` menu item is visible
|
||||
cy.get('#channelViewMembers').should('be.visible');
|
||||
cy.get('#channelMembers').should('be.visible');
|
||||
|
||||
// # Close channel header menu
|
||||
cy.get('body').type('{esc}');
|
||||
@@ -166,10 +160,11 @@ describe('Team Permissions', () => {
|
||||
cy.uiOpenChannelMenu().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Verify dropdown opens
|
||||
cy.get('#channelHeaderDropdownMenu .Menu__content.dropdown-menu').should('be.visible');
|
||||
cy.get('#channelHeaderDropdownMenu').should('be.visible');
|
||||
|
||||
// # Click on `Invite Members`
|
||||
cy.get('#channelInviteMembers').should('be.visible').click().wait(TIMEOUTS.HALF_SEC);
|
||||
// # Click on `Members`
|
||||
cy.get('#channelMembers').should('be.visible').click().wait(TIMEOUTS.HALF_SEC);
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// # Search and select otherUser
|
||||
cy.get('#selectItems input').typeWithForce(otherUser.username).wait(TIMEOUTS.HALF_SEC);
|
||||
@@ -182,10 +177,11 @@ describe('Team Permissions', () => {
|
||||
cy.uiOpenChannelMenu().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Verify dropdown opens
|
||||
cy.get('#channelHeaderDropdownMenu .Menu__content.dropdown-menu').should('be.visible');
|
||||
cy.get('#channelHeaderDropdownMenu').should('be.visible');
|
||||
|
||||
// # Click on `Manage Members`
|
||||
cy.get('#channelManageMembers').should('be.visible').click().wait(TIMEOUTS.HALF_SEC);
|
||||
// # Click on `Members`
|
||||
cy.get('#channelMembers').should('be.visible').click().wait(TIMEOUTS.HALF_SEC);
|
||||
cy.uiGetButton('Manage').click();
|
||||
|
||||
// # Click on `Member`
|
||||
cy.uiGetRHS().findByTestId(`memberline-${otherUser.id}`).within(() => {
|
||||
@@ -210,7 +206,8 @@ describe('Team Permissions', () => {
|
||||
cy.uiOpenChannelMenu().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * 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
|
||||
cy.get('#channelEditHeader').should('be.visible');
|
||||
|
||||
@@ -125,7 +125,7 @@ function verifyRenamePrivateChannel(teamName, privateChannelName, testUsers) {
|
||||
cy.visit(`/${teamName}/channels/${privateChannelName}`);
|
||||
|
||||
// * 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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,8 +115,13 @@ describe('Keyboard shortcut CTRL/CMD+Shift+\\ for adding reaction to last messag
|
||||
verifyEmojiPickerNotOpen();
|
||||
|
||||
['Edit Channel Header', 'Rename Channel'].forEach((modal) => {
|
||||
// # Open the modal and do keyboard shortcut
|
||||
cy.uiOpenChannelMenu(modal);
|
||||
// # Click on the channel name in the channel header to open the channel menu options
|
||||
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();
|
||||
|
||||
// * 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
|
||||
cy.get('#emojiPicker').should('not.exist');
|
||||
|
||||
// close channel Menu
|
||||
pressEscapeKey();
|
||||
|
||||
// * Open the main menu dropdown and do keyboard shortcut
|
||||
cy.uiOpenTeamMenu();
|
||||
doReactToLastMessageShortcut();
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('Channels', () => {
|
||||
cy.get('#channelHeaderTitle').should('contain', testChannel.display_name).click();
|
||||
|
||||
// * Verify that the menu is opened
|
||||
cy.get('.Menu__content').should('be.visible').within(() => {
|
||||
cy.get('#channelHeaderDropdownMenu').should('be.visible').within(() => {
|
||||
// # Archive the channel
|
||||
cy.findByText('Archive Channel').should('be.visible').click();
|
||||
});
|
||||
|
||||
@@ -170,14 +170,14 @@ describe('Direct Message', () => {
|
||||
// # Visit the DM channel
|
||||
cy.visit(`/${testTeam.name}/messages/@${otherUser.username}`);
|
||||
|
||||
// # Open channel menu and click Mute Conversation
|
||||
cy.uiOpenChannelMenu('Mute Conversation');
|
||||
// # Open channel menu and click Mute
|
||||
cy.uiOpenChannelMenu('Mute');
|
||||
|
||||
// * Assert that channel appears as muted on the LHS
|
||||
cy.uiGetLhsSection('DIRECT MESSAGES').find('.muted').first().should('contain', otherUser.username);
|
||||
|
||||
// # Open channel menu and click Unmute Conversation
|
||||
cy.uiOpenChannelMenu('Unmute Conversation');
|
||||
// # Open channel menu and click Unmute
|
||||
cy.uiOpenChannelMenu('Unmute');
|
||||
|
||||
// * Assert that channel does not appear as muted on the LHS
|
||||
cy.uiGetLhsSection('DIRECT MESSAGES').find('.muted').should('not.exist');
|
||||
|
||||
@@ -142,7 +142,8 @@ function verifyFocusInAddChannelMemberModal() {
|
||||
cy.get('#channelLeaveChannel').should('be.visible');
|
||||
|
||||
// # Click 'Add Members'
|
||||
cy.get('#channelInviteMembers').click();
|
||||
cy.get('#channelMembers').click();
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// * Assert that modal appears
|
||||
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', () => {
|
||||
// * Verify with short channel header
|
||||
updateAndVerifyChannelHeader('>', 'newheader');
|
||||
updateAndVerifyChannelHeader(false, '>', 'newheader');
|
||||
|
||||
// * Verify with long channel header
|
||||
updateAndVerifyChannelHeader('>', 'newheader'.repeat(20));
|
||||
updateAndVerifyChannelHeader(false, '>', 'newheader'.repeat(20));
|
||||
});
|
||||
|
||||
it('MM-T881_1 - Header: Markdown quote', () => {
|
||||
// # Update channel header text
|
||||
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', () => {
|
||||
@@ -56,7 +56,7 @@ describe('Header', () => {
|
||||
// # Update DM channel header
|
||||
const header = `quote ${'newheader'.repeat(15)}`;
|
||||
|
||||
updateAndVerifyChannelHeader('>', header);
|
||||
updateAndVerifyChannelHeader(true, '>', header);
|
||||
|
||||
// # Hover on channel header
|
||||
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
|
||||
cy.updateChannelHeader(prefix + header);
|
||||
if (isDM) {
|
||||
cy.updateDMGMChannelHeader(prefix + header);
|
||||
} else {
|
||||
cy.updateChannelHeader(prefix + header);
|
||||
}
|
||||
|
||||
// * Should render blockquote if it starts with ">"
|
||||
if (prefix === '>') {
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('Multi-user group messages', () => {
|
||||
cy.get('#saveItems').click();
|
||||
|
||||
// # Click channel dropdown icon to open menu
|
||||
cy.get('#channelHeaderDropdownIcon').click();
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// # Click 'close' item
|
||||
cy.findByText('Close Group Message').should('be.visible').click();
|
||||
|
||||
@@ -108,7 +108,8 @@ describe('Multi-user group messages', () => {
|
||||
cy.postMessage('messages');
|
||||
|
||||
// # 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."
|
||||
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');
|
||||
|
||||
// * New user is added to the GM
|
||||
cy.get('.member-rhs__trigger').click();
|
||||
cy.uiGetRHS().contains(testUser.username).should('be.visible');
|
||||
|
||||
// * Other users are still there
|
||||
|
||||
@@ -136,8 +136,10 @@ describe('Multi-user group header', () => {
|
||||
});
|
||||
|
||||
function editHeader(header) {
|
||||
// # Click edit conversation header
|
||||
cy.uiOpenChannelMenu('Edit Conversation Header');
|
||||
// # Click edit header
|
||||
cy.uiOpenChannelMenu('');
|
||||
cy.findByRole('menuitem', {name: 'Settings'}).trigger('mouseover');
|
||||
cy.findByText('Edit Header').click();
|
||||
|
||||
// * Verify the modal open to add header
|
||||
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');
|
||||
|
||||
// # 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"
|
||||
cy.get('#channelAddMembers').click();
|
||||
cy.get('#channelMembers').click();
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// # Try to add one more user from the group message's list
|
||||
addUsersToGMViaModal(1);
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('System Console > User Management > Deactivation', () => {
|
||||
cy.sendDirectMessageToUser(other, MESSAGES.SMALL);
|
||||
|
||||
// # Close DM
|
||||
cy.get('#channelHeaderDropdownIcon').click();
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Close Direct Message').click();
|
||||
|
||||
// # Deactivate the user
|
||||
|
||||
@@ -56,6 +56,9 @@ describe('channels > general actions', {testIsolation: true}, () => {
|
||||
|
||||
// # Open Channel Header and the Channel Actions modal
|
||||
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();
|
||||
|
||||
// # 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
|
||||
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();
|
||||
|
||||
// # 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
|
||||
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();
|
||||
|
||||
// # 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
|
||||
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();
|
||||
|
||||
// # 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
|
||||
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();
|
||||
|
||||
// # 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
|
||||
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();
|
||||
|
||||
// * Verify the toggles are disabled
|
||||
@@ -333,6 +351,9 @@ describe('channels > general actions', {testIsolation: true}, () => {
|
||||
|
||||
// # Open Channel Header and the Channel Actions modal
|
||||
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();
|
||||
|
||||
// # 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
|
||||
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();
|
||||
|
||||
// * Verify that the categorization action is disabled
|
||||
|
||||
@@ -104,6 +104,7 @@ describe('channels > run', {testIsolation: true}, () => {
|
||||
|
||||
// # Open the channel header
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||
|
||||
// * Verify the ability to edit the channel header exists
|
||||
cy.get('#channelEditHeader').should('exist');
|
||||
|
||||
@@ -43,8 +43,9 @@ Cypress.Commands.add('uiCreateChannel', ({
|
||||
|
||||
Cypress.Commands.add('uiAddUsersToCurrentChannel', (usernameList) => {
|
||||
if (usernameList.length) {
|
||||
cy.get('#channelHeaderDropdownIcon').click();
|
||||
cy.get('#channelAddMembers').click();
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.get('#channelMembers').click();
|
||||
cy.uiGetButton('Add').click();
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
usernameList.forEach((username) => {
|
||||
cy.get('#selectItems input').typeWithForce(`@${username}{enter}`);
|
||||
@@ -56,8 +57,9 @@ Cypress.Commands.add('uiAddUsersToCurrentChannel', (usernameList) => {
|
||||
|
||||
Cypress.Commands.add('uiInviteUsersToCurrentChannel', (usernameList) => {
|
||||
if (usernameList.length) {
|
||||
cy.get('#channelHeaderDropdownIcon').click();
|
||||
cy.get('#channelInviteMembers').click();
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.get('#channelMembers').click();
|
||||
cy.uiGetButton('Add').click();
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
usernameList.forEach((username) => {
|
||||
cy.get('#selectItems input').typeWithForce(`@${username}{enter}`);
|
||||
@@ -68,19 +70,19 @@ Cypress.Commands.add('uiInviteUsersToCurrentChannel', (usernameList) => {
|
||||
});
|
||||
|
||||
Cypress.Commands.add('uiArchiveChannel', () => {
|
||||
cy.get('#channelHeaderDropdownIcon').click();
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.get('#channelArchiveChannel').click();
|
||||
return cy.get('#deleteChannelModalDeleteButton').click();
|
||||
});
|
||||
|
||||
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();
|
||||
return cy.get('#unarchiveChannelModalDeleteButton').should('be.visible').click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('uiLeaveChannel', (isPrivate = false) => {
|
||||
cy.get('#channelHeaderDropdownIcon').click();
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
if (isPrivate) {
|
||||
cy.get('#channelLeaveChannel').click();
|
||||
|
||||
@@ -32,7 +32,6 @@ Cypress.Commands.add('uiGetChannelFileButton', () => {
|
||||
Cypress.Commands.add('uiGetChannelMenu', (options = {exist: true}) => {
|
||||
if (options.exist) {
|
||||
return cy.get('#channelHeaderDropdownMenu').
|
||||
find('.dropdown-menu').
|
||||
should('be.visible');
|
||||
}
|
||||
|
||||
|
||||
@@ -462,13 +462,15 @@ function getCurrentChannelId(): ChainableT<string> {
|
||||
Cypress.Commands.add('getCurrentChannelId', getCurrentChannelId);
|
||||
|
||||
function updateChannelHeader(text: string) {
|
||||
cy.get('#channelHeaderDropdownIcon').
|
||||
cy.get('#channelHeaderTitle').
|
||||
should('be.visible').
|
||||
click();
|
||||
cy.get('.Menu__content').
|
||||
should('be.visible').
|
||||
find('#channelEditHeader').
|
||||
click();
|
||||
cy.get('#channelHeaderDropdownMenu').
|
||||
should('be.visible');
|
||||
|
||||
// * 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').
|
||||
clear().
|
||||
type(text).
|
||||
@@ -478,6 +480,24 @@ function updateChannelHeader(text: string) {
|
||||
|
||||
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> {
|
||||
return cy.apiGetLDAPSync().then((response) => {
|
||||
const jobs = response.body;
|
||||
@@ -787,6 +807,12 @@ declare global {
|
||||
*/
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import type {ReactNode} from 'react';
|
||||
import React, {memo, useState, useRef, useEffect} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import React, {memo} from 'react';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
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 SharedChannelIndicator from 'components/shared_channel_indicator';
|
||||
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 WithTooltip from 'components/with_tooltip';
|
||||
|
||||
import {Constants} from 'utils/constants';
|
||||
|
||||
@@ -29,6 +21,8 @@ import ChannelHeaderTitleDirect from './channel_header_title_direct';
|
||||
import ChannelHeaderTitleFavorite from './channel_header_title_favorite';
|
||||
import ChannelHeaderTitleGroup from './channel_header_title_group';
|
||||
|
||||
import ChannelHeaderMenu from '../channel_header_menu/channel_header_menu';
|
||||
|
||||
type Props = {
|
||||
dmUser?: UserProfile;
|
||||
gmMembers?: UserProfile[];
|
||||
@@ -38,45 +32,22 @@ const ChannelHeaderTitle = ({
|
||||
dmUser,
|
||||
gmMembers,
|
||||
}: Props) => {
|
||||
const [titleMenuOpen, setTitleMenuOpen] = useState(false);
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const intl = useIntl();
|
||||
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) {
|
||||
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 isGroup = (channel.type === Constants.GM_CHANNEL);
|
||||
const channelIsArchived = channel.delete_at !== 0;
|
||||
|
||||
let archivedIcon: React.ReactNode = null;
|
||||
let archivedIcon;
|
||||
if (channelIsArchived) {
|
||||
archivedIcon = <ArchiveIcon className='icon icon__archive icon channel-header-archived-icon svg-text-color'/>;
|
||||
}
|
||||
|
||||
let sharedIcon = null;
|
||||
let sharedIcon;
|
||||
if (channel.shared) {
|
||||
sharedIcon = (
|
||||
<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 (
|
||||
<div className='channel-header__top'>
|
||||
<ChannelHeaderTitleFavorite/>
|
||||
@@ -142,60 +99,12 @@ const ChannelHeaderTitle = ({
|
||||
status={channel.status}
|
||||
/>
|
||||
)}
|
||||
<MenuWrapper onToggle={setTitleMenuOpen}>
|
||||
<div
|
||||
id='channelHeaderDropdownButton'
|
||||
>
|
||||
<button
|
||||
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>
|
||||
<ChannelHeaderMenu
|
||||
dmUser={dmUser}
|
||||
gmMembers={gmMembers}
|
||||
sharedIcon={sharedIcon}
|
||||
archivedIcon={archivedIcon}
|
||||
/>
|
||||
</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