[MM-55277] Team's menu doesn't follow standard accessible keyboard behavior (#29917)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
b59f10cbbd
Коммит
502506a517
@@ -105,9 +105,9 @@ describe('Verify Accessibility Support in Dropdown Menus', () => {
|
||||
{id: 'invitePeople', label: 'Invite People dialog'},
|
||||
{id: 'teamSettings', label: 'Team Settings dialog'},
|
||||
{id: 'manageMembers', label: 'Manage Members dialog'},
|
||||
{id: 'joinTeam', text: 'Join Another Team'},
|
||||
{id: 'joinTeam', text: 'Join another team'},
|
||||
{id: 'leaveTeam', label: 'Leave Team dialog'},
|
||||
{id: 'createTeam', text: 'Create a Team'},
|
||||
{id: 'createTeam', text: 'Create a team'},
|
||||
];
|
||||
|
||||
menuItems.forEach((item) => {
|
||||
|
||||
@@ -70,7 +70,7 @@ describe('Profile > Profile Settings> Full Name', () => {
|
||||
|
||||
it('MM-T2048 Empty full name: @ still displays before username', () => {
|
||||
// # Open any user list ("View Members", "Add Members", "Manage Members", ..)
|
||||
cy.uiOpenTeamMenu('View Members');
|
||||
cy.uiOpenTeamMenu('View members');
|
||||
|
||||
// # Find a user who hasn't set their full name
|
||||
cy.get('.modal-title').should('be.visible');
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('Settings > Sidebar > General', () => {
|
||||
});
|
||||
|
||||
// # Open team menu and click "View Members"
|
||||
cy.uiOpenTeamMenu('View Members');
|
||||
cy.uiOpenTeamMenu('View members');
|
||||
|
||||
// # Search for username and check that no nickname is present
|
||||
cy.get('.modal-title').should('be.visible');
|
||||
@@ -68,7 +68,7 @@ describe('Settings > Sidebar > General', () => {
|
||||
});
|
||||
|
||||
// # Open team menu and click "View Members"
|
||||
cy.uiOpenTeamMenu('View Members');
|
||||
cy.uiOpenTeamMenu('View members');
|
||||
|
||||
// # Search for username and check that expected nickname is present
|
||||
cy.get('.modal-title').should('be.visible');
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('Leave an archived channel', () => {
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Open channel menu and click View Members
|
||||
cy.uiOpenChannelMenu('View Members');
|
||||
cy.uiOpenChannelMenu('View members');
|
||||
|
||||
// * Channel Members modal opens
|
||||
cy.get('div#channelMembersModal').should('be.visible');
|
||||
|
||||
@@ -267,7 +267,7 @@ describe('Authentication', () => {
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Open team menu and click on "Invite People"
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Click invite members if needed
|
||||
cy.findByText('Copy invite link').click();
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('Bots in lists', () => {
|
||||
|
||||
// # Open channel members
|
||||
cy.get('.channel-header__trigger').click();
|
||||
cy.findByText('Manage Members').click();
|
||||
cy.findByText('Manage members').click();
|
||||
|
||||
cy.get('.more-modal__row .more-modal__name').then(async ($query) => {
|
||||
// # Extract usernames from jQuery collection
|
||||
|
||||
@@ -37,7 +37,7 @@ describe('Managing bots in Teams and Channels', () => {
|
||||
await client.addToTeam(team.id, bot.user_id);
|
||||
|
||||
// # Open team menu and click 'Manage Members'
|
||||
cy.uiOpenTeamMenu('Manage Members');
|
||||
cy.uiOpenTeamMenu('Manage members');
|
||||
|
||||
// # Find bot
|
||||
cy.get('.more-modal__list').find('.more-modal__row').its('length').should('be.gt', 0);
|
||||
|
||||
@@ -105,7 +105,7 @@ describe('Custom Status - Verifying Where Custom Status Appears', () => {
|
||||
|
||||
it('MM-T3850_8 should show custom status emoji next to username in the team members modal', () => {
|
||||
// # Open team menu and click on "View Members"
|
||||
cy.uiOpenTeamMenu('View Members');
|
||||
cy.uiOpenTeamMenu('View members');
|
||||
cy.get('#teamMembersModal').should('exist');
|
||||
|
||||
// # Search the current user's username in the search input
|
||||
|
||||
@@ -36,7 +36,7 @@ describe('Verify Accessibility Support in different input fields', () => {
|
||||
|
||||
it('MM-T1456 Verify Accessibility Support in Input fields in Invite People Flow', () => {
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Click invite members if needed
|
||||
cy.get('.InviteAs').findByTestId('inviteMembersLink').click();
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
verifyUserMenuModal('Profile');
|
||||
|
||||
// * Verify the accessibility support in Team Settings Dialog
|
||||
verifyMainMenuModal('Team Settings');
|
||||
verifyMainMenuModal('Team settings');
|
||||
|
||||
// * Verify the accessibility support in Manage Members Dialog
|
||||
verifyMainMenuModal('Manage Members', `${testTeam.display_name} Members`);
|
||||
@@ -72,7 +72,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
|
||||
// # Open Channel Members Dialog
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Manage Members').click().wait(TIMEOUTS.FIVE_SEC);
|
||||
cy.findByText('Manage members').click().wait(TIMEOUTS.FIVE_SEC);
|
||||
|
||||
// * Verify the accessibility support in Manage Members Dialog
|
||||
cy.findByRole('dialog', {name: 'Off-Topic Members'}).within(() => {
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
|
||||
it.skip('MM-T1466 Accessibility Support in Direct Messages Dialog screen', () => {
|
||||
it('MM-T1466 Accessibility Support in Direct Messages Dialog screen', () => {
|
||||
// * Verify the aria-label in create direct message button
|
||||
cy.uiAddDirectMessage().click();
|
||||
|
||||
@@ -53,11 +53,11 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
cy.findByRole('heading', {name: 'Direct Messages'});
|
||||
|
||||
// * Verify the accessibility support in search input
|
||||
cy.findByRole('textbox', {name: 'Search for people'}).
|
||||
cy.findByLabelText('Search for people').
|
||||
should('have.attr', 'aria-autocomplete', 'list');
|
||||
|
||||
// # Search for a text and then check up and down arrow
|
||||
cy.findByRole('textbox', {name: 'Search for people'}).
|
||||
cy.findByLabelText('Search for people').
|
||||
typeWithForce('s').
|
||||
wait(TIMEOUTS.HALF_SEC).
|
||||
typeWithForce('{downarrow}{downarrow}{downarrow}{uparrow}');
|
||||
@@ -80,7 +80,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
|
||||
// # Search for an invalid text
|
||||
const additionalSearchTerm = 'somethingwhichdoesnotexist';
|
||||
cy.findByRole('textbox', {name: 'Search for people'}).clear().
|
||||
cy.findByLabelText('Search for people').clear().
|
||||
typeWithForce(additionalSearchTerm).
|
||||
wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
@@ -137,7 +137,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('MM-T1468 Accessibility Support in Add people to Channel Dialog screen', () => {
|
||||
it('MM-T1468 Accessibility Support in Add people to Channel Dialog screen', () => {
|
||||
// # Add atleast 5 users
|
||||
for (let i = 0; i < 5; i++) {
|
||||
cy.apiCreateUser().then(({user}) => { // eslint-disable-line
|
||||
@@ -159,11 +159,11 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
cy.wait(TIMEOUTS.ONE_SEC);
|
||||
|
||||
// * Verify the accessibility support in search input
|
||||
cy.findByRole('textbox', {name: 'Search for people or groups'}).
|
||||
cy.findByLabelText('Search for people or groups').
|
||||
should('have.attr', 'aria-autocomplete', 'list');
|
||||
|
||||
// # Search for a text and then check up and down arrow
|
||||
cy.findByRole('textbox', {name: 'Search for people or groups'}).
|
||||
cy.findByLabelText('Search for people or groups').
|
||||
wait(TIMEOUTS.HALF_SEC).
|
||||
typeWithForce('u').
|
||||
wait(TIMEOUTS.HALF_SEC).
|
||||
@@ -189,7 +189,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
});
|
||||
|
||||
// # Search for an invalid text and check if reader can read no results
|
||||
cy.findByRole('textbox', {name: 'Search for people or groups'}).
|
||||
cy.findByLabelText('Search for people or groups').
|
||||
typeWithForce('somethingwhichdoesnotexist').
|
||||
wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
@@ -203,7 +203,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
it('MM-T1515 Verify Accessibility Support in Invite People Flow', () => {
|
||||
// # Open Invite People
|
||||
cy.uiGetLHSHeader().click();
|
||||
cy.get('#invitePeople').should('be.visible').click();
|
||||
cy.get("#sidebarTeamMenu li:contains('Invite people')").should('be.visible').click();
|
||||
|
||||
// * Verify accessibility support in Invite People Dialog
|
||||
cy.findByTestId('invitationModal').should('have.attr', 'aria-modal', 'true').and('have.attr', 'aria-labelledby', 'invitation_modal_title').and('have.attr', 'role', 'dialog');
|
||||
|
||||
@@ -111,9 +111,14 @@ function userGroupsNotification() {
|
||||
}
|
||||
|
||||
function creatNewTeamNotification() {
|
||||
cy.get('.test-team-header').click().then(() => {
|
||||
// # Click on the team menu button
|
||||
cy.get('#sidebarTeamMenuButton').click();
|
||||
|
||||
// # Click on the lock button on Create a team menu item
|
||||
cy.get('#sidebarTeamMenu').within(() => {
|
||||
cy.get('#mattermost_feature_create_multiple_teams-restricted-indicator').click();
|
||||
});
|
||||
|
||||
cy.get('#FeatureRestrictedModal').should('exist');
|
||||
cy.get('#button-plans').as('notifyButton').should('have.text', 'Notify admin').click();
|
||||
cy.get('@notifyButton').should('have.text', 'Admin notified!');
|
||||
|
||||
@@ -64,24 +64,27 @@ describe('Guest Account - Guest User Experience', () => {
|
||||
|
||||
// * Verify reduced options in Team Menu
|
||||
const missingMainOptions = [
|
||||
'Invite People',
|
||||
'Team Settings',
|
||||
'Manage Members',
|
||||
'Join Another Team',
|
||||
'Create a Team',
|
||||
'Invite people',
|
||||
'Team settings',
|
||||
'Manage members',
|
||||
'Join another team',
|
||||
'Create a team',
|
||||
];
|
||||
missingMainOptions.forEach((missingOption) => {
|
||||
cy.uiGetLHSTeamMenu().should('not.contain', missingOption);
|
||||
});
|
||||
|
||||
const includeMainOptions = [
|
||||
'View Members',
|
||||
'Leave Team',
|
||||
'View members',
|
||||
'Leave team',
|
||||
];
|
||||
includeMainOptions.forEach((includeOption) => {
|
||||
cy.uiGetLHSTeamMenu().findByText(includeOption);
|
||||
});
|
||||
|
||||
// # Close the main menu
|
||||
cy.get('body').type('{esc}');
|
||||
|
||||
// * Verify Reduced Options in LHS
|
||||
cy.uiGetLHSAddChannelButton().should('not.exist');
|
||||
|
||||
@@ -120,7 +123,7 @@ describe('Guest Account - Guest User Experience', () => {
|
||||
cy.uiGetLhsSection('CHANNELS').find('.SidebarChannel').should('have.length', 3);
|
||||
|
||||
// * Verify list of Users a Guest User can see in Team Members dialog
|
||||
cy.uiOpenTeamMenu('View Members');
|
||||
cy.uiOpenTeamMenu('View members');
|
||||
cy.get('#searchableUserListTotal').should('be.visible').and('have.text', '1 - 2 members of 2 total');
|
||||
});
|
||||
|
||||
@@ -136,17 +139,18 @@ describe('Guest Account - Guest User Experience', () => {
|
||||
// * Verify options in team menu are changed
|
||||
cy.uiOpenTeamMenu();
|
||||
const includeOptions = [
|
||||
'Invite People',
|
||||
'View Members',
|
||||
'Leave Team',
|
||||
'Create a Team',
|
||||
'Invite people',
|
||||
'View members',
|
||||
'Leave team',
|
||||
'Create a team',
|
||||
];
|
||||
includeOptions.forEach((option) => {
|
||||
cy.uiGetLHSTeamMenu().findByText(option);
|
||||
});
|
||||
|
||||
// # Close the main menu
|
||||
cy.uiGetLHSHeader().click();
|
||||
// Close the main menu with Escape key
|
||||
cy.get('body').type('{esc}');
|
||||
cy.uiGetLHSTeamMenu().should('not.exist');
|
||||
|
||||
// * Verify Options in LHS are changed
|
||||
cy.uiGetLHSAddChannelButton();
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('Guest Accounts', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu, click Invite People, then invite guest
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
cy.findByTestId('inviteGuestLink').click();
|
||||
|
||||
// # Type guest user e-mail address.
|
||||
|
||||
@@ -68,7 +68,7 @@ describe('Verify Guest User Identification in different screens', () => {
|
||||
|
||||
it('Verify Guest Badge in Team Members dialog', () => {
|
||||
// # Open team menu and click 'View Members'
|
||||
cy.uiOpenTeamMenu('View Members');
|
||||
cy.uiOpenTeamMenu('View members');
|
||||
|
||||
cy.get('#teamMembersModal').should('be.visible').within(($el) => {
|
||||
cy.wrap($el).findAllByTestId('userListItemDetails').each(($elChild) => {
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('Guest Account - Guest User Invitation Flow', () => {
|
||||
|
||||
it('MM-T4451 Verify UI Elements of Guest User Invitation Flow', () => {
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// * Verify Invite Guest link
|
||||
cy.findByTestId('inviteGuestLink').should('be.visible').click();
|
||||
@@ -117,7 +117,7 @@ describe('Guest Account - Guest User Invitation Flow', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// * Verify if Invite Members modal is displayed when guest account feature is disabled
|
||||
cy.findByTestId('invitationModal').find('h1').should('have.text', `Invite people to ${testTeam.display_name}`);
|
||||
@@ -164,7 +164,7 @@ describe('Guest Account - Guest User Invitation Flow', () => {
|
||||
cy.apiDemoteUserToGuest(newUser.id);
|
||||
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Click invite members if needed
|
||||
cy.get('.InviteAs').findByTestId('inviteMembersLink').click();
|
||||
@@ -226,7 +226,7 @@ describe('Guest Account - Guest User Invitation Flow', () => {
|
||||
|
||||
it('hides the copy link button when inviting guests', () => {
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Select Guest
|
||||
cy.findByTestId('inviteGuestLink').should('be.visible').click();
|
||||
|
||||
@@ -16,7 +16,7 @@ export function changeGuestFeatureSettings(featureFlag = true, emailInvitation =
|
||||
|
||||
export function invitePeople(typeText: string, resultsCount: number, verifyText: string, channelName = 'Town Square', clickInvite = true) {
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Click on the next icon to invite guest
|
||||
cy.findByTestId('inviteGuestLink').click();
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('Guest Account - Member Invitation Flow', () => {
|
||||
const email = `temp-${getRandomId()}@mattermost.com`;
|
||||
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// * Verify UI Elements in initial step
|
||||
cy.findByTestId('invitationModal').within(() => {
|
||||
@@ -251,7 +251,7 @@ describe('Guest Account - Member Invitation Flow', () => {
|
||||
|
||||
function invitePeople(typeText, resultsCount, verifyText, clickInvite = true) {
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Search and add a member
|
||||
cy.get('.users-emails-input__control').should('be.visible').within(() => {
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('Guest Account - Verify Guest Access UI', () => {
|
||||
});
|
||||
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// * Verify that an option to Invite via Guest should not be available
|
||||
cy.findByTestId('inviteGuestLink').should('not.exist');
|
||||
|
||||
@@ -219,7 +219,7 @@ describe('LDAP guest', () => {
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('Group Synced Team - Bot invitation flow', () => {
|
||||
cy.visit(`/${groupConstrainedTeam.name}`);
|
||||
|
||||
// # Click 'Invite People' at team menu
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Type the first letters of a bot
|
||||
cy.get('.users-emails-input__control input').typeWithForce(bot.username);
|
||||
@@ -75,7 +75,7 @@ describe('Group Synced Team - Bot invitation flow', () => {
|
||||
cy.visit(`/${groupConstrainedTeam.name}`);
|
||||
|
||||
// # Click 'Manage Members' at team menu
|
||||
cy.uiOpenTeamMenu('Manage Members');
|
||||
cy.uiOpenTeamMenu('Manage members');
|
||||
|
||||
// # Search for the bot that we want to remove
|
||||
cy.get('#searchUsersInput').should('be.visible').type(bot.username);
|
||||
|
||||
@@ -95,10 +95,10 @@ describe('Team Permissions', () => {
|
||||
cy.uiOpenTeamMenu().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Verify `Invite People` menu item is not present
|
||||
cy.get('#invitePeople').should('not.exist');
|
||||
cy.get("#sidebarTeamMenu li:contains('Invite people')").should('not.exist');
|
||||
|
||||
// # Click `View Members` menu item
|
||||
cy.get('#viewMembers').should('be.visible').click().wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get("#sidebarTeamMenu li:contains('View members')").should('be.visible').click().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Verify team members modal opens
|
||||
cy.get('#teamMembersModal').should('be.visible');
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('Keyboard shortcut CTRL/CMD+Shift+\\ for adding reaction to last messag
|
||||
cy.uiOpenProductMenu('About Mattermost');
|
||||
verifyEmojiPickerNotOpen();
|
||||
|
||||
cy.uiOpenTeamMenu('View Members');
|
||||
cy.uiOpenTeamMenu('View members');
|
||||
verifyEmojiPickerNotOpen();
|
||||
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
@@ -167,9 +167,6 @@ describe('Keyboard shortcut CTRL/CMD+Shift+\\ for adding reaction to last messag
|
||||
// # Close the expanded RHS
|
||||
cy.findByLabelText('Collapse Sidebar Icon').click();
|
||||
|
||||
// # Open the pinned posts
|
||||
cy.uiGetChannelPinButton().click();
|
||||
|
||||
// # Expand RHS
|
||||
cy.findByLabelText('Expand Sidebar Icon').click();
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ describe('Message Reply', () => {
|
||||
newChannel = channel;
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
|
||||
// HERE WE NEED TO UPDATE THE AREA SINCE WE REMOVED THIS ID
|
||||
// # Wait for the page to fully load before continuing
|
||||
cy.get('#sidebar-header-container').should('be.visible').and('have.text', team.display_name);
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('Send a DM', () => {
|
||||
cy.visit(testChannelUrl);
|
||||
cy.get('#postListContent', {timeout: TIMEOUTS.HALF_MIN}).should('be.visible');
|
||||
cy.uiGetLHSHeader().click();
|
||||
cy.findByText('Leave Team').click();
|
||||
cy.findByText('Leave team').click();
|
||||
cy.findByText('Yes').click();
|
||||
cy.url().should('include', '/select_team');
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('Notifications', () => {
|
||||
|
||||
// # Invites User 1
|
||||
cy.get('#member_popover').should('be.visible').click();
|
||||
cy.contains('Manage Members').click();
|
||||
cy.contains('Manage members').click();
|
||||
cy.contains('Add Members').click();
|
||||
cy.get('.channel-invite__content').
|
||||
should('be.visible').
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('Onboarding', () => {
|
||||
cy.reload();
|
||||
|
||||
// # Open the 'Invite People' modal
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Wait half a second to ensure that the modal has been fully loaded
|
||||
cy.wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('Onboarding', () => {
|
||||
|
||||
it('MM-T400 Create account from login page link using email-password', () => {
|
||||
// # Open team menu and click on "Team Settings"
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// * Check that the 'Team Settings' modal was opened
|
||||
cy.get('#teamSettingsModal').should('exist').within(() => {
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('Onboarding', () => {
|
||||
stubClipboard().as('clipboard');
|
||||
|
||||
// # Open the 'Invite People' full screen modal and get the invite url
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Copy invite link to clipboard
|
||||
cy.findByTestId('InviteView__copyInviteLink').click();
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('Environment', () => {
|
||||
cy.visit(townsquareLink);
|
||||
|
||||
// # Open team menu and click "Team Settings"
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// * Check that the 'Team Settings' modal was opened
|
||||
cy.get('#teamSettingsModal').should('exist').within(() => {
|
||||
@@ -79,7 +79,7 @@ describe('Environment', () => {
|
||||
cy.visit(townsquareLink);
|
||||
|
||||
// # Open team menu and click "Team Settings"
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// * Check that the 'Team Settings' modal was opened
|
||||
cy.get('#teamSettingsModal').should('exist').within(() => {
|
||||
@@ -123,7 +123,7 @@ describe('Environment', () => {
|
||||
cy.visit(townsquareLink);
|
||||
|
||||
// # Open team menu and click "Team Settings"
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// * Check that the 'Team Settings' modal was opened
|
||||
cy.get('#teamSettingsModal').should('exist').within(() => {
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('User Management', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Open team menu and click View Members
|
||||
cy.uiOpenTeamMenu('View Members');
|
||||
cy.uiOpenTeamMenu('View members');
|
||||
|
||||
// * Check View Members modal dialog
|
||||
cy.get('#teamMembersModal').should('be.visible').within(() => {
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('Team Settings', () => {
|
||||
|
||||
it('MM-T385 Invite new user to closed team using email invite', () => {
|
||||
// # Open 'Team Settings' modal
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// * Check that the 'Team Settings' modal was opened
|
||||
cy.get('#teamSettingsModal').should('exist').within(() => {
|
||||
@@ -75,7 +75,7 @@ describe('Team Settings', () => {
|
||||
});
|
||||
|
||||
// # Open the 'Invite People' full screen modal
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Wait half a second to ensure that the modal has been fully loaded
|
||||
cy.wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('Teams Suite', () => {
|
||||
|
||||
it('MM-T383 Create a new team', () => {
|
||||
// # Open team menu and click "Create a Team"
|
||||
cy.uiOpenTeamMenu('Create a Team');
|
||||
cy.uiOpenTeamMenu('Create a team');
|
||||
|
||||
// # Input team name as Team Test
|
||||
const teamName = 'Team Test';
|
||||
@@ -69,7 +69,7 @@ describe('Teams Suite', () => {
|
||||
|
||||
function tryReservedTeamURLAndVerifyError(teamURL) {
|
||||
// # Open team menu and click "Create a Team"
|
||||
cy.uiOpenTeamMenu('Create a Team');
|
||||
cy.uiOpenTeamMenu('Create a team');
|
||||
|
||||
// # Input passed in team name
|
||||
cy.get('#teamNameInput').should('be.visible').type(teamURL);
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
|
||||
export const allowOnlyUserFromSpecificDomain = (domain) => {
|
||||
// # Open 'Team Settings' modal
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// * Check that the 'Team Settings' modal was opened
|
||||
cy.get('#teamSettingsModal').should('exist').within(() => {
|
||||
@@ -31,7 +31,7 @@ export const allowOnlyUserFromSpecificDomain = (domain) => {
|
||||
|
||||
export const inviteUserByEmail = (email) => {
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Wait half a second to ensure that the modal has been fully loaded
|
||||
cy.wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('Invite Members', () => {
|
||||
cy.visit('/');
|
||||
|
||||
// # Open and select invite menu item
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
});
|
||||
|
||||
it('allows user to exit when there are no inputs', () => {
|
||||
|
||||
@@ -111,7 +111,7 @@ describe('Invite Members', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open and select invite menu item
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// * Users emails input is focused by default
|
||||
cy.get('.users-emails-input__control--is-focused').should('be.visible');
|
||||
@@ -181,7 +181,7 @@ function inviteUserToTeamAsMember(testUser, testTeam, user) {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open and select invite menu item
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// * Verify Invite Members
|
||||
verifyInviteMembersModal(testTeam);
|
||||
@@ -196,7 +196,7 @@ function inviteUserToTeamAsSysadmin(testTeam, user) {
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Open and select invite menu item
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// * Verify Invite Members
|
||||
verifyInviteMembersModal(testTeam);
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('Team Settings', () => {
|
||||
const inviteFailedMessage = `The following email addresses do not belong to an accepted domain: ${invalidEmail}. Please contact your System Administrator for details.`;
|
||||
|
||||
// # Open team menu and click 'Team Settings'
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// * Check that the 'Team Settings' modal was opened
|
||||
cy.get('#teamSettingsModal').should('exist').within(() => {
|
||||
@@ -55,7 +55,7 @@ describe('Team Settings', () => {
|
||||
});
|
||||
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Invite user with valid email domain that is not in the team
|
||||
inviteNewMemberToTeam(newUser.email);
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('Team Settings', () => {
|
||||
stubClipboard().as('clipboard');
|
||||
|
||||
// # Open team menu and click 'Team Settings'
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// * Check that the 'Team Settings' modal was opened
|
||||
cy.get('#teamSettingsModal').should('exist').within(() => {
|
||||
@@ -63,7 +63,7 @@ describe('Team Settings', () => {
|
||||
cy.uiClose();
|
||||
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Get the invite URL
|
||||
cy.findByTestId('InviteView__copyInviteLink').should('be.visible').click();
|
||||
@@ -94,7 +94,7 @@ describe('Team Settings', () => {
|
||||
|
||||
it('MM-T2341 Cannot add a user to a team if the user\'s email is not from the correct domain', () => {
|
||||
// # Open team menu and click 'Team Settings'
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// * Check that the 'Team Settings' modal was opened
|
||||
cy.get('#teamSettingsModal').should('exist').within(() => {
|
||||
@@ -136,7 +136,7 @@ describe('Team Settings', () => {
|
||||
cy.visit(`/${otherTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click 'Join Another Team'
|
||||
cy.uiOpenTeamMenu('Join Another Team');
|
||||
cy.uiOpenTeamMenu('Join another team');
|
||||
|
||||
// # Try to join the existing team
|
||||
cy.get('.signup-team-dir').find(`#${testTeam.display_name.replace(' ', '_')}`).scrollIntoView().click();
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('Manage Members', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click 'Manage Members'
|
||||
cy.uiOpenTeamMenu('Manage Members');
|
||||
cy.uiOpenTeamMenu('Manage members');
|
||||
|
||||
// # Open member dropdown
|
||||
cy.get(`#teamMembersDropdown_${testUser.username}`).should('be.visible').click();
|
||||
@@ -61,7 +61,7 @@ describe('Manage Members', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click 'Manage Members'
|
||||
cy.uiOpenTeamMenu('Manage Members');
|
||||
cy.uiOpenTeamMenu('Manage members');
|
||||
|
||||
// # Open member dropdown
|
||||
cy.get(`#teamMembersDropdown_${user.username}`).should('be.visible').click();
|
||||
@@ -94,7 +94,7 @@ describe('Manage Members', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click 'Manage Members'
|
||||
cy.uiOpenTeamMenu('Manage Members');
|
||||
cy.uiOpenTeamMenu('Manage members');
|
||||
|
||||
// # Open member dropdown
|
||||
cy.get(`#teamMembersDropdown_${user.username}`).should('be.visible').click();
|
||||
@@ -139,7 +139,7 @@ describe('Manage Members', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click 'Manage Members'
|
||||
cy.uiOpenTeamMenu('Manage Members');
|
||||
cy.uiOpenTeamMenu('Manage members');
|
||||
|
||||
// # Open member dropdown
|
||||
cy.get(`#teamMembersDropdown_${user.username}`).should('be.visible').click();
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('Teams Settings', () => {
|
||||
|
||||
function openTeamSettingsDialog() {
|
||||
// # Open team menu and click 'Team Settings'
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// * Verify the team settings dialog is open
|
||||
cy.get('#teamSettingsModalLabel').should('be.visible').and('contain', 'Team Settings');
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('Teams Suite', () => {
|
||||
|
||||
it('MM-T384 Team Admin can use Next button to page through list in Manage Members', () => {
|
||||
// # Open team menu and click "Manage Members"
|
||||
cy.uiOpenTeamMenu('Manage Members');
|
||||
cy.uiOpenTeamMenu('Manage members');
|
||||
|
||||
// * Check Manage Members modal dialog
|
||||
cy.get('#teamMemberModalLabel').should('be.visible');
|
||||
|
||||
@@ -41,7 +41,7 @@ describe('Teams Suite', () => {
|
||||
cy.url().should('include', `/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click "Manage Members"
|
||||
cy.uiOpenTeamMenu('Leave Team');
|
||||
cy.uiOpenTeamMenu('Leave team');
|
||||
|
||||
// * Check that the "leave team modal" opened up
|
||||
cy.get('#leaveTeamModal').should('be.visible');
|
||||
@@ -75,8 +75,8 @@ describe('Teams Suite', () => {
|
||||
|
||||
// # Open team menu and click "Invite People"
|
||||
cy.uiOpenTeamMenu();
|
||||
cy.uiGetLHSTeamMenu().findByText('Add people to the team');
|
||||
cy.uiGetLHSTeamMenu().findByText('Invite People').click().wait(TIMEOUTS.HALF_SEC);
|
||||
cy.uiGetLHSTeamMenu().find('div.label-elements > span:nth-child(2)').should('have.text', 'Add or invite people to the team');
|
||||
cy.uiGetLHSTeamMenu().find("li span:contains('Invite people')").should('have.text', 'Invite people').click().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Check that the Invitation Modal opened up
|
||||
cy.findByTestId('invitationModal', {timeout: TIMEOUTS.HALF_SEC}).should('be.visible');
|
||||
@@ -184,7 +184,7 @@ describe('Teams Suite', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click "Team Settings"
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// # Go to Access section
|
||||
cy.get('#accessButton').click();
|
||||
@@ -208,7 +208,7 @@ describe('Teams Suite', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click "Team Settings"
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// # Click on the team name menu item
|
||||
cy.findByText('Team Name').should('be.visible').click();
|
||||
@@ -236,7 +236,7 @@ describe('Teams Suite', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click "Team Settings"
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// # Change team description in the input
|
||||
cy.get('#teamDescription').should('be.visible').clear().type(teamDescription);
|
||||
@@ -246,7 +246,7 @@ describe('Teams Suite', () => {
|
||||
cy.uiSaveAndClose();
|
||||
|
||||
// # Open team menu and click "Team Settings"
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// * Verify team description is updated
|
||||
cy.get('#teamDescription').should('have.text', teamDescription);
|
||||
@@ -257,7 +257,7 @@ describe('Teams Suite', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click "Team Settings"
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// # Go to Access section
|
||||
cy.get('#accessButton').click();
|
||||
@@ -296,7 +296,7 @@ describe('Teams Suite', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click "Team Settings"
|
||||
cy.uiOpenTeamMenu('Team Settings');
|
||||
cy.uiOpenTeamMenu('Team settings');
|
||||
|
||||
// # Go to Access section
|
||||
cy.get('#accessButton').click();
|
||||
@@ -314,7 +314,7 @@ describe('Teams Suite', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click "Join Another Team"
|
||||
cy.uiOpenTeamMenu('Join Another Team');
|
||||
cy.uiOpenTeamMenu('Join another team');
|
||||
|
||||
// # Verify the original test team isn't on the list
|
||||
cy.get('.signup-team-dir').children().should('not.contain', `#${testTeam.name.charAt(0).toUpperCase() + testTeam.name.slice(1)}`);
|
||||
@@ -329,7 +329,7 @@ describe('Teams Suite', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open team menu and click "View Members"
|
||||
cy.uiOpenTeamMenu('View Members');
|
||||
cy.uiOpenTeamMenu('View members');
|
||||
cy.wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#searchUsersInput').should('be.visible').type('sysadmin');
|
||||
cy.get('.more-modal__list').should('be.visible').children().should('have.length', 1);
|
||||
@@ -341,7 +341,7 @@ function removeTeamMember(teamName, username) {
|
||||
cy.visit(`/${teamName}`);
|
||||
|
||||
// # Open team menu and click "Manage Members"
|
||||
cy.uiOpenTeamMenu('Manage Members');
|
||||
cy.uiOpenTeamMenu('Manage members');
|
||||
|
||||
cy.get(`#teamMembersDropdown_${username}`).should('be.visible').click();
|
||||
cy.get('#removeFromTeam').should('be.visible').click();
|
||||
|
||||
@@ -79,7 +79,7 @@ function getInvitePeopleLink(settings: TestSettings): ChainableT<any> {
|
||||
cy.checkLeftSideBar(settings);
|
||||
|
||||
// # Open team menu and click 'Invite People'
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
stubClipboard().as('clipboard');
|
||||
cy.checkInvitePeoplePage();
|
||||
|
||||
@@ -9,7 +9,7 @@ Cypress.Commands.add('uiGetLHS', () => {
|
||||
|
||||
Cypress.Commands.add('uiGetLHSHeader', () => {
|
||||
return cy.uiGetLHS().
|
||||
find('.SidebarHeaderMenuWrapper').
|
||||
find('#sidebarTeamMenuButton').
|
||||
should('be.visible');
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ Cypress.Commands.add('uiGetLHSAddChannelButton', () => {
|
||||
});
|
||||
|
||||
Cypress.Commands.add('uiGetLHSTeamMenu', () => {
|
||||
return cy.uiGetLHS().find('#sidebarDropdownMenu');
|
||||
return cy.get('#sidebarTeamMenu');
|
||||
});
|
||||
|
||||
function uiOpenSystemConsoleMenu(item = ''): ChainableT<JQuery> {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
function uiInviteMemberToCurrentTeam(username: string) {
|
||||
// # Open member invite screen
|
||||
cy.uiOpenTeamMenu('Invite People');
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
// # Open members section if licensed for guest accounts
|
||||
cy.findByTestId('invitationModal').
|
||||
|
||||
@@ -393,8 +393,8 @@ function getCurrentTeamURL(siteURL: string): ChainableT<string> {
|
||||
Cypress.Commands.add('getCurrentTeamURL', getCurrentTeamURL);
|
||||
|
||||
function leaveTeam() {
|
||||
// # Open team menu and click "Leave Team"
|
||||
cy.uiOpenTeamMenu('Leave Team');
|
||||
// # Open team menu and click "Leave team"
|
||||
cy.uiOpenTeamMenu('Leave team');
|
||||
|
||||
// * Check that the "leave team modal" opened up
|
||||
cy.get('#leaveTeamModal').should('be.visible');
|
||||
|
||||
@@ -5,7 +5,7 @@ import {Locator, Page} from '@playwright/test';
|
||||
export {waitUntil} from 'async-wait-until';
|
||||
|
||||
const visibilityHidden = 'visibility: hidden !important;';
|
||||
const hideTeamHeader = `.test-team-header {${visibilityHidden}} `;
|
||||
const hideTeamHeader = `#sidebarTeamMenuButton {${visibilityHidden}} `;
|
||||
const hidePostHeaderTime = `.post__time {${visibilityHidden}} `;
|
||||
const hidePostProfileIcon = `.profile-icon {${visibilityHidden}} `;
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"@guyplusplus/turndown-plugin-gfm": "1.0.7",
|
||||
"@mattermost/client": "*",
|
||||
"@mattermost/compass-components": "^0.2.12",
|
||||
"@mattermost/compass-icons": "0.1.39",
|
||||
"@mattermost/desktop-api": "5.10.0-2",
|
||||
"@mattermost/types": "*",
|
||||
"@mui/base": "5.0.0-alpha.127",
|
||||
|
||||
@@ -17,6 +17,7 @@ import MultiSelect from 'components/multiselect/multiselect';
|
||||
import type {Value} from 'components/multiselect/multiselect';
|
||||
|
||||
import groupsAvatar from 'images/groups-avatar.png';
|
||||
import {focusElement} from 'utils/a11y_utils';
|
||||
import Constants from 'utils/constants';
|
||||
|
||||
const GROUPS_PER_PAGE = 50;
|
||||
@@ -30,6 +31,7 @@ type Props = {
|
||||
intl: IntlShape;
|
||||
searchTerm: string;
|
||||
groups: Group[];
|
||||
focusOriginElement?: string;
|
||||
|
||||
// used in tandem with 'skipCommit' to allow using this component without performing actual linking
|
||||
excludeGroups?: Group[];
|
||||
@@ -114,6 +116,9 @@ export class AddGroupsToTeamModal extends React.PureComponent<Props, State> {
|
||||
|
||||
// public for tests
|
||||
public handleExit = (): void => {
|
||||
if (this.props.focusOriginElement) {
|
||||
focusElement(this.props.focusOriginElement, true);
|
||||
}
|
||||
this.props.onExited();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {DateTime} from 'luxon';
|
||||
import React from 'react';
|
||||
|
||||
import type {DeepPartial} from '@mattermost/types/utilities';
|
||||
@@ -22,12 +23,6 @@ function getBaseState(): DeepPartial<GlobalState> {
|
||||
type: 'D',
|
||||
name: 'current_user_id__teammate_user_id',
|
||||
},
|
||||
dm_near_timezone: {
|
||||
id: 'dm_same_timezone',
|
||||
teammate_id: 'teammate_near_timezone_id',
|
||||
type: 'D',
|
||||
name: 'current_user_id__teammate_near_timezone_id',
|
||||
},
|
||||
bot_dm_channel_id: {
|
||||
id: 'bot_dm_channel_id',
|
||||
teammate_id: 'bot_user_id',
|
||||
@@ -57,18 +52,6 @@ function getBaseState(): DeepPartial<GlobalState> {
|
||||
manualTimezone: '',
|
||||
},
|
||||
},
|
||||
teammate_near_timezone_id: {
|
||||
id: 'teammate_near_timezone_id',
|
||||
username: 'teammate_near_timezone_username',
|
||||
nickname: 'teammate_near_timezone_nickname',
|
||||
first_name: 'teammate_near_timezone_first_name',
|
||||
last_name: 'teammate_near_timezone_last_name',
|
||||
timezone: {
|
||||
useAutomaticTimezone: 'false',
|
||||
automaticTimezone: '',
|
||||
manualTimezone: 'CET',
|
||||
},
|
||||
},
|
||||
bot_user_id: {
|
||||
id: 'bot_user_id',
|
||||
username: 'bot_username',
|
||||
@@ -109,12 +92,13 @@ function getBaseState(): DeepPartial<GlobalState> {
|
||||
}
|
||||
|
||||
describe('useTimePostBoxIndicator', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2021-01-01T18:00:00Z').getTime());
|
||||
});
|
||||
|
||||
it('should pass base case', () => {
|
||||
const fakeLocal = DateTime.local(2025, 1, 1, 3, {
|
||||
zone: 'Asia/Kolkata',
|
||||
});
|
||||
|
||||
DateTime.local = jest.fn(() => fakeLocal);
|
||||
|
||||
const {result: {current}} = renderHookWithContext(() => useTimePostBoxIndicator('dm_channel_id'), getBaseState());
|
||||
|
||||
expect(current.isDM).toBe(true);
|
||||
@@ -127,20 +111,12 @@ describe('useTimePostBoxIndicator', () => {
|
||||
expect(current.teammateTimezone.automaticTimezone).toBe('IST');
|
||||
});
|
||||
|
||||
it('should not show if within working hours', () => {
|
||||
const {result: {current}} = renderHookWithContext(() => useTimePostBoxIndicator('dm_near_timezone'), getBaseState());
|
||||
|
||||
expect(current.isDM).toBe(true);
|
||||
expect(current.showDndWarning).toBe(false);
|
||||
expect(current.isSelfDM).toBe(false);
|
||||
expect(current.isBot).toBe(false);
|
||||
expect(current.showRemoteUserHour).toBe(false);
|
||||
expect(current.isScheduledPostEnabled).toBe(true);
|
||||
expect(current.teammateTimezone.useAutomaticTimezone).toBe(false);
|
||||
expect(current.teammateTimezone.manualTimezone).toBe('CET');
|
||||
});
|
||||
|
||||
it('should work for DM with bots', () => {
|
||||
const fakeLocal = DateTime.local(2025, 1, 1, 3, {
|
||||
zone: 'Asia/Kolkata',
|
||||
});
|
||||
|
||||
DateTime.local = jest.fn(() => fakeLocal);
|
||||
const {result: {current}} = renderHookWithContext(() => useTimePostBoxIndicator('bot_dm_channel_id'), getBaseState());
|
||||
|
||||
expect(current.isDM).toBe(true);
|
||||
@@ -155,6 +131,12 @@ describe('useTimePostBoxIndicator', () => {
|
||||
});
|
||||
|
||||
it('should handle teammate not loaded', () => {
|
||||
const fakeLocal = DateTime.local(2025, 1, 1, 1, {
|
||||
zone: 'Asia/Kolkata',
|
||||
});
|
||||
|
||||
DateTime.local = jest.fn(() => fakeLocal);
|
||||
|
||||
const {result: {current}} = renderHookWithContext(() => useTimePostBoxIndicator('unknown_dm_channel_id'), getBaseState());
|
||||
|
||||
expect(current.isDM).toBe(true);
|
||||
|
||||
@@ -14,7 +14,7 @@ import {IDENTIFIER_PATH_PATTERN, ID_PATH_PATTERN, TEAM_NAME_PATH_PATTERN} from '
|
||||
|
||||
import type {OwnProps, PropsFromRedux} from './index';
|
||||
|
||||
const ChannelHeaderMobile = makeAsyncComponent('ChannelHeaderMobile', lazy(() => import('components/channel_header_mobile')));
|
||||
const MobileChannelHeader = makeAsyncComponent('MobileChannelHeader', lazy(() => import('components/mobile_channel_header')));
|
||||
const GlobalThreads = makeAsyncComponent('GlobalThreads', lazy(() => import('components/threading/global_threads')),
|
||||
(
|
||||
<div className='app__content'>
|
||||
@@ -76,15 +76,7 @@ export default class CenterChannel extends React.PureComponent<Props, State> {
|
||||
'move--left-small': this.props.rhsMenuOpen,
|
||||
})}
|
||||
>
|
||||
{isMobileView && (
|
||||
<>
|
||||
<div className='row header'>
|
||||
<div id='navbar_wrapper'>
|
||||
<ChannelHeaderMobile/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{isMobileView && <MobileChannelHeader/>}
|
||||
<div className='row main'>
|
||||
<Switch>
|
||||
<Route
|
||||
|
||||
@@ -34,6 +34,10 @@ jest.mock('mattermost-redux/selectors/entities/general', () => ({
|
||||
...jest.requireActual('mattermost-redux/selectors/entities/general') as typeof import('mattermost-redux/selectors/entities/general'),
|
||||
}));
|
||||
|
||||
jest.mock('selectors/views/browser', () => ({
|
||||
getIsMobileView: () => false,
|
||||
}));
|
||||
|
||||
describe('ChannelController', () => {
|
||||
beforeEach(() => {
|
||||
mockState = {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {cleanUpStatusAndProfileFetchingPoll} from 'mattermost-redux/actions/stat
|
||||
import {getIsUserStatusesConfigEnabled} from 'mattermost-redux/selectors/entities/common';
|
||||
|
||||
import {addVisibleUsersInCurrentChannelAndSelfToStatusPoll} from 'actions/status_actions';
|
||||
import {getIsMobileView} from 'selectors/views/browser';
|
||||
|
||||
import {makeAsyncComponent} from 'components/async_load';
|
||||
import CenterChannel from 'components/channel_layout/center_channel';
|
||||
@@ -24,6 +25,7 @@ import {isInternetExplorer, isEdge} from 'utils/user_agent';
|
||||
|
||||
const ProductNoticesModal = makeAsyncComponent('ProductNoticesModal', lazy(() => import('components/product_notices_modal')));
|
||||
const ResetStatusModal = makeAsyncComponent('ResetStatusModal', lazy(() => import('components/reset_status_modal')));
|
||||
const MobileSidebarRight = makeAsyncComponent('MobileSidebarRight', lazy(() => import('components/mobile_sidebar_right')));
|
||||
|
||||
const BODY_CLASS_FOR_CHANNEL = ['app__body', 'channel-view'];
|
||||
|
||||
@@ -32,6 +34,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export default function ChannelController(props: Props) {
|
||||
const isMobileView = useSelector(getIsMobileView);
|
||||
const enabledUserStatuses = useSelector(getIsUserStatusesConfigEnabled);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -85,6 +88,7 @@ export default function ChannelController(props: Props) {
|
||||
<ResetStatusModal/>
|
||||
</div>
|
||||
</div>
|
||||
{isMobileView && <MobileSidebarRight/>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ export type Props = {
|
||||
initialValue?: string;
|
||||
inviteAsGuest?: boolean;
|
||||
roleForTrackFlow: {started_by_role: string};
|
||||
focusOriginElement: string;
|
||||
focusOriginElement?: string;
|
||||
}
|
||||
|
||||
export const View = {
|
||||
@@ -132,7 +132,9 @@ export default class InvitationModal extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
handleExit = () => {
|
||||
focusElement(this.props.focusOriginElement, true);
|
||||
if (this.props.focusOriginElement) {
|
||||
focusElement(this.props.focusOriginElement, true);
|
||||
}
|
||||
this.props.onExited?.();
|
||||
};
|
||||
|
||||
|
||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -1,40 +0,0 @@
|
||||
.LearnAboutTeamsLink {
|
||||
>span {
|
||||
width: 100%;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 18px;
|
||||
column-gap: 10px;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
>i {
|
||||
display: inline-flex !important;
|
||||
padding: 3px;
|
||||
border-radius: 50%;
|
||||
background-color: hsla(221, 77%, 48%, 0.08);
|
||||
font-size: 14px !important;
|
||||
|
||||
&::before {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// 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 ExternalLink from 'components/external_link';
|
||||
import './learn_about_teams_link.scss';
|
||||
|
||||
const LearnAboutTeamsLink = () => {
|
||||
return (
|
||||
<div className='LearnAboutTeamsLink'>
|
||||
<FormattedMessage
|
||||
id='learn_about_teams'
|
||||
defaultMessage='<a>Learn about teams</a>'
|
||||
values={{
|
||||
a: (chunks) => (
|
||||
<ExternalLink
|
||||
location='learn_about_teams'
|
||||
href='https://mattermost.com/pl/mattermost-academy-team-training'
|
||||
>
|
||||
<i className='icon icon-lightbulb-outline'/>
|
||||
<span>{chunks}</span>
|
||||
</ExternalLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default LearnAboutTeamsLink;
|
||||
@@ -1,13 +0,0 @@
|
||||
.MainMenu_dropdown-link {
|
||||
a {
|
||||
&:focus,
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: rgba(63, 67, 80, 0.1);
|
||||
}
|
||||
|
||||
i + span:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import type {ComponentProps} from 'react';
|
||||
import React from 'react';
|
||||
import {createIntl} from 'react-intl';
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
import {Permissions} from 'mattermost-redux/constants';
|
||||
|
||||
import Menu from 'components/widgets/menu/menu';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import mockStore from 'tests/test_store';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import {MainMenu} from './main_menu';
|
||||
import type {Props} from './main_menu';
|
||||
|
||||
describe('components/Menu', () => {
|
||||
// Neccessary for components enhanced by HOCs due to issue with enzyme.
|
||||
// See https://github.com/enzymejs/enzyme/issues/539
|
||||
const getMainMenuWrapper = (props: Props) => {
|
||||
return shallow(<MainMenu {...props}/>);
|
||||
|
||||
// const wrapper = shallowWithIntl(<MainMenu {...props}/>);
|
||||
// return wrapper.find('MainMenu').shallow();
|
||||
};
|
||||
|
||||
const defaultProps: ComponentProps<typeof MainMenu> = {
|
||||
mobile: false,
|
||||
teamId: 'team-id',
|
||||
teamName: 'team_name',
|
||||
currentUser: TestHelper.getUserMock(),
|
||||
appDownloadLink: undefined,
|
||||
enableCommands: false,
|
||||
enableIncomingWebhooks: false,
|
||||
enableOAuthServiceProvider: false,
|
||||
enableOutgoingWebhooks: false,
|
||||
canManageSystemBots: false,
|
||||
canManageIntegrations: true,
|
||||
experimentalPrimaryTeam: undefined,
|
||||
helpLink: undefined,
|
||||
reportAProblemLink: undefined,
|
||||
moreTeamsToJoin: false,
|
||||
pluginMenuItems: [],
|
||||
isMentionSearch: false,
|
||||
intl: createIntl({locale: 'en', defaultLocale: 'en', timeZone: 'Etc/UTC', textComponent: 'span'}),
|
||||
guestAccessEnabled: true,
|
||||
canInviteTeamMember: true,
|
||||
actions: {
|
||||
openModal: jest.fn(),
|
||||
showMentions: jest.fn(),
|
||||
showFlaggedPosts: jest.fn(),
|
||||
closeRightHandSide: jest.fn(),
|
||||
closeRhsMenu: jest.fn(),
|
||||
},
|
||||
teamIsGroupConstrained: false,
|
||||
isCloud: false,
|
||||
isStarterFree: false,
|
||||
isFreeTrial: false,
|
||||
usageDeltaTeams: 1,
|
||||
};
|
||||
|
||||
const defaultState = {
|
||||
entities: {
|
||||
channels: {
|
||||
myMembers: {},
|
||||
},
|
||||
general: {
|
||||
config: {},
|
||||
license: {
|
||||
Cloud: 'false',
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: 'team-id',
|
||||
myMembers: {
|
||||
'team-id': {
|
||||
team_id: 'team-id',
|
||||
user_id: 'test-user-id',
|
||||
roles: 'team_user',
|
||||
scheme_user: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
users: {
|
||||
currentUserId: 'test-user-id',
|
||||
profiles: {
|
||||
'test-user-id': {
|
||||
id: 'test-user-id',
|
||||
roles: 'system_user system_manager',
|
||||
},
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
roles: {
|
||||
system_manager: {
|
||||
permissions: [
|
||||
Permissions.SYSCONSOLE_WRITE_PLUGINS,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test('should match snapshot with id', () => {
|
||||
const props = {...defaultProps, id: 'test-id'};
|
||||
const wrapper = getMainMenuWrapper(props);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with most of the thing disabled', () => {
|
||||
const wrapper = getMainMenuWrapper(defaultProps);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with most of the thing disabled in mobile', () => {
|
||||
const props = {...defaultProps, mobile: true};
|
||||
const wrapper = getMainMenuWrapper(props);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with most of the thing enabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
appDownloadLink: 'test',
|
||||
enableCommands: true,
|
||||
enableCustomEmoji: true,
|
||||
canCreateOrDeleteCustomEmoji: true,
|
||||
enableIncomingWebhooks: true,
|
||||
enableOAuthServiceProvider: true,
|
||||
enableOutgoingWebhooks: true,
|
||||
enableUserCreation: true,
|
||||
enableEmailInvitations: true,
|
||||
enablePluginMarketplace: true,
|
||||
experimentalPrimaryTeam: 'test',
|
||||
helpLink: 'test-link-help',
|
||||
reportAProblemLink: 'test-report-link',
|
||||
moreTeamsToJoin: true,
|
||||
};
|
||||
const wrapper = getMainMenuWrapper(props);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with most of the thing enabled in mobile', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
mobile: true,
|
||||
appDownloadLink: 'test',
|
||||
enableCommands: true,
|
||||
enableCustomEmoji: true,
|
||||
canCreateOrDeleteCustomEmoji: true,
|
||||
enableIncomingWebhooks: true,
|
||||
enableOAuthServiceProvider: true,
|
||||
enableOutgoingWebhooks: true,
|
||||
enableUserCreation: true,
|
||||
enableEmailInvitations: true,
|
||||
enablePluginMarketplace: true,
|
||||
experimentalPrimaryTeam: 'test',
|
||||
helpLink: 'test-link-help',
|
||||
reportAProblemLink: 'test-report-link',
|
||||
moreTeamsToJoin: true,
|
||||
};
|
||||
const wrapper = getMainMenuWrapper(props);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with plugins', () => {
|
||||
const props: ComponentProps<typeof MainMenu> = {
|
||||
...defaultProps,
|
||||
pluginMenuItems: [{
|
||||
id: 'plugin-id-1',
|
||||
pluginId: 'plugin-1',
|
||||
mobileIcon: <i className='fa fa-anchor'/>,
|
||||
action: jest.fn,
|
||||
text: 'some text',
|
||||
},
|
||||
{
|
||||
id: 'plugind-id-2',
|
||||
pluginId: 'plugin-2',
|
||||
mobileIcon: <i className='fa fa-anchor'/>,
|
||||
action: jest.fn,
|
||||
text: 'some text',
|
||||
},
|
||||
],
|
||||
};
|
||||
const wrapper = getMainMenuWrapper(props);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with plugins in mobile', () => {
|
||||
const props: ComponentProps<typeof MainMenu> = {
|
||||
...defaultProps,
|
||||
mobile: true,
|
||||
pluginMenuItems: [{
|
||||
id: 'plugin-id-1',
|
||||
pluginId: 'plugin-1',
|
||||
mobileIcon: <i className='fa fa-anchor'/>,
|
||||
action: jest.fn,
|
||||
text: 'some text',
|
||||
},
|
||||
{
|
||||
id: 'plugind-id-2',
|
||||
pluginId: 'plugin-2',
|
||||
mobileIcon: <i className='fa fa-anchor'/>,
|
||||
action: jest.fn,
|
||||
text: 'some text',
|
||||
},
|
||||
],
|
||||
};
|
||||
const wrapper = getMainMenuWrapper(props);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should show leave team option when primary team is not set', () => {
|
||||
const props = {...defaultProps, teamIsGroupConstrained: false, experimentalPrimaryTeam: undefined};
|
||||
const wrapper = getMainMenuWrapper(props);
|
||||
|
||||
// show leave team option when experimentalPrimaryTeam is not set
|
||||
expect(wrapper.find('#leaveTeam')).toHaveLength(1);
|
||||
expect(wrapper.find('#leaveTeam').find(Menu.ItemToggleModalRedux).props().show).toEqual(true);
|
||||
});
|
||||
|
||||
test('should hide leave team option when experimentalPrimaryTeam is same as current team', () => {
|
||||
const props = {...defaultProps, teamIsGroupConstrained: false};
|
||||
const wrapper = getMainMenuWrapper(props);
|
||||
expect(wrapper.find('#leaveTeam')).toHaveLength(1);
|
||||
expect(wrapper.find('#leaveTeam').find(Menu.ItemToggleModalRedux).props().show).toEqual(true);
|
||||
});
|
||||
|
||||
test('should hide leave team option when experimentalPrimaryTeam is same as current team', () => {
|
||||
const props = {...defaultProps, teamIsGroupConstrained: false, experimentalPrimaryTeam: 'other-team'};
|
||||
const wrapper = getMainMenuWrapper(props);
|
||||
expect(wrapper.find('#leaveTeam')).toHaveLength(1);
|
||||
expect(wrapper.find('#leaveTeam').find(Menu.ItemToggleModalRedux).props().show).toEqual(true);
|
||||
});
|
||||
|
||||
test('mobile view should hide the subscribe now button when does not have permissions', () => {
|
||||
const noPermissionsState = {...defaultState};
|
||||
noPermissionsState.entities.roles.roles.system_manager.permissions = [];
|
||||
const store = mockStore(noPermissionsState);
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<MainMenu {...defaultProps}/>
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('UpgradeLink')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('mobile view should hide start trial menu item because user state does not have permission to write license', () => {
|
||||
const store = mockStore(defaultState);
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<Provider store={store}>
|
||||
<MainMenu {...defaultProps}/>
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('#startTrial')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should match snapshot with guest access disabled and no team invite permission', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
guestAccessEnabled: false,
|
||||
canInviteTeamMember: false,
|
||||
};
|
||||
const wrapper = getMainMenuWrapper(props);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with cloud free trial', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
isCloud: true,
|
||||
isStarterFree: false,
|
||||
isFreeTrial: true,
|
||||
usageDeltaTeams: -1,
|
||||
};
|
||||
const wrapper = getMainMenuWrapper(props);
|
||||
expect(wrapper.find('#createTeam')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with cloud free and team limit reached', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
isCloud: true,
|
||||
isStarterFree: true,
|
||||
isFreeTrial: false,
|
||||
usageDeltaTeams: 0,
|
||||
};
|
||||
const wrapper = getMainMenuWrapper(props);
|
||||
expect(wrapper.find('#createTeam')).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,538 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {injectIntl} from 'react-intl';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {Permissions} from 'mattermost-redux/constants';
|
||||
|
||||
import * as GlobalActions from 'actions/global_actions';
|
||||
import {trackEvent} from 'actions/telemetry_actions';
|
||||
|
||||
import AboutBuildModal from 'components/about_build_modal';
|
||||
import AddGroupsToTeamModal from 'components/add_groups_to_team_modal';
|
||||
import InvitationModal from 'components/invitation_modal';
|
||||
import LeaveTeamModal from 'components/leave_team_modal';
|
||||
import SystemPermissionGate from 'components/permissions_gates/system_permission_gate';
|
||||
import TeamPermissionGate from 'components/permissions_gates/team_permission_gate';
|
||||
import TeamGroupsManageModal from 'components/team_groups_manage_modal';
|
||||
import TeamMembersModal from 'components/team_members_modal';
|
||||
import TeamSettingsModal from 'components/team_settings_modal';
|
||||
import UserSettingsModal from 'components/user_settings/modal';
|
||||
import LeaveTeamIcon from 'components/widgets/icons/leave_team_icon';
|
||||
import Menu from 'components/widgets/menu/menu';
|
||||
import RestrictedIndicator from 'components/widgets/menu/menu_items/restricted_indicator';
|
||||
|
||||
import {FREEMIUM_TO_ENTERPRISE_TRIAL_LENGTH_DAYS} from 'utils/cloud_utils';
|
||||
import {Constants, LicenseSkus, ModalIdentifiers, MattermostFeatures} from 'utils/constants';
|
||||
import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard';
|
||||
import {makeUrlSafe} from 'utils/url';
|
||||
import * as UserAgent from 'utils/user_agent';
|
||||
|
||||
import type {ModalData} from 'types/actions';
|
||||
import type {MainMenuAction} from 'types/store/plugins';
|
||||
|
||||
import LearnAboutTeamsLink from './learn_about_teams_link';
|
||||
import './main_menu.scss';
|
||||
|
||||
export type Props = {
|
||||
mobile: boolean;
|
||||
id?: string;
|
||||
teamId?: string;
|
||||
teamName?: string;
|
||||
siteName?: string;
|
||||
currentUser?: UserProfile;
|
||||
appDownloadLink?: string;
|
||||
enableCommands: boolean;
|
||||
enableIncomingWebhooks: boolean;
|
||||
enableOAuthServiceProvider: boolean;
|
||||
enableOutgoingWebhooks: boolean;
|
||||
canManageSystemBots: boolean;
|
||||
canManageIntegrations: boolean;
|
||||
experimentalPrimaryTeam?: string;
|
||||
helpLink?: string;
|
||||
reportAProblemLink?: string;
|
||||
moreTeamsToJoin: boolean;
|
||||
pluginMenuItems?: MainMenuAction[];
|
||||
isMentionSearch?: boolean;
|
||||
teamIsGroupConstrained: boolean;
|
||||
isLicensedForLDAPGroups?: boolean;
|
||||
intl: IntlShape;
|
||||
isCloud: boolean;
|
||||
isStarterFree: boolean;
|
||||
isFreeTrial: boolean;
|
||||
usageDeltaTeams: number;
|
||||
guestAccessEnabled: boolean;
|
||||
canInviteTeamMember: boolean;
|
||||
actions: {
|
||||
openModal: <P>(modalData: ModalData<P>) => void;
|
||||
showMentions: () => void;
|
||||
showFlaggedPosts: () => void;
|
||||
closeRightHandSide: () => void;
|
||||
closeRhsMenu: () => void;
|
||||
};
|
||||
|
||||
};
|
||||
export class MainMenu extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
teamType: '',
|
||||
mobile: false,
|
||||
pluginMenuItems: [],
|
||||
};
|
||||
|
||||
async componentDidMount(): Promise<void> {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
handleKeyDown = (e: KeyboardEvent): void => {
|
||||
if (cmdOrCtrlPressed(e) && e.shiftKey && isKeyPressed(e, Constants.KeyCodes.A)) {
|
||||
e.preventDefault();
|
||||
this.props.actions.openModal({modalId: ModalIdentifiers.USER_SETTINGS, dialogType: UserSettingsModal, dialogProps: {isContentProductSettings: true, focusOriginElement: 'userAccountMenuButton'}});
|
||||
}
|
||||
};
|
||||
|
||||
handleEmitUserLoggedOutEvent = (): void => {
|
||||
GlobalActions.emitUserLoggedOutEvent();
|
||||
};
|
||||
|
||||
getFlagged = (e: Event): void => {
|
||||
e.preventDefault();
|
||||
this.props.actions.showFlaggedPosts();
|
||||
this.props.actions.closeRhsMenu();
|
||||
};
|
||||
|
||||
searchMentions = (e: Event): void => {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.props.isMentionSearch) {
|
||||
this.props.actions.closeRightHandSide();
|
||||
} else {
|
||||
this.props.actions.closeRhsMenu();
|
||||
this.props.actions.showMentions();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
appDownloadLink,
|
||||
currentUser,
|
||||
teamIsGroupConstrained,
|
||||
isLicensedForLDAPGroups,
|
||||
teamId = '',
|
||||
guestAccessEnabled,
|
||||
canInviteTeamMember,
|
||||
} = this.props;
|
||||
|
||||
const safeAppDownloadLink = makeUrlSafe(appDownloadLink || '');
|
||||
|
||||
if (!currentUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pluginItems = this.props.pluginMenuItems?.map((item) => (
|
||||
<Menu.ItemAction
|
||||
id={item.id + '_pluginmenuitem'}
|
||||
key={item.id + '_pluginmenuitem'}
|
||||
onClick={() => {
|
||||
if (item.action) {
|
||||
item.action();
|
||||
}
|
||||
}}
|
||||
text={item.text}
|
||||
icon={this.props.mobile && item.mobileIcon}
|
||||
/>
|
||||
));
|
||||
|
||||
const someIntegrationEnabled = this.props.enableIncomingWebhooks || this.props.enableOutgoingWebhooks || this.props.enableCommands || this.props.enableOAuthServiceProvider || this.props.canManageSystemBots;
|
||||
const showIntegrations = !this.props.mobile && someIntegrationEnabled && this.props.canManageIntegrations;
|
||||
const teamsLimitReached = this.props.isStarterFree && !this.props.isFreeTrial && this.props.usageDeltaTeams >= 0;
|
||||
const createTeamRestricted = this.props.isCloud && (this.props.isFreeTrial || teamsLimitReached);
|
||||
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
let invitePeopleModal = null;
|
||||
if (guestAccessEnabled || canInviteTeamMember) {
|
||||
invitePeopleModal = (
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='invitePeople'
|
||||
modalId={ModalIdentifiers.INVITATION}
|
||||
dialogType={InvitationModal}
|
||||
text={formatMessage({
|
||||
id: 'navbar_dropdown.invitePeople',
|
||||
defaultMessage: 'Invite People',
|
||||
})}
|
||||
extraText={formatMessage({
|
||||
id: 'navbar_dropdown.invitePeopleExtraText',
|
||||
defaultMessage: 'Add people to the team',
|
||||
})}
|
||||
icon={this.props.mobile && <i className='fa fa-user-plus'/>}
|
||||
onClick={() => trackEvent('ui', 'click_sidebar_team_dropdown_invite_people')}
|
||||
dialogProps={{focusOriginElement: 'sidebarDropdownMenuButton'}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.mobile ? (
|
||||
<Menu
|
||||
id={this.props.id}
|
||||
ariaLabel={formatMessage({id: 'navbar_dropdown.menuAriaLabel', defaultMessage: 'main menu'})}
|
||||
>
|
||||
<Menu.Group>
|
||||
<SystemPermissionGate
|
||||
permissions={[Permissions.SYSCONSOLE_WRITE_BILLING]}
|
||||
>
|
||||
<Menu.CloudTrial
|
||||
id='menuCloudTrial'
|
||||
/>
|
||||
</SystemPermissionGate>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<SystemPermissionGate
|
||||
permissions={[Permissions.SYSCONSOLE_WRITE_ABOUT_EDITION_AND_LICENSE]}
|
||||
>
|
||||
<Menu.StartTrial
|
||||
id='startTrial'
|
||||
/>
|
||||
</SystemPermissionGate>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<Menu.ItemAction
|
||||
id='recentMentions'
|
||||
onClick={this.searchMentions}
|
||||
icon={<i className='mentions'>{'@'}</i>}
|
||||
text={formatMessage({id: 'sidebar_right_menu.recentMentions', defaultMessage: 'Recent Mentions'})}
|
||||
/>
|
||||
<Menu.ItemAction
|
||||
id='flaggedPosts'
|
||||
onClick={this.getFlagged}
|
||||
icon={<i className='fa fa-bookmark'/>}
|
||||
text={formatMessage({id: 'sidebar_right_menu.flagged', defaultMessage: 'Saved messages'})}
|
||||
/>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='profileSettings'
|
||||
modalId={ModalIdentifiers.USER_SETTINGS}
|
||||
dialogType={UserSettingsModal}
|
||||
dialogProps={{isContentProductSettings: false, focusOriginElement: 'userAccountMenuButton2'}}
|
||||
text={formatMessage({id: 'navbar_dropdown.profileSettings', defaultMessage: 'Profile'})}
|
||||
icon={<i className='fa fa-user'/>}
|
||||
/>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='accountSettings'
|
||||
modalId={ModalIdentifiers.USER_SETTINGS}
|
||||
dialogType={UserSettingsModal}
|
||||
dialogProps={{isContentProductSettings: true, focusOriginElement: 'userAccountMenuButton3'}}
|
||||
text={formatMessage({id: 'navbar_dropdown.accountSettings', defaultMessage: 'Settings'})}
|
||||
icon={<i className='fa fa-cog'/>}
|
||||
/>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<TeamPermissionGate
|
||||
teamId={teamId}
|
||||
permissions={[Permissions.MANAGE_TEAM]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='addGroupsToTeam'
|
||||
show={teamIsGroupConstrained && isLicensedForLDAPGroups}
|
||||
modalId={ModalIdentifiers.ADD_GROUPS_TO_TEAM}
|
||||
dialogType={AddGroupsToTeamModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.addGroupsToTeam', defaultMessage: 'Add Groups to Team'})}
|
||||
icon={<i className='fa fa-user-plus'/>}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
<TeamPermissionGate
|
||||
teamId={teamId}
|
||||
permissions={[Permissions.ADD_USER_TO_TEAM, Permissions.INVITE_GUEST]}
|
||||
>
|
||||
{invitePeopleModal}
|
||||
</TeamPermissionGate>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<TeamPermissionGate
|
||||
teamId={teamId}
|
||||
permissions={[Permissions.MANAGE_TEAM]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='teamSettings'
|
||||
modalId={ModalIdentifiers.TEAM_SETTINGS}
|
||||
dialogType={TeamSettingsModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.teamSettings', defaultMessage: 'Team Settings'})}
|
||||
icon={<i className='fa fa-globe'/>}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
<TeamPermissionGate
|
||||
teamId={teamId}
|
||||
permissions={[Permissions.MANAGE_TEAM]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='manageGroups'
|
||||
show={teamIsGroupConstrained && isLicensedForLDAPGroups}
|
||||
modalId={ModalIdentifiers.MANAGE_TEAM_GROUPS}
|
||||
dialogProps={{
|
||||
teamID: teamId,
|
||||
}}
|
||||
dialogType={TeamGroupsManageModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.manageGroups', defaultMessage: 'Manage Groups'})}
|
||||
icon={<i className='fa fa-user-plus'/>}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
<TeamPermissionGate
|
||||
teamId={teamId}
|
||||
permissions={[Permissions.REMOVE_USER_FROM_TEAM, Permissions.MANAGE_TEAM_ROLES]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='manageMembers'
|
||||
modalId={ModalIdentifiers.TEAM_MEMBERS}
|
||||
dialogType={TeamMembersModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.manageMembers', defaultMessage: 'Manage Members'})}
|
||||
icon={<i className='fa fa-users'/>}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
<TeamPermissionGate
|
||||
teamId={teamId}
|
||||
permissions={[Permissions.REMOVE_USER_FROM_TEAM, Permissions.MANAGE_TEAM_ROLES]}
|
||||
invert={true}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='viewMembers'
|
||||
modalId={ModalIdentifiers.TEAM_MEMBERS}
|
||||
dialogType={TeamMembersModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.viewMembers', defaultMessage: 'View Members'})}
|
||||
icon={<i className='fa fa-users'/>}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<SystemPermissionGate permissions={[Permissions.CREATE_TEAM]}>
|
||||
<Menu.ItemLink
|
||||
id='createTeam'
|
||||
show={!teamsLimitReached}
|
||||
to='/create_team'
|
||||
text={formatMessage({id: 'navbar_dropdown.create', defaultMessage: 'Create a Team'})}
|
||||
icon={<i className='fa fa-plus-square'/>}
|
||||
/>
|
||||
</SystemPermissionGate>
|
||||
<Menu.ItemLink
|
||||
id='joinTeam'
|
||||
show={!this.props.experimentalPrimaryTeam && this.props.moreTeamsToJoin}
|
||||
to='/select_team'
|
||||
text={formatMessage({id: 'navbar_dropdown.join', defaultMessage: 'Join Another Team'})}
|
||||
icon={<i className='fa fa-plus-square'/>}
|
||||
/>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='leaveTeam'
|
||||
show={!teamIsGroupConstrained && this.props.experimentalPrimaryTeam !== this.props.teamName}
|
||||
modalId={ModalIdentifiers.LEAVE_TEAM}
|
||||
dialogType={LeaveTeamModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.leave', defaultMessage: 'Leave Team'})}
|
||||
icon={<LeaveTeamIcon/>}
|
||||
/>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
{pluginItems}
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<Menu.ItemLink
|
||||
id='integrations'
|
||||
show={showIntegrations}
|
||||
to={'/' + this.props.teamName + '/integrations'}
|
||||
text={formatMessage({id: 'navbar_dropdown.integrations', defaultMessage: 'Integrations'})}
|
||||
/>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<Menu.ItemExternalLink
|
||||
id='helpLink'
|
||||
show={Boolean(this.props.helpLink)}
|
||||
url={this.props.helpLink}
|
||||
text={formatMessage({id: 'navbar_dropdown.help', defaultMessage: 'Help'})}
|
||||
icon={<i className='fa fa-question'/>}
|
||||
/>
|
||||
<Menu.ItemExternalLink
|
||||
id='reportLink'
|
||||
show={Boolean(this.props.reportAProblemLink)}
|
||||
url={this.props.reportAProblemLink}
|
||||
text={formatMessage({id: 'navbar_dropdown.report', defaultMessage: 'Report a Problem'})}
|
||||
icon={<i className='fa fa-phone'/>}
|
||||
/>
|
||||
<Menu.ItemExternalLink
|
||||
id='nativeAppLink'
|
||||
show={this.props.appDownloadLink && !UserAgent.isMobileApp()}
|
||||
url={safeAppDownloadLink}
|
||||
text={formatMessage({id: 'navbar_dropdown.nativeApps', defaultMessage: 'Download Apps'})}
|
||||
icon={<i className='fa fa-mobile'/>}
|
||||
/>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='about'
|
||||
modalId={ModalIdentifiers.ABOUT}
|
||||
dialogType={AboutBuildModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.about', defaultMessage: 'About {appTitle}'}, {appTitle: this.props.siteName || 'Mattermost'})}
|
||||
icon={<i className='fa fa-info'/>}
|
||||
/>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<Menu.ItemAction
|
||||
id='logout'
|
||||
onClick={this.handleEmitUserLoggedOutEvent}
|
||||
text={formatMessage({id: 'navbar_dropdown.logout', defaultMessage: 'Log Out'})}
|
||||
icon={<i className='fa fa-sign-out'/>}
|
||||
/>
|
||||
</Menu.Group>
|
||||
</Menu>
|
||||
) : (
|
||||
<Menu
|
||||
id={this.props.id}
|
||||
ariaLabel={formatMessage({id: 'sidebar.team_menu.menuAriaLabel', defaultMessage: 'team menu'})}
|
||||
>
|
||||
<Menu.Group>
|
||||
<TeamPermissionGate
|
||||
teamId={teamId}
|
||||
permissions={[Permissions.MANAGE_TEAM]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='addGroupsToTeam'
|
||||
show={teamIsGroupConstrained && isLicensedForLDAPGroups}
|
||||
modalId={ModalIdentifiers.ADD_GROUPS_TO_TEAM}
|
||||
dialogType={AddGroupsToTeamModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.addGroupsToTeam', defaultMessage: 'Add Groups to Team'})}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
<TeamPermissionGate
|
||||
teamId={teamId}
|
||||
permissions={[Permissions.ADD_USER_TO_TEAM, Permissions.INVITE_GUEST]}
|
||||
>
|
||||
{invitePeopleModal}
|
||||
</TeamPermissionGate>
|
||||
<TeamPermissionGate
|
||||
teamId={teamId}
|
||||
permissions={[Permissions.MANAGE_TEAM]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='teamSettings'
|
||||
modalId={ModalIdentifiers.TEAM_SETTINGS}
|
||||
dialogType={TeamSettingsModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.teamSettings', defaultMessage: 'Team Settings'})}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
<TeamPermissionGate
|
||||
teamId={teamId}
|
||||
permissions={[Permissions.MANAGE_TEAM]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='manageGroups'
|
||||
show={teamIsGroupConstrained && isLicensedForLDAPGroups}
|
||||
modalId={ModalIdentifiers.MANAGE_TEAM_GROUPS}
|
||||
dialogProps={{teamID: teamId}}
|
||||
dialogType={TeamGroupsManageModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.manageGroups', defaultMessage: 'Manage Groups'})}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
<TeamPermissionGate
|
||||
teamId={teamId}
|
||||
permissions={[Permissions.REMOVE_USER_FROM_TEAM, Permissions.MANAGE_TEAM_ROLES]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='manageMembers'
|
||||
modalId={ModalIdentifiers.TEAM_MEMBERS}
|
||||
dialogType={TeamMembersModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.manageMembers', defaultMessage: 'Manage Members'})}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
<TeamPermissionGate
|
||||
teamId={teamId}
|
||||
permissions={[Permissions.REMOVE_USER_FROM_TEAM, Permissions.MANAGE_TEAM_ROLES]}
|
||||
invert={true}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='viewMembers'
|
||||
modalId={ModalIdentifiers.TEAM_MEMBERS}
|
||||
dialogType={TeamMembersModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.viewMembers', defaultMessage: 'View Members'})}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
<Menu.ItemLink
|
||||
id='joinTeam'
|
||||
show={!this.props.experimentalPrimaryTeam && this.props.moreTeamsToJoin}
|
||||
to='/select_team'
|
||||
text={formatMessage({id: 'navbar_dropdown.join', defaultMessage: 'Join Another Team'})}
|
||||
/>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='leaveTeam'
|
||||
className='destructive'
|
||||
show={!teamIsGroupConstrained && this.props.experimentalPrimaryTeam !== this.props.teamName}
|
||||
modalId={ModalIdentifiers.LEAVE_TEAM}
|
||||
dialogType={LeaveTeamModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.leave', defaultMessage: 'Leave Team'})}
|
||||
/>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<SystemPermissionGate permissions={[Permissions.CREATE_TEAM]}>
|
||||
<Menu.ItemLink
|
||||
id='createTeam'
|
||||
to='/create_team'
|
||||
className={createTeamRestricted ? 'MenuItem__with-icon-tooltip' : ''}
|
||||
disabled={teamsLimitReached}
|
||||
text={formatMessage({id: 'navbar_dropdown.create', defaultMessage: 'Create a Team'})}
|
||||
sibling={createTeamRestricted && (
|
||||
<RestrictedIndicator
|
||||
feature={MattermostFeatures.CREATE_MULTIPLE_TEAMS}
|
||||
minimumPlanRequiredForFeature={LicenseSkus.Professional}
|
||||
blocked={!this.props.isFreeTrial}
|
||||
tooltipMessage={formatMessage({
|
||||
id: 'navbar_dropdown.create.tooltip.cloudFreeTrial',
|
||||
defaultMessage: 'During your trial you are able to create multiple teams. These teams will be archived after your trial.',
|
||||
})}
|
||||
titleAdminPreTrial={formatMessage({
|
||||
id: 'navbar_dropdown.create.modal.titleAdminPreTrial',
|
||||
defaultMessage: 'Try unlimited teams with a free trial',
|
||||
})}
|
||||
messageAdminPreTrial={formatMessage({
|
||||
id: 'navbar_dropdown.create.modal.messageAdminPreTrial',
|
||||
defaultMessage: 'Create unlimited teams with one of our paid plans. Get the full experience of Enterprise when you start a free, {trialLength} day trial.',
|
||||
},
|
||||
{
|
||||
trialLength: FREEMIUM_TO_ENTERPRISE_TRIAL_LENGTH_DAYS,
|
||||
},
|
||||
)}
|
||||
titleAdminPostTrial={formatMessage({
|
||||
id: 'navbar_dropdown.create.modal.titleAdminPostTrial',
|
||||
defaultMessage: 'Upgrade to create unlimited teams',
|
||||
})}
|
||||
messageAdminPostTrial={formatMessage({
|
||||
id: 'navbar_dropdown.create.modal.messageAdminPostTrial',
|
||||
defaultMessage: 'Multiple teams allow for context-specific spaces that are more attuned to your and your teams’ needs. Upgrade to the Professional plan to create unlimited teams.',
|
||||
})}
|
||||
titleEndUser={formatMessage({
|
||||
id: 'navbar_dropdown.create.modal.titleEndUser',
|
||||
defaultMessage: 'Multiple teams available in paid plans',
|
||||
})}
|
||||
messageEndUser={formatMessage({
|
||||
id: 'navbar_dropdown.create.modal.messageEndUser',
|
||||
defaultMessage: 'Multiple teams allow for context-specific spaces that are more attuned to your teams’ needs.',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</SystemPermissionGate>
|
||||
<Menu.Group>
|
||||
<div className='MainMenu_dropdown-link'>
|
||||
<LearnAboutTeamsLink/>
|
||||
</div>
|
||||
</Menu.Group>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
{pluginItems}
|
||||
</Menu.Group>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(MainMenu);
|
||||
@@ -10,6 +10,7 @@
|
||||
padding: 4px 0;
|
||||
background-color: var(--center-channel-bg);
|
||||
box-shadow: var(--elevation-4), 0 0 0 1px rgba(var(--center-channel-color-rgb), 0.12) inset;
|
||||
margin-block-start: 4px;
|
||||
}
|
||||
&.AsSubMenu {
|
||||
pointer-events: none;
|
||||
|
||||
@@ -24,7 +24,7 @@ import {getIsMobileView} from 'selectors/views/browser';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import ChannelHeaderMobile from './channel_header_mobile';
|
||||
import MobileChannelHeader from './mobile_channel_header';
|
||||
|
||||
const isCurrentChannelMuted = createSelector(
|
||||
'isCurrentChannelMuted',
|
||||
@@ -53,4 +53,4 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
}, dispatch),
|
||||
});
|
||||
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ChannelHeaderMobile));
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MobileChannelHeader));
|
||||
@@ -6,7 +6,7 @@ import React from 'react';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import ChannelHeaderMobile from './channel_header_mobile';
|
||||
import ChannelHeaderMobile from './mobile_channel_header';
|
||||
|
||||
describe('components/ChannelHeaderMobile/ChannelHeaderMobile', () => {
|
||||
global.document.querySelector = jest.fn().mockReturnValue({
|
||||
@@ -33,7 +33,7 @@ type Props = {
|
||||
};
|
||||
}
|
||||
|
||||
export default class ChannelHeaderMobile extends React.PureComponent<Props> {
|
||||
export default class MobileChannelHeader extends React.PureComponent<Props> {
|
||||
componentDidMount() {
|
||||
document.querySelector('.inner-wrap')?.addEventListener('click', this.hideSidebars);
|
||||
}
|
||||
26
webapp/channels/src/components/mobile_sidebar_right/index.ts
Обычный файл
26
webapp/channels/src/components/mobile_sidebar_right/index.ts
Обычный файл
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {ConnectedProps} from 'react-redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {getIsRhsMenuOpen} from 'selectors/rhs';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import MobileSidebarRight from './mobile_sidebar_right';
|
||||
|
||||
function mapStateToProps(state: GlobalState) {
|
||||
return {
|
||||
currentUser: getCurrentUser(state),
|
||||
isOpen: getIsRhsMenuOpen(state),
|
||||
};
|
||||
}
|
||||
|
||||
const connector = connect(mapStateToProps);
|
||||
|
||||
export type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||
|
||||
export default connect(mapStateToProps)(MobileSidebarRight);
|
||||
@@ -4,15 +4,29 @@
|
||||
.sidebar--menu {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
display: none;
|
||||
overflow: auto;
|
||||
width: 220px;
|
||||
width: 290px;
|
||||
height: 100%;
|
||||
padding: 0 0 2em;
|
||||
border-right: variables.$border-gray;
|
||||
background: var(--sidebar-bg);
|
||||
color: rgba(var(--sidebar-text-rgb), 0.8);
|
||||
-webkit-overflow-scrolling: touch;
|
||||
transform: translate3d(290px, 0, 0);
|
||||
transition: transform 0.3s linear;
|
||||
|
||||
&.move--left {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.mentions {
|
||||
font-size: 17px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.Menu .MenuItem.MenuItem--with-icon .MenuItem__help-text {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
@@ -31,53 +45,6 @@
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.team__header {
|
||||
@include mixins.pie-clearfix;
|
||||
|
||||
display: none;
|
||||
padding: 0 15px;
|
||||
background: var(--sidebar-header-bg);
|
||||
|
||||
a {
|
||||
color: variables.$white;
|
||||
}
|
||||
|
||||
.navbar-right {
|
||||
margin: 16px -5px 0;
|
||||
font-size: 0.85em;
|
||||
|
||||
.dropdown-toggle {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
li a {
|
||||
padding: 3 20px;
|
||||
color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown__icon {
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.team__name {
|
||||
overflow: hidden;
|
||||
max-width: 80%;
|
||||
float: left;
|
||||
font-size: 1.2em;
|
||||
font-weight: 600;
|
||||
line-height: 50px;
|
||||
text-decoration: none;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
> li {
|
||||
> a,
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, {memo} from 'react';
|
||||
import {CSSTransition} from 'react-transition-group';
|
||||
|
||||
import useGetUsageDeltas from 'components/common/hooks/useGetUsageDeltas';
|
||||
|
||||
import MobileRightDrawerItems from './mobile_sidebar_right_items';
|
||||
|
||||
import type {PropsFromRedux} from './index';
|
||||
import './mobile_sidebar_right.scss';
|
||||
|
||||
const TRANSITION_TIMEOUT = 300; // in ms
|
||||
|
||||
type Props = PropsFromRedux;
|
||||
|
||||
const MobileRightDrawer = ({
|
||||
isOpen,
|
||||
currentUser,
|
||||
}: Props) => {
|
||||
const usageDeltas = useGetUsageDeltas();
|
||||
|
||||
if (!currentUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('sidebar--menu', {'move--left': isOpen})}
|
||||
id='sidebar-menu'
|
||||
>
|
||||
<div className='nav-pills__container mobile-main-menu'>
|
||||
<CSSTransition
|
||||
in={isOpen}
|
||||
enter={true}
|
||||
exit={true}
|
||||
mountOnEnter={true}
|
||||
unmountOnExit={true}
|
||||
timeout={TRANSITION_TIMEOUT}
|
||||
>
|
||||
<MobileRightDrawerItems
|
||||
usageDeltaTeams={usageDeltas.teams.active}
|
||||
/>
|
||||
</CSSTransition>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(MobileRightDrawer);
|
||||
@@ -1,25 +1,21 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {ConnectedProps} from 'react-redux';
|
||||
import {connect} from 'react-redux';
|
||||
import {withRouter} from 'react-router-dom';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import type {Dispatch} from 'redux';
|
||||
|
||||
import {Permissions} from 'mattermost-redux/constants';
|
||||
import {getCloudSubscription as selectCloudSubscription, getSubscriptionProduct} from 'mattermost-redux/selectors/entities/cloud';
|
||||
import {
|
||||
getConfig,
|
||||
getLicense,
|
||||
} from 'mattermost-redux/selectors/entities/general';
|
||||
import {haveICurrentTeamPermission, haveISystemPermission} from 'mattermost-redux/selectors/entities/roles';
|
||||
import {
|
||||
getJoinableTeamIds,
|
||||
getCurrentTeam,
|
||||
} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {openModal} from 'actions/views/modals';
|
||||
import {showMentions, showFlaggedPosts, closeRightHandSide, closeMenu as closeRhsMenu} from 'actions/views/rhs';
|
||||
import {getRhsState} from 'selectors/rhs';
|
||||
|
||||
@@ -28,28 +24,18 @@ import {isCloudLicense} from 'utils/license_utils';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import MainMenu from './main_menu';
|
||||
import MobileSidebarRightItems from './mobile_sidebar_right_items';
|
||||
|
||||
function mapStateToProps(state: GlobalState) {
|
||||
const config = getConfig(state);
|
||||
const currentTeam = getCurrentTeam(state);
|
||||
const currentUser = getCurrentUser(state);
|
||||
|
||||
const appDownloadLink = config.AppDownloadLink;
|
||||
const enableCommands = config.EnableCommands === 'true';
|
||||
const siteName = config.SiteName;
|
||||
const enableIncomingWebhooks = config.EnableIncomingWebhooks === 'true';
|
||||
const enableOAuthServiceProvider = config.EnableOAuthServiceProvider === 'true';
|
||||
const enableOutgoingWebhooks = config.EnableOutgoingWebhooks === 'true';
|
||||
const experimentalPrimaryTeam = config.ExperimentalPrimaryTeam;
|
||||
const helpLink = config.HelpLink;
|
||||
const reportAProblemLink = config.ReportAProblemLink;
|
||||
|
||||
const canManageTeamIntegrations = (haveICurrentTeamPermission(state, Permissions.MANAGE_SLASH_COMMANDS) || haveICurrentTeamPermission(state, Permissions.MANAGE_OAUTH) || haveICurrentTeamPermission(state, Permissions.MANAGE_INCOMING_WEBHOOKS) || haveICurrentTeamPermission(state, Permissions.MANAGE_OUTGOING_WEBHOOKS));
|
||||
const canManageSystemBots = (haveISystemPermission(state, {permission: Permissions.MANAGE_BOTS}) || haveISystemPermission(state, {permission: Permissions.MANAGE_OTHERS_BOTS}));
|
||||
const canManageIntegrations = canManageTeamIntegrations || canManageSystemBots;
|
||||
const canInviteTeamMember = haveICurrentTeamPermission(state, Permissions.ADD_USER_TO_TEAM);
|
||||
|
||||
const joinableTeams = getJoinableTeamIds(state);
|
||||
const moreTeamsToJoin = joinableTeams && joinableTeams.length > 0;
|
||||
const rhsState = getRhsState(state);
|
||||
@@ -64,12 +50,6 @@ function mapStateToProps(state: GlobalState) {
|
||||
|
||||
return {
|
||||
appDownloadLink,
|
||||
enableCommands,
|
||||
canManageIntegrations,
|
||||
enableIncomingWebhooks,
|
||||
enableOAuthServiceProvider,
|
||||
enableOutgoingWebhooks,
|
||||
canManageSystemBots,
|
||||
experimentalPrimaryTeam,
|
||||
helpLink,
|
||||
reportAProblemLink,
|
||||
@@ -78,13 +58,10 @@ function mapStateToProps(state: GlobalState) {
|
||||
siteName,
|
||||
teamId: currentTeam?.id,
|
||||
teamName: currentTeam?.name,
|
||||
currentUser,
|
||||
isMentionSearch: rhsState === RHSStates.MENTION,
|
||||
teamIsGroupConstrained: Boolean(currentTeam?.group_constrained),
|
||||
isLicensedForLDAPGroups: state.entities.general.license.LDAPGroups === 'true',
|
||||
guestAccessEnabled: config.EnableGuestAccounts === 'true',
|
||||
canInviteTeamMember,
|
||||
isCloud,
|
||||
isStarterFree,
|
||||
isFreeTrial,
|
||||
};
|
||||
@@ -93,7 +70,6 @@ function mapStateToProps(state: GlobalState) {
|
||||
function mapDispatchToProps(dispatch: Dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
openModal,
|
||||
showMentions,
|
||||
showFlaggedPosts,
|
||||
closeRightHandSide,
|
||||
@@ -102,4 +78,8 @@ function mapDispatchToProps(dispatch: Dispatch) {
|
||||
};
|
||||
}
|
||||
|
||||
export default withRouter<any, any>(connect(mapStateToProps, mapDispatchToProps)(MainMenu));
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||
|
||||
export default connector(MobileSidebarRightItems);
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {Permissions} from 'mattermost-redux/constants';
|
||||
|
||||
import type {MockIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
|
||||
import {MobileSidebarRightItems} from './mobile_sidebar_right_items';
|
||||
import type {Props} from './mobile_sidebar_right_items';
|
||||
|
||||
describe('MobileSidebarRightItems', () => {
|
||||
const defaultProps: Props = {
|
||||
teamId: 'team-id',
|
||||
teamName: 'team_name',
|
||||
appDownloadLink: undefined,
|
||||
experimentalPrimaryTeam: undefined,
|
||||
helpLink: undefined,
|
||||
reportAProblemLink: undefined,
|
||||
moreTeamsToJoin: false,
|
||||
pluginMenuItems: [],
|
||||
isMentionSearch: false,
|
||||
usageDeltaTeams: 0,
|
||||
siteName: 'site-name',
|
||||
isLicensedForLDAPGroups: false,
|
||||
guestAccessEnabled: true,
|
||||
actions: {
|
||||
showMentions: jest.fn(),
|
||||
showFlaggedPosts: jest.fn(),
|
||||
closeRightHandSide: jest.fn(),
|
||||
closeRhsMenu: jest.fn(),
|
||||
},
|
||||
teamIsGroupConstrained: false,
|
||||
isStarterFree: false,
|
||||
isFreeTrial: false,
|
||||
intl: {
|
||||
formatMessage: ({defaultMessage}) => defaultMessage,
|
||||
} as MockIntl,
|
||||
};
|
||||
|
||||
const defaultState = {
|
||||
entities: {
|
||||
channels: {
|
||||
myMembers: {},
|
||||
},
|
||||
general: {
|
||||
config: {},
|
||||
license: {
|
||||
Cloud: 'false',
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: 'team-id',
|
||||
myMembers: {
|
||||
'team-id': {
|
||||
team_id: 'team-id',
|
||||
user_id: 'test-user-id',
|
||||
roles: 'team_user',
|
||||
scheme_user: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
users: {
|
||||
currentUserId: 'test-user-id',
|
||||
profiles: {
|
||||
'test-user-id': {
|
||||
id: 'test-user-id',
|
||||
roles: 'system_user system_admin',
|
||||
},
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
roles: {
|
||||
system_admin: {
|
||||
permissions: [
|
||||
Permissions.CREATE_TEAM,
|
||||
Permissions.SYSCONSOLE_WRITE_PLUGINS,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test('should render basic menu items', () => {
|
||||
renderWithContext(<MobileSidebarRightItems {...defaultProps}/>, defaultState);
|
||||
expect(screen.getByText('Recent Mentions')).toBeInTheDocument();
|
||||
expect(screen.getByText('Saved messages')).toBeInTheDocument();
|
||||
expect(screen.getByText('Profile')).toBeInTheDocument();
|
||||
expect(screen.getByText('Settings')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show leave team option when primary team is not set', () => {
|
||||
renderWithContext(
|
||||
<MobileSidebarRightItems
|
||||
{...defaultProps}
|
||||
teamIsGroupConstrained={false}
|
||||
experimentalPrimaryTeam={undefined}
|
||||
/>,
|
||||
defaultState,
|
||||
);
|
||||
expect(screen.getByText('Leave Team')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should hide leave team option when team is group constrained', () => {
|
||||
renderWithContext(
|
||||
<MobileSidebarRightItems
|
||||
{...defaultProps}
|
||||
teamIsGroupConstrained={true}
|
||||
/>,
|
||||
defaultState,
|
||||
);
|
||||
expect(screen.queryByText('Leave Team')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show create team option with proper permissions', () => {
|
||||
renderWithContext(<MobileSidebarRightItems {...defaultProps}/>, defaultState);
|
||||
expect(screen.getByText('Create a Team')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show plugins when provided', () => {
|
||||
const pluginMenuItems = [
|
||||
{
|
||||
id: 'plugin-1',
|
||||
pluginId: 'plugin-1',
|
||||
mobileIcon: <i className='fa fa-anchor'/>,
|
||||
action: jest.fn(),
|
||||
text: 'Plugin Item 1',
|
||||
},
|
||||
];
|
||||
renderWithContext(
|
||||
<MobileSidebarRightItems
|
||||
{...defaultProps}
|
||||
pluginMenuItems={pluginMenuItems}
|
||||
/>,
|
||||
defaultState,
|
||||
);
|
||||
expect(screen.getByText('Plugin Item 1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show help link when provided', () => {
|
||||
renderWithContext(
|
||||
<MobileSidebarRightItems
|
||||
{...defaultProps}
|
||||
helpLink='https://help.example.com'
|
||||
/>,
|
||||
defaultState,
|
||||
);
|
||||
expect(screen.getByText('Help')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show report link when provided', () => {
|
||||
renderWithContext(
|
||||
<MobileSidebarRightItems
|
||||
{...defaultProps}
|
||||
reportAProblemLink='https://report.example.com'
|
||||
/>,
|
||||
defaultState,
|
||||
);
|
||||
expect(screen.getByText('Report a Problem')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,299 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {injectIntl} from 'react-intl';
|
||||
import type {WrappedComponentProps} from 'react-intl';
|
||||
|
||||
import {Permissions} from 'mattermost-redux/constants';
|
||||
|
||||
import {emitUserLoggedOutEvent} from 'actions/global_actions';
|
||||
import {trackEvent} from 'actions/telemetry_actions';
|
||||
|
||||
import AboutBuildModal from 'components/about_build_modal';
|
||||
import AddGroupsToTeamModal from 'components/add_groups_to_team_modal';
|
||||
import InvitationModal from 'components/invitation_modal';
|
||||
import LeaveTeamModal from 'components/leave_team_modal';
|
||||
import SystemPermissionGate from 'components/permissions_gates/system_permission_gate';
|
||||
import TeamPermissionGate from 'components/permissions_gates/team_permission_gate';
|
||||
import TeamGroupsManageModal from 'components/team_groups_manage_modal';
|
||||
import TeamMembersModal from 'components/team_members_modal';
|
||||
import TeamSettingsModal from 'components/team_settings_modal';
|
||||
import UserSettingsModal from 'components/user_settings/modal';
|
||||
import LeaveTeamIcon from 'components/widgets/icons/leave_team_icon';
|
||||
import Menu from 'components/widgets/menu/menu';
|
||||
|
||||
import {ModalIdentifiers} from 'utils/constants';
|
||||
import {makeUrlSafe} from 'utils/url';
|
||||
|
||||
import type {PropsFromRedux} from './index';
|
||||
|
||||
export interface Props extends PropsFromRedux, WrappedComponentProps {
|
||||
usageDeltaTeams: number;
|
||||
}
|
||||
|
||||
export class MobileSidebarRightItems extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
pluginMenuItems: [],
|
||||
};
|
||||
|
||||
onRecentMentionItemClick = (e: Event): void => {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.props.isMentionSearch) {
|
||||
this.props.actions.closeRightHandSide();
|
||||
} else {
|
||||
this.props.actions.closeRhsMenu();
|
||||
this.props.actions.showMentions();
|
||||
}
|
||||
};
|
||||
|
||||
onShowFlaggedPostItemClick = (e: Event): void => {
|
||||
e.preventDefault();
|
||||
this.props.actions.showFlaggedPosts();
|
||||
this.props.actions.closeRhsMenu();
|
||||
};
|
||||
|
||||
onLogoutItemClick = (): void => {
|
||||
emitUserLoggedOutEvent();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
const safeAppDownloadLink = makeUrlSafe(this.props.appDownloadLink || '');
|
||||
const teamsLimitReached = this.props.isStarterFree && !this.props.isFreeTrial && this.props.usageDeltaTeams >= 0;
|
||||
|
||||
const pluginItems = this.props.pluginMenuItems.map((item) => (
|
||||
<Menu.ItemAction
|
||||
id={item.id + '_pluginmenuitem'}
|
||||
key={item.id + '_pluginmenuitem'}
|
||||
onClick={() => {
|
||||
if (item.action) {
|
||||
item.action();
|
||||
}
|
||||
}}
|
||||
text={item.text}
|
||||
icon={item.mobileIcon}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<Menu
|
||||
ariaLabel={formatMessage({id: 'navbar_dropdown.menuAriaLabel', defaultMessage: 'main menu'})}
|
||||
>
|
||||
<Menu.Group>
|
||||
<SystemPermissionGate
|
||||
permissions={[Permissions.SYSCONSOLE_WRITE_BILLING]}
|
||||
>
|
||||
<Menu.CloudTrial
|
||||
id='menuCloudTrial'
|
||||
/>
|
||||
</SystemPermissionGate>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<SystemPermissionGate
|
||||
permissions={[Permissions.SYSCONSOLE_WRITE_ABOUT_EDITION_AND_LICENSE]}
|
||||
>
|
||||
<Menu.StartTrial
|
||||
id='startTrial'
|
||||
/>
|
||||
</SystemPermissionGate>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<Menu.ItemAction
|
||||
id='recentMentions'
|
||||
onClick={this.onRecentMentionItemClick}
|
||||
icon={<i className='mentions'>{'@'}</i>}
|
||||
text={formatMessage({id: 'sidebar_right_menu.recentMentions', defaultMessage: 'Recent Mentions'})}
|
||||
/>
|
||||
<Menu.ItemAction
|
||||
id='flaggedPosts'
|
||||
onClick={this.onShowFlaggedPostItemClick}
|
||||
icon={<i className='fa fa-bookmark'/>}
|
||||
text={formatMessage({id: 'sidebar_right_menu.flagged', defaultMessage: 'Saved messages'})}
|
||||
/>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='profileSettings'
|
||||
modalId={ModalIdentifiers.USER_SETTINGS}
|
||||
dialogType={UserSettingsModal}
|
||||
dialogProps={{isContentProductSettings: false}}
|
||||
text={formatMessage({id: 'navbar_dropdown.profileSettings', defaultMessage: 'Profile'})}
|
||||
icon={<i className='fa fa-user'/>}
|
||||
/>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='accountSettings'
|
||||
modalId={ModalIdentifiers.USER_SETTINGS}
|
||||
dialogType={UserSettingsModal}
|
||||
dialogProps={{isContentProductSettings: true}}
|
||||
text={formatMessage({id: 'navbar_dropdown.accountSettings', defaultMessage: 'Settings'})}
|
||||
icon={<i className='fa fa-cog'/>}
|
||||
/>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<TeamPermissionGate
|
||||
teamId={this.props.teamId}
|
||||
permissions={[Permissions.MANAGE_TEAM]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='addGroupsToTeam'
|
||||
show={this.props.teamIsGroupConstrained && this.props.isLicensedForLDAPGroups}
|
||||
modalId={ModalIdentifiers.ADD_GROUPS_TO_TEAM}
|
||||
dialogType={AddGroupsToTeamModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.addGroupsToTeam', defaultMessage: 'Add Groups to Team'})}
|
||||
icon={<i className='fa fa-user-plus'/>}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
{this.props.guestAccessEnabled && (
|
||||
<TeamPermissionGate
|
||||
teamId={this.props.teamId}
|
||||
permissions={[Permissions.ADD_USER_TO_TEAM, Permissions.INVITE_GUEST]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='invitePeople'
|
||||
modalId={ModalIdentifiers.INVITATION}
|
||||
dialogType={InvitationModal}
|
||||
text={formatMessage({
|
||||
id: 'navbar_dropdown.invitePeople',
|
||||
defaultMessage: 'Invite People',
|
||||
})}
|
||||
extraText={formatMessage({
|
||||
id: 'navbar_dropdown.invitePeopleExtraText',
|
||||
defaultMessage: 'Add people to the team',
|
||||
})}
|
||||
icon={<i className='fa fa-user-plus'/>}
|
||||
onClick={() => trackEvent('ui', 'click_sidebar_team_dropdown_invite_people')}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
)}
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<TeamPermissionGate
|
||||
teamId={this.props.teamId}
|
||||
permissions={[Permissions.MANAGE_TEAM]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='teamSettings'
|
||||
modalId={ModalIdentifiers.TEAM_SETTINGS}
|
||||
dialogType={TeamSettingsModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.teamSettings', defaultMessage: 'Team Settings'})}
|
||||
icon={<i className='fa fa-globe'/>}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
<TeamPermissionGate
|
||||
teamId={this.props.teamId}
|
||||
permissions={[Permissions.MANAGE_TEAM]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='manageGroups'
|
||||
show={this.props.teamIsGroupConstrained && this.props.isLicensedForLDAPGroups}
|
||||
modalId={ModalIdentifiers.MANAGE_TEAM_GROUPS}
|
||||
dialogProps={{
|
||||
teamID: this.props.teamId,
|
||||
}}
|
||||
dialogType={TeamGroupsManageModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.manageGroups', defaultMessage: 'Manage Groups'})}
|
||||
icon={<i className='fa fa-user-plus'/>}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
<TeamPermissionGate
|
||||
teamId={this.props.teamId}
|
||||
permissions={[Permissions.REMOVE_USER_FROM_TEAM, Permissions.MANAGE_TEAM_ROLES]}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='manageMembers'
|
||||
modalId={ModalIdentifiers.TEAM_MEMBERS}
|
||||
dialogType={TeamMembersModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.manageMembers', defaultMessage: 'Manage Members'})}
|
||||
icon={<i className='fa fa-users'/>}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
<TeamPermissionGate
|
||||
teamId={this.props.teamId}
|
||||
permissions={[Permissions.REMOVE_USER_FROM_TEAM, Permissions.MANAGE_TEAM_ROLES]}
|
||||
invert={true}
|
||||
>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='viewMembers'
|
||||
modalId={ModalIdentifiers.TEAM_MEMBERS}
|
||||
dialogType={TeamMembersModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.viewMembers', defaultMessage: 'View Members'})}
|
||||
icon={<i className='fa fa-users'/>}
|
||||
/>
|
||||
</TeamPermissionGate>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<SystemPermissionGate permissions={[Permissions.CREATE_TEAM]}>
|
||||
<Menu.ItemLink
|
||||
id='createTeam'
|
||||
show={!teamsLimitReached}
|
||||
to='/create_team'
|
||||
text={formatMessage({id: 'navbar_dropdown.create', defaultMessage: 'Create a Team'})}
|
||||
icon={<i className='fa fa-plus-square'/>}
|
||||
/>
|
||||
</SystemPermissionGate>
|
||||
<Menu.ItemLink
|
||||
id='joinTeam'
|
||||
show={!this.props.experimentalPrimaryTeam && this.props.moreTeamsToJoin}
|
||||
to='/select_team'
|
||||
text={formatMessage({id: 'navbar_dropdown.join', defaultMessage: 'Join Another Team'})}
|
||||
icon={<i className='fa fa-plus-square'/>}
|
||||
/>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='leaveTeam'
|
||||
show={!this.props.teamIsGroupConstrained && this.props.experimentalPrimaryTeam !== this.props.teamName}
|
||||
modalId={ModalIdentifiers.LEAVE_TEAM}
|
||||
dialogType={LeaveTeamModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.leave', defaultMessage: 'Leave Team'})}
|
||||
icon={<LeaveTeamIcon/>}
|
||||
/>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
{pluginItems}
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<Menu.ItemExternalLink
|
||||
id='helpLink'
|
||||
show={Boolean(this.props.helpLink)}
|
||||
url={this.props.helpLink}
|
||||
text={formatMessage({id: 'navbar_dropdown.help', defaultMessage: 'Help'})}
|
||||
icon={<i className='fa fa-question'/>}
|
||||
/>
|
||||
<Menu.ItemExternalLink
|
||||
id='reportLink'
|
||||
show={Boolean(this.props.reportAProblemLink)}
|
||||
url={this.props.reportAProblemLink}
|
||||
text={formatMessage({id: 'navbar_dropdown.report', defaultMessage: 'Report a Problem'})}
|
||||
icon={<i className='fa fa-phone'/>}
|
||||
/>
|
||||
<Menu.ItemExternalLink
|
||||
id='nativeAppLink'
|
||||
show={this.props.appDownloadLink}
|
||||
url={safeAppDownloadLink}
|
||||
text={formatMessage({id: 'navbar_dropdown.nativeApps', defaultMessage: 'Download Apps'})}
|
||||
icon={<i className='fa fa-mobile'/>}
|
||||
/>
|
||||
<Menu.ItemToggleModalRedux
|
||||
id='about'
|
||||
modalId={ModalIdentifiers.ABOUT}
|
||||
dialogType={AboutBuildModal}
|
||||
text={formatMessage({id: 'navbar_dropdown.about', defaultMessage: 'About {appTitle}'}, {appTitle: this.props.siteName || 'Mattermost'})}
|
||||
icon={<i className='fa fa-info'/>}
|
||||
/>
|
||||
</Menu.Group>
|
||||
<Menu.Group>
|
||||
<Menu.ItemAction
|
||||
id='logout'
|
||||
onClick={this.onLogoutItemClick}
|
||||
text={formatMessage({id: 'navbar_dropdown.logout', defaultMessage: 'Log Out'})}
|
||||
icon={<i className='fa fa-sign-out'/>}
|
||||
/>
|
||||
</Menu.Group>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(MobileSidebarRightItems);
|
||||
@@ -164,7 +164,7 @@ export default class MoreDirectChannels extends React.PureComponent<Props, State
|
||||
|
||||
if (this.exitToChannel) {
|
||||
getHistory().push(this.exitToChannel);
|
||||
} else {
|
||||
} else if (this.props.focusOriginElement) {
|
||||
setTimeout(() => {
|
||||
focusElement(this.props.focusOriginElement, true);
|
||||
}, 0);
|
||||
|
||||
@@ -10,10 +10,12 @@ import {TourTip, useMeasurePunchouts} from '@mattermost/components';
|
||||
import {setShowOnboardingCompleteProfileTour} from 'actions/views/onboarding_tasks';
|
||||
import {isShowOnboardingCompleteProfileTour} from 'selectors/views/onboarding_tasks';
|
||||
|
||||
import {ELEMENT_ID_FOR_USER_ACCOUNT_MENU} from 'components/user_account_menu/user_account_menu';
|
||||
|
||||
import {OnboardingTasksName, TaskNameMapToSteps} from './constants';
|
||||
import {useHandleOnBoardingTaskData} from './onboarding_tasks_manager';
|
||||
|
||||
const translate = {x: 1, y: -9};
|
||||
const translate = {x: 1.5, y: -9};
|
||||
|
||||
export const CompleteYourProfileTour = () => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -43,7 +45,7 @@ export const CompleteYourProfileTour = () => {
|
||||
</p>
|
||||
);
|
||||
|
||||
const overlayPunchOut = useMeasurePunchouts(['userAccountMenu'], [], {x: -2.5, y: -2.5, width: 5, height: 5});
|
||||
const overlayPunchOut = useMeasurePunchouts([ELEMENT_ID_FOR_USER_ACCOUNT_MENU], [], {x: -2.5, y: -2.5, width: 5, height: 5});
|
||||
const onDismiss = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
@@ -26,7 +26,6 @@ import LoggedIn from 'components/logged_in';
|
||||
import LoggedInRoute from 'components/logged_in_route';
|
||||
import {LAUNCHING_WORKSPACE_FULLSCREEN_Z_INDEX} from 'components/preparing_workspace/launching_workspace';
|
||||
import {Animations} from 'components/preparing_workspace/steps';
|
||||
import SidebarMobileRightMenu from 'components/sidebar_mobile_right_menu';
|
||||
|
||||
import webSocketClient from 'client/web_websocket_client';
|
||||
import {initializePlugins} from 'plugins';
|
||||
@@ -584,7 +583,6 @@ export default class Root extends React.PureComponent<Props, State> {
|
||||
</div>
|
||||
<Pluggable pluggableName='Global'/>
|
||||
<AppBar/>
|
||||
<SidebarMobileRightMenu/>
|
||||
</CompassThemeProvider>
|
||||
</Switch>
|
||||
</RootProvider>
|
||||
|
||||
@@ -33,7 +33,20 @@ exports[`components/select_team/SelectTeam should match snapshot 1`] = `
|
||||
id="signup_team.join_open"
|
||||
/>
|
||||
</h4>
|
||||
<LearnAboutTeamsLink />
|
||||
<ForwardRef
|
||||
className="LearnAboutTeamsLink"
|
||||
href="https://mattermost.com/pl/mattermost-academy-team-training"
|
||||
location="learn_about_teams"
|
||||
>
|
||||
<i
|
||||
aria-hidden={true}
|
||||
className="icon icon-lightbulb-outline"
|
||||
/>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Learn about teams"
|
||||
id="learn_about_teams"
|
||||
/>
|
||||
</ForwardRef>
|
||||
</div>
|
||||
<InfiniteScroll
|
||||
bufferValue={280}
|
||||
@@ -150,7 +163,20 @@ exports[`components/select_team/SelectTeam should match snapshot, on create team
|
||||
id="signup_team.join_open"
|
||||
/>
|
||||
</h4>
|
||||
<LearnAboutTeamsLink />
|
||||
<ForwardRef
|
||||
className="LearnAboutTeamsLink"
|
||||
href="https://mattermost.com/pl/mattermost-academy-team-training"
|
||||
location="learn_about_teams"
|
||||
>
|
||||
<i
|
||||
aria-hidden={true}
|
||||
className="icon icon-lightbulb-outline"
|
||||
/>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Learn about teams"
|
||||
id="learn_about_teams"
|
||||
/>
|
||||
</ForwardRef>
|
||||
</div>
|
||||
<InfiniteScroll
|
||||
bufferValue={280}
|
||||
@@ -404,7 +430,20 @@ exports[`components/select_team/SelectTeam should match snapshot, on no joinable
|
||||
id="signup_team.join_open"
|
||||
/>
|
||||
</h4>
|
||||
<LearnAboutTeamsLink />
|
||||
<ForwardRef
|
||||
className="LearnAboutTeamsLink"
|
||||
href="https://mattermost.com/pl/mattermost-academy-team-training"
|
||||
location="learn_about_teams"
|
||||
>
|
||||
<i
|
||||
aria-hidden={true}
|
||||
className="icon icon-lightbulb-outline"
|
||||
/>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Learn about teams"
|
||||
id="learn_about_teams"
|
||||
/>
|
||||
</ForwardRef>
|
||||
</div>
|
||||
<InfiniteScroll
|
||||
bufferValue={280}
|
||||
@@ -633,7 +672,20 @@ exports[`components/select_team/SelectTeam should match snapshot, on no joinable
|
||||
id="signup_team.join_open"
|
||||
/>
|
||||
</h4>
|
||||
<LearnAboutTeamsLink />
|
||||
<ForwardRef
|
||||
className="LearnAboutTeamsLink"
|
||||
href="https://mattermost.com/pl/mattermost-academy-team-training"
|
||||
location="learn_about_teams"
|
||||
>
|
||||
<i
|
||||
aria-hidden={true}
|
||||
className="icon icon-lightbulb-outline"
|
||||
/>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Learn about teams"
|
||||
id="learn_about_teams"
|
||||
/>
|
||||
</ForwardRef>
|
||||
</div>
|
||||
<InfiniteScroll
|
||||
bufferValue={280}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
margin-top: 10px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
>h4 {
|
||||
> h4 {
|
||||
margin: 0;
|
||||
|
||||
span {
|
||||
@@ -15,4 +15,39 @@
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
a.LearnAboutTeamsLink {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 18px;
|
||||
column-gap: 10px;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
> i {
|
||||
display: inline-flex;
|
||||
padding: 3px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(var(--denim-button-bg-rgb), 0.08);
|
||||
font-size: 14px;
|
||||
|
||||
&::before {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ import AnnouncementBar from 'components/announcement_bar';
|
||||
import BackButton from 'components/common/back_button';
|
||||
import InfiniteScroll from 'components/common/infinite_scroll';
|
||||
import SiteNameAndDescription from 'components/common/site_name_and_description';
|
||||
import ExternalLink from 'components/external_link';
|
||||
import LoadingScreen from 'components/loading_screen';
|
||||
import LearnAboutTeamsLink from 'components/main_menu/learn_about_teams_link';
|
||||
import SystemPermissionGate from 'components/permissions_gates/system_permission_gate';
|
||||
import LogoutIcon from 'components/widgets/icons/fa_logout_icon';
|
||||
|
||||
@@ -28,10 +28,12 @@ import Constants from 'utils/constants';
|
||||
import * as UserAgent from 'utils/user_agent';
|
||||
|
||||
import SelectTeamItem from './components/select_team_item';
|
||||
|
||||
import './select_team.scss';
|
||||
|
||||
export const TEAMS_PER_PAGE = 30;
|
||||
const TEAM_MEMBERSHIP_DENIAL_ERROR_ID = 'api.team.add_members.user_denied';
|
||||
const MATTERMOST_ACADEMY_TEAM_TRAINING_LINK = 'https://mattermost.com/pl/mattermost-academy-team-training';
|
||||
|
||||
type Actions = {
|
||||
getTeams: (page?: number, perPage?: number, includeTotalCount?: boolean) => any;
|
||||
@@ -289,7 +291,20 @@ export default class SelectTeam extends React.PureComponent<Props, State> {
|
||||
defaultMessage='Teams you can join: '
|
||||
/>
|
||||
</h4>
|
||||
<LearnAboutTeamsLink/>
|
||||
<ExternalLink
|
||||
location='learn_about_teams'
|
||||
href={MATTERMOST_ACADEMY_TEAM_TRAINING_LINK}
|
||||
className='LearnAboutTeamsLink'
|
||||
>
|
||||
<i
|
||||
className='icon icon-lightbulb-outline'
|
||||
aria-hidden={true}
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='learn_about_teams'
|
||||
defaultMessage='Learn about teams'
|
||||
/>
|
||||
</ExternalLink>
|
||||
</div>
|
||||
<InfiniteScroll
|
||||
callBack={this.fetchMoreTeams}
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/sidebar should match empty div snapshot when teamId is missing 1`] = `<div />`;
|
||||
|
||||
exports[`components/sidebar should match snapshot 1`] = `
|
||||
<ResizableLhs
|
||||
className=""
|
||||
id="SidebarContainer"
|
||||
>
|
||||
<SidebarHeader
|
||||
canCreateChannel={true}
|
||||
canCreateCustomGroups={true}
|
||||
canJoinPublicChannel={true}
|
||||
handleOpenDirectMessagesModal={[Function]}
|
||||
invitePeopleModal={[Function]}
|
||||
showCreateCategoryModal={[Function]}
|
||||
showCreateUserGroupModal={[Function]}
|
||||
showMoreChannelsModal={[Function]}
|
||||
showNewChannelModal={[Function]}
|
||||
unreadFilterEnabled={false}
|
||||
/>
|
||||
<div
|
||||
aria-label="channel navigator region"
|
||||
className="a11y__region"
|
||||
data-a11y-sort-order="6"
|
||||
id="lhsNavigator"
|
||||
role="application"
|
||||
>
|
||||
<Connect(injectIntl(ChannelNavigator)) />
|
||||
</div>
|
||||
<div
|
||||
className="sidebar--left__icons"
|
||||
>
|
||||
<Pluggable
|
||||
pluggableName="LeftSidebarHeader"
|
||||
/>
|
||||
</div>
|
||||
<Connect(injectIntl(SidebarList))
|
||||
handleOpenMoreDirectChannelsModal={[Function]}
|
||||
onDragEnd={[Function]}
|
||||
onDragStart={[Function]}
|
||||
/>
|
||||
<Connect(DataPrefetch) />
|
||||
</ResizableLhs>
|
||||
`;
|
||||
|
||||
exports[`components/sidebar should match snapshot when direct channels modal is open 1`] = `
|
||||
<ResizableLhs
|
||||
className=""
|
||||
id="SidebarContainer"
|
||||
>
|
||||
<SidebarHeader
|
||||
canCreateChannel={true}
|
||||
canCreateCustomGroups={true}
|
||||
canJoinPublicChannel={true}
|
||||
handleOpenDirectMessagesModal={[Function]}
|
||||
invitePeopleModal={[Function]}
|
||||
showCreateCategoryModal={[Function]}
|
||||
showCreateUserGroupModal={[Function]}
|
||||
showMoreChannelsModal={[Function]}
|
||||
showNewChannelModal={[Function]}
|
||||
unreadFilterEnabled={false}
|
||||
/>
|
||||
<div
|
||||
aria-label="channel navigator region"
|
||||
className="a11y__region"
|
||||
data-a11y-sort-order="6"
|
||||
id="lhsNavigator"
|
||||
role="application"
|
||||
>
|
||||
<Connect(injectIntl(ChannelNavigator)) />
|
||||
</div>
|
||||
<div
|
||||
className="sidebar--left__icons"
|
||||
>
|
||||
<Pluggable
|
||||
pluggableName="LeftSidebarHeader"
|
||||
/>
|
||||
</div>
|
||||
<Connect(injectIntl(SidebarList))
|
||||
handleOpenMoreDirectChannelsModal={[Function]}
|
||||
onDragEnd={[Function]}
|
||||
onDragStart={[Function]}
|
||||
/>
|
||||
<Connect(DataPrefetch) />
|
||||
<MoreDirectChannels
|
||||
focusOriginElement="newDirectMessageButton"
|
||||
isExistingChannel={false}
|
||||
onModalDismissed={[Function]}
|
||||
/>
|
||||
</ResizableLhs>
|
||||
`;
|
||||
|
||||
exports[`components/sidebar should match snapshot when more channels modal is open 1`] = `
|
||||
<ResizableLhs
|
||||
className=""
|
||||
id="SidebarContainer"
|
||||
>
|
||||
<SidebarHeader
|
||||
canCreateChannel={true}
|
||||
canCreateCustomGroups={true}
|
||||
canJoinPublicChannel={true}
|
||||
handleOpenDirectMessagesModal={[Function]}
|
||||
invitePeopleModal={[Function]}
|
||||
showCreateCategoryModal={[Function]}
|
||||
showCreateUserGroupModal={[Function]}
|
||||
showMoreChannelsModal={[Function]}
|
||||
showNewChannelModal={[Function]}
|
||||
unreadFilterEnabled={false}
|
||||
/>
|
||||
<div
|
||||
aria-label="channel navigator region"
|
||||
className="a11y__region"
|
||||
data-a11y-sort-order="6"
|
||||
id="lhsNavigator"
|
||||
role="application"
|
||||
>
|
||||
<Connect(injectIntl(ChannelNavigator)) />
|
||||
</div>
|
||||
<div
|
||||
className="sidebar--left__icons"
|
||||
>
|
||||
<Pluggable
|
||||
pluggableName="LeftSidebarHeader"
|
||||
/>
|
||||
</div>
|
||||
<Connect(injectIntl(SidebarList))
|
||||
handleOpenMoreDirectChannelsModal={[Function]}
|
||||
onDragEnd={[Function]}
|
||||
onDragStart={[Function]}
|
||||
/>
|
||||
<Connect(DataPrefetch) />
|
||||
</ResizableLhs>
|
||||
`;
|
||||
@@ -1,113 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import CustomStatusEmoji from 'components/custom_status/custom_status_emoji';
|
||||
import CustomStatusModal from 'components/custom_status/custom_status_modal';
|
||||
import WithTooltip from 'components/with_tooltip';
|
||||
|
||||
import {ModalIdentifiers} from 'utils/constants';
|
||||
|
||||
import type {ModalData} from 'types/actions';
|
||||
|
||||
const HeaderLine = styled.div`
|
||||
display: flex;
|
||||
padding: 2px 16px 0 0;
|
||||
flex-grow: 1;
|
||||
user-select: none;
|
||||
color: var(--sidebar-header-text-color);
|
||||
`;
|
||||
|
||||
const VerticalStack = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
teamDescription?: string;
|
||||
teamId?: string;
|
||||
currentUser: UserProfile;
|
||||
teamDisplayName?: string;
|
||||
actions: Actions;
|
||||
};
|
||||
|
||||
type Actions = {
|
||||
openModal: <P>(modalData: ModalData<P>) => void;
|
||||
};
|
||||
|
||||
export default class Contents extends React.PureComponent<Props> {
|
||||
handleCustomStatusEmojiClick = (event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
const customStatusInputModalData = {
|
||||
modalId: ModalIdentifiers.CUSTOM_STATUS,
|
||||
dialogType: CustomStatusModal,
|
||||
};
|
||||
this.props.actions.openModal(customStatusInputModalData);
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.props.currentUser || !this.props.teamId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let teamNameWithToolTip = (
|
||||
<h1
|
||||
id='headerTeamName'
|
||||
className='team__name'
|
||||
data-teamid={this.props.teamId}
|
||||
>
|
||||
{this.props.teamDisplayName}
|
||||
</h1>
|
||||
);
|
||||
|
||||
if (this.props.teamDescription) {
|
||||
teamNameWithToolTip = (
|
||||
<WithTooltip
|
||||
title={this.props.teamDescription}
|
||||
>
|
||||
{teamNameWithToolTip}
|
||||
</WithTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className='SidebarHeaderDropdownButton'
|
||||
id='sidebarHeaderDropdownButton'
|
||||
>
|
||||
<HeaderLine
|
||||
id='headerInfo'
|
||||
className='header__info'
|
||||
>
|
||||
<VerticalStack>
|
||||
{teamNameWithToolTip}
|
||||
<div
|
||||
id='headerInfoContent'
|
||||
className='header__info__content'
|
||||
>
|
||||
<div
|
||||
id='headerUsername'
|
||||
className='user__name'
|
||||
>
|
||||
{'@' + this.props.currentUser.username}
|
||||
</div>
|
||||
<CustomStatusEmoji
|
||||
showTooltip={true}
|
||||
emojiStyle={{
|
||||
verticalAlign: 'top',
|
||||
marginLeft: 2,
|
||||
}}
|
||||
onClick={this.handleCustomStatusEmojiClick as unknown as () => void}
|
||||
/>
|
||||
</div>
|
||||
</VerticalStack>
|
||||
</HeaderLine>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +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 {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {openModal} from 'actions/views/modals';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import Contents from './contents';
|
||||
|
||||
function mapStateToProps(state: GlobalState) {
|
||||
const currentTeam = getCurrentTeam(state);
|
||||
const currentUser = getCurrentUser(state);
|
||||
|
||||
return {
|
||||
currentUser,
|
||||
teamDescription: currentTeam?.description,
|
||||
teamDisplayName: currentTeam?.display_name,
|
||||
teamId: currentTeam?.id,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: Dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
openModal,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Contents);
|
||||
@@ -1,5 +1,34 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {ConnectedProps} from 'react-redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {openModal} from 'actions/views/modals';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import MobileSidebarHeader from './mobile_sidebar_header';
|
||||
export default MobileSidebarHeader;
|
||||
|
||||
function mapStateToProps(state: GlobalState) {
|
||||
const currentTeam = getCurrentTeam(state);
|
||||
const currentUser = getCurrentUser(state);
|
||||
|
||||
return {
|
||||
username: currentUser?.username,
|
||||
teamDisplayName: currentTeam?.display_name ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
openModal,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||
|
||||
export default connector(MobileSidebarHeader);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
.mobileSidebarHeader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 15px;
|
||||
gap: 4px;
|
||||
|
||||
h1 {
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
color: var(--sidebar-header-text-color);
|
||||
font-family: Metropolis, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__username {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: row;
|
||||
color: rgba(var(--sidebar-header-text-color-rgb), 0.75);
|
||||
font-family: "Open Sans", sans-serif;
|
||||
font-size: 14px;
|
||||
gap: 2px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
span:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,27 +2,45 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import type {MouseEvent, KeyboardEvent} from 'react';
|
||||
|
||||
import Contents from './contents';
|
||||
import CustomStatusEmoji from 'components/custom_status/custom_status_emoji';
|
||||
import CustomStatusModal from 'components/custom_status/custom_status_modal';
|
||||
|
||||
export default function MobileSidebarHeader() {
|
||||
const intl = useIntl();
|
||||
const ariaLabel = intl.formatMessage({id: 'accessibility.sections.lhsHeader', defaultMessage: 'team menu region'});
|
||||
import {ModalIdentifiers} from 'utils/constants';
|
||||
|
||||
import type {PropsFromRedux} from './index';
|
||||
import './mobile_sidebar_header.scss';
|
||||
|
||||
type Props = PropsFromRedux;
|
||||
|
||||
export default function MobileSidebarHeader(props: Props) {
|
||||
if (!props.username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleCustomStatusEmojiClick(event: MouseEvent<HTMLSpanElement> | KeyboardEvent<HTMLSpanElement>) {
|
||||
event.stopPropagation();
|
||||
|
||||
const customStatusInputModalData = {
|
||||
modalId: ModalIdentifiers.CUSTOM_STATUS,
|
||||
dialogType: CustomStatusModal,
|
||||
};
|
||||
props.openModal(customStatusInputModalData);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id='lhsHeader'
|
||||
aria-label={ariaLabel}
|
||||
tabIndex={-1}
|
||||
role='application'
|
||||
className='SidebarHeader team__header theme a11y__region'
|
||||
data-a11y-sort-order='5'
|
||||
>
|
||||
<div
|
||||
className='d-flex'
|
||||
>
|
||||
<Contents/>
|
||||
<div className='mobileSidebarHeader'>
|
||||
<h1>{props.teamDisplayName}</h1>
|
||||
<div className='mobileSidebarHeader__username'>
|
||||
<span>{'@' + props.username}</span>
|
||||
<CustomStatusEmoji
|
||||
showTooltip={false}
|
||||
emojiStyle={{
|
||||
verticalAlign: 'top',
|
||||
}}
|
||||
onClick={handleCustomStatusEmojiClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
// 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 {DeepPartial} from '@mattermost/types/utilities';
|
||||
|
||||
import {Preferences} from 'mattermost-redux/constants';
|
||||
import {Permissions, Preferences} from 'mattermost-redux/constants';
|
||||
|
||||
import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
|
||||
import {renderWithContext, screen, waitFor} from 'tests/react_testing_utils';
|
||||
import {fireEvent, renderWithContext, screen, waitFor} from 'tests/react_testing_utils';
|
||||
import Constants, {ModalIdentifiers} from 'utils/constants';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
@@ -18,14 +17,61 @@ import type {GlobalState} from 'types/store';
|
||||
import Sidebar from './sidebar';
|
||||
|
||||
describe('components/sidebar', () => {
|
||||
const currentTeamId = 'fake_team_id';
|
||||
const currentTeam = TestHelper.getTeamMock({
|
||||
id: 'current_team_id',
|
||||
display_name: 'Current Test Team',
|
||||
});
|
||||
|
||||
const initialState: DeepPartial<GlobalState> = {
|
||||
entities: {
|
||||
teams: {
|
||||
currentTeamId: currentTeam.id,
|
||||
teams: {
|
||||
[currentTeam.id]: currentTeam,
|
||||
},
|
||||
myMembers: {
|
||||
[currentTeam.id]: {
|
||||
roles: 'team_user',
|
||||
},
|
||||
},
|
||||
},
|
||||
users: {
|
||||
currentUserId: 'current_user_id',
|
||||
profiles: {
|
||||
current_user_id: {
|
||||
id: 'current_user_id',
|
||||
roles: 'system_user system_admin',
|
||||
},
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
roles: {
|
||||
system_admin: {
|
||||
permissions: [Permissions.MANAGE_TEAM],
|
||||
},
|
||||
system_user: {
|
||||
permissions: [],
|
||||
},
|
||||
team_user: {
|
||||
permissions: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
myPreferences: {},
|
||||
},
|
||||
general: {
|
||||
config: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const baseProps = {
|
||||
canCreatePublicChannel: true,
|
||||
canCreatePrivateChannel: true,
|
||||
canJoinPublicChannel: true,
|
||||
isOpen: false,
|
||||
teamId: currentTeamId,
|
||||
teamId: currentTeam.id,
|
||||
hasSeenModal: true,
|
||||
isCloud: false,
|
||||
unreadFilterEnabled: false,
|
||||
@@ -43,94 +89,36 @@ describe('components/sidebar', () => {
|
||||
},
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
test('should render the sidebar components correctly', () => {
|
||||
renderWithContext(
|
||||
<Sidebar {...baseProps}/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
// Check for SidebarContainer that is the parent of the sidebar
|
||||
expect(document.getElementById('SidebarContainer')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByRole('application', {name: /channel sidebar region/i})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should match snapshot when direct channels modal is open', () => {
|
||||
const wrapper = shallow(
|
||||
<Sidebar {...baseProps}/>,
|
||||
);
|
||||
|
||||
wrapper.instance().setState({showDirectChannelsModal: true});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when more channels modal is open', () => {
|
||||
const wrapper = shallow(
|
||||
<Sidebar {...baseProps}/>,
|
||||
);
|
||||
|
||||
wrapper.instance().setState({showMoreChannelsModal: true});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Should call Shortcut modal on FORWARD_SLASH+ctrl/meta', () => {
|
||||
const wrapper = shallow<Sidebar>(
|
||||
<Sidebar {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
let key = Constants.KeyCodes.BACK_SLASH[0] as string;
|
||||
let keyCode = Constants.KeyCodes.BACK_SLASH[1] as number;
|
||||
instance.handleKeyDownEvent({ctrlKey: true, preventDefault: jest.fn(), key, keyCode} as any);
|
||||
expect(wrapper.instance().props.actions.openModal).not.toHaveBeenCalled();
|
||||
|
||||
key = 'ù';
|
||||
keyCode = Constants.KeyCodes.FORWARD_SLASH[1] as number;
|
||||
instance.handleKeyDownEvent({ctrlKey: true, preventDefault: jest.fn(), key, keyCode} as any);
|
||||
expect(wrapper.instance().props.actions.openModal).toHaveBeenCalledWith(expect.objectContaining({modalId: ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL}));
|
||||
|
||||
key = '/';
|
||||
keyCode = Constants.KeyCodes.SEVEN[1] as number;
|
||||
instance.handleKeyDownEvent({ctrlKey: true, preventDefault: jest.fn(), key, keyCode} as any);
|
||||
expect(wrapper.instance().props.actions.openModal).toHaveBeenCalledWith(expect.objectContaining({modalId: ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL}));
|
||||
|
||||
key = Constants.KeyCodes.FORWARD_SLASH[0] as string;
|
||||
keyCode = Constants.KeyCodes.FORWARD_SLASH[1] as number;
|
||||
instance.handleKeyDownEvent({ctrlKey: true, preventDefault: jest.fn(), key, keyCode} as any);
|
||||
expect(wrapper.instance().props.actions.openModal).toHaveBeenCalledWith(expect.objectContaining({modalId: ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL}));
|
||||
});
|
||||
|
||||
test('should toggle direct messages modal correctly', () => {
|
||||
const wrapper = shallow<Sidebar>(
|
||||
<Sidebar {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
const mockEvent: Partial<Event> = {preventDefault: jest.fn()};
|
||||
|
||||
instance.hideMoreDirectChannelsModal = jest.fn();
|
||||
instance.showMoreDirectChannelsModal = jest.fn();
|
||||
|
||||
instance.handleOpenMoreDirectChannelsModal(mockEvent as any);
|
||||
expect(instance.showMoreDirectChannelsModal).toHaveBeenCalled();
|
||||
|
||||
instance.setState({showDirectChannelsModal: true});
|
||||
instance.handleOpenMoreDirectChannelsModal(mockEvent as any);
|
||||
expect(instance.hideMoreDirectChannelsModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should match empty div snapshot when teamId is missing', () => {
|
||||
test('should not rendering anything when teamId is missing', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
teamId: '',
|
||||
};
|
||||
const wrapper = shallow(
|
||||
renderWithContext(
|
||||
<Sidebar {...props}/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(screen.queryByRole('application', {name: /channel sidebar region/i})).toBeNull();
|
||||
});
|
||||
|
||||
describe('unreads category', () => {
|
||||
const currentUserId = 'current_user_id';
|
||||
|
||||
const channel1 = TestHelper.getChannelMock({id: 'channel1', team_id: currentTeamId});
|
||||
const channel2 = TestHelper.getChannelMock({id: 'channel2', team_id: currentTeamId});
|
||||
const channel1 = TestHelper.getChannelMock({id: 'channel1', team_id: currentTeam.id});
|
||||
const channel2 = TestHelper.getChannelMock({id: 'channel2', team_id: currentTeam.id});
|
||||
|
||||
const baseState: DeepPartial<GlobalState> = {
|
||||
entities: {
|
||||
@@ -141,7 +129,7 @@ describe('components/sidebar', () => {
|
||||
channel2,
|
||||
},
|
||||
channelsInTeam: {
|
||||
[currentTeamId]: new Set([channel1.id, channel2.id]),
|
||||
[currentTeam.id]: new Set([channel1.id, channel2.id]),
|
||||
},
|
||||
messageCounts: {
|
||||
channel1: {total: 10},
|
||||
@@ -153,13 +141,40 @@ describe('components/sidebar', () => {
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId,
|
||||
currentTeamId: currentTeam.id,
|
||||
teams: {
|
||||
[currentTeamId]: TestHelper.getTeamMock({id: currentTeamId}),
|
||||
[currentTeam.id]: currentTeam,
|
||||
},
|
||||
myMembers: {
|
||||
[currentTeam.id]: {
|
||||
roles: 'team_user',
|
||||
},
|
||||
},
|
||||
},
|
||||
users: {
|
||||
currentUserId,
|
||||
profiles: {
|
||||
[currentUserId]: {
|
||||
id: currentUserId,
|
||||
roles: 'system_user system_admin',
|
||||
},
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
roles: {
|
||||
system_admin: {
|
||||
permissions: [Permissions.MANAGE_TEAM],
|
||||
},
|
||||
system_user: {
|
||||
permissions: [],
|
||||
},
|
||||
team_user: {
|
||||
permissions: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
general: {
|
||||
config: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -263,4 +278,111 @@ describe('components/sidebar', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('modals', () => {
|
||||
test('should call Shortcut modal on FORWARD_SLASH+ctrl/meta', () => {
|
||||
const openModalSpy = jest.fn();
|
||||
const closeModalSpy = jest.fn();
|
||||
|
||||
const props = {
|
||||
...baseProps,
|
||||
isKeyBoardShortcutModalOpen: false,
|
||||
actions: {
|
||||
...baseProps.actions,
|
||||
openModal: openModalSpy,
|
||||
closeModal: closeModalSpy,
|
||||
},
|
||||
};
|
||||
|
||||
renderWithContext(
|
||||
<Sidebar {...props}/>,
|
||||
initialState,
|
||||
);
|
||||
expect(document.getElementById('SidebarContainer')).toBeInTheDocument();
|
||||
|
||||
// Test with backslash key (should not trigger the modal)
|
||||
fireEvent.keyDown(document, {
|
||||
key: '\\',
|
||||
code: 'Backslash',
|
||||
keyCode: Constants.KeyCodes.BACK_SLASH[1],
|
||||
ctrlKey: true,
|
||||
});
|
||||
expect(openModalSpy).not.toHaveBeenCalled();
|
||||
|
||||
// Test with 'ù' key but with forward slash keyCode (should trigger the modal)
|
||||
fireEvent.keyDown(document, {
|
||||
key: 'ù',
|
||||
code: 'Slash',
|
||||
keyCode: Constants.KeyCodes.FORWARD_SLASH[1],
|
||||
ctrlKey: true,
|
||||
});
|
||||
expect(openModalSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
modalId: ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL,
|
||||
}));
|
||||
|
||||
// Reset the spy
|
||||
openModalSpy.mockClear();
|
||||
|
||||
// Test with '/' key but with seven keyCode (should trigger the modal)
|
||||
fireEvent.keyDown(document, {
|
||||
key: '/',
|
||||
code: 'Digit7',
|
||||
keyCode: Constants.KeyCodes.SEVEN[1],
|
||||
ctrlKey: true,
|
||||
});
|
||||
expect(openModalSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
modalId: ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL,
|
||||
}));
|
||||
|
||||
// Reset the spy
|
||||
openModalSpy.mockClear();
|
||||
|
||||
// Test with forward slash key (should trigger the modal)
|
||||
fireEvent.keyDown(document, {
|
||||
key: '/',
|
||||
code: 'Slash',
|
||||
keyCode: Constants.KeyCodes.FORWARD_SLASH[1],
|
||||
ctrlKey: true,
|
||||
});
|
||||
expect(openModalSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
modalId: ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL,
|
||||
}));
|
||||
});
|
||||
|
||||
test('should close Shortcut modal on FORWARD_SLASH+ctrl/meta when already open', () => {
|
||||
const openModalSpy = jest.fn();
|
||||
const closeModalSpy = jest.fn();
|
||||
|
||||
const props = {
|
||||
...baseProps,
|
||||
isKeyBoardShortcutModalOpen: true, // Modal is already open
|
||||
actions: {
|
||||
...baseProps.actions,
|
||||
openModal: openModalSpy,
|
||||
closeModal: closeModalSpy,
|
||||
},
|
||||
};
|
||||
|
||||
renderWithContext(
|
||||
<Sidebar {...props}/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
expect(document.getElementById('SidebarContainer')).toBeInTheDocument();
|
||||
|
||||
// Test with forward slash key (should close the modal since it's already open)
|
||||
fireEvent.keyDown(document, {
|
||||
key: '/',
|
||||
code: 'Slash',
|
||||
keyCode: Constants.KeyCodes.FORWARD_SLASH[1],
|
||||
ctrlKey: true,
|
||||
});
|
||||
|
||||
// Should call closeModal with the keyboard shortcuts modal ID
|
||||
expect(closeModalSpy).toHaveBeenCalledWith(ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL);
|
||||
|
||||
// Should not call openModal
|
||||
expect(openModalSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,8 @@ import * as Menu from 'components/menu';
|
||||
import {OnboardingTourSteps} from 'components/tours';
|
||||
import {useShowOnboardingTutorialStep, CreateAndJoinChannelsTour, InvitePeopleTour} from 'components/tours/onboarding_tour';
|
||||
|
||||
export const ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU = 'browseOrAddChannelMenuButton';
|
||||
export const ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU = 'browserOrAddChannelMenu';
|
||||
export const ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU_BUTTON = 'browseOrAddChannelMenuButton';
|
||||
|
||||
type Props = {
|
||||
canCreateChannel: boolean;
|
||||
@@ -52,6 +53,7 @@ export default function SidebarBrowserOrAddChannelMenu(props: Props) {
|
||||
/>
|
||||
)}
|
||||
trailingElements={showCreateAndJoinChannelsTutorialTip && <CreateAndJoinChannelsTour/>}
|
||||
aria-haspopup='true'
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -69,6 +71,7 @@ export default function SidebarBrowserOrAddChannelMenu(props: Props) {
|
||||
defaultMessage='Browse channels'
|
||||
/>
|
||||
)}
|
||||
aria-haspopup='true'
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -84,6 +87,7 @@ export default function SidebarBrowserOrAddChannelMenu(props: Props) {
|
||||
defaultMessage='Open a direct message'
|
||||
/>
|
||||
)}
|
||||
aria-haspopup='true'
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -100,6 +104,7 @@ export default function SidebarBrowserOrAddChannelMenu(props: Props) {
|
||||
defaultMessage='Create new user group'
|
||||
/>
|
||||
)}
|
||||
aria-haspopup='true'
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -117,6 +122,7 @@ export default function SidebarBrowserOrAddChannelMenu(props: Props) {
|
||||
defaultMessage='Create new category'
|
||||
/>
|
||||
)}
|
||||
aria-haspopup='true'
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -139,13 +145,14 @@ export default function SidebarBrowserOrAddChannelMenu(props: Props) {
|
||||
</>
|
||||
)}
|
||||
trailingElements={showInvitePeopleTutorialTip && <InvitePeopleTour/>}
|
||||
aria-haspopup='true'
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Menu.Container
|
||||
menuButton={{
|
||||
id: ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU,
|
||||
id: ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU_BUTTON,
|
||||
'aria-label': formatMessage({
|
||||
id: 'sidebarLeft.browserOrCreateChannelMenuButton.label',
|
||||
defaultMessage: 'Browse or create channels',
|
||||
@@ -158,7 +165,7 @@ export default function SidebarBrowserOrAddChannelMenu(props: Props) {
|
||||
}}
|
||||
menu={{
|
||||
id: 'browserOrAddChannelMenu',
|
||||
'aria-labelledby': ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU,
|
||||
'aria-labelledby': ELEMENT_ID_FOR_BROWSE_OR_ADD_CHANNEL_MENU_BUTTON,
|
||||
}}
|
||||
>
|
||||
{createNewChannelMenuItem}
|
||||
|
||||
@@ -6,31 +6,31 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.sidebarHeader {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
color: var(--sidebar-text);
|
||||
cursor: pointer;
|
||||
font-family: Metropolis, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
|
||||
.title {
|
||||
display: inline-block;
|
||||
button#sidebarTeamMenuButton {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.icon-chevron-down {
|
||||
margin-right: -1px;
|
||||
margin-left: -3px;
|
||||
}
|
||||
|
||||
#SidebarContainer & {
|
||||
padding: 2px 6px;
|
||||
font-family: Metropolis, sans-serif;
|
||||
font-size: 16px;
|
||||
gap: 1px;
|
||||
|
||||
& > span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
& > .icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#sidebarTeamMenu {
|
||||
.learnAboutTeamsMenuItem {
|
||||
.leading-element,.label-elements>span {
|
||||
color: var(--link-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,141 +1,156 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import IconButton from '@mattermost/compass-components/components/icon-button'; // eslint-disable-line no-restricted-imports
|
||||
import {Permissions} from 'mattermost-redux/constants';
|
||||
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import {CloudProducts} from 'utils/constants';
|
||||
import {FileSizes} from 'utils/file_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import BrowserOrAddChannelMenu from './sidebar_browse_or_add_channel_menu';
|
||||
import SidebarHeader from './sidebar_header';
|
||||
import type {Props} from './sidebar_header';
|
||||
|
||||
let props: Props;
|
||||
describe('SidebarHeader', () => {
|
||||
const defaultProps: Props = {
|
||||
showNewChannelModal: jest.fn(),
|
||||
showMoreChannelsModal: jest.fn(),
|
||||
invitePeopleModal: jest.fn(),
|
||||
showCreateCategoryModal: jest.fn(),
|
||||
canCreateChannel: true,
|
||||
canJoinPublicChannel: true,
|
||||
handleOpenDirectMessagesModal: jest.fn(),
|
||||
unreadFilterEnabled: true,
|
||||
showCreateUserGroupModal: jest.fn(),
|
||||
canCreateCustomGroups: true,
|
||||
};
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
let mockState: any;
|
||||
const team = TestHelper.getTeamMock({
|
||||
display_name: 'Steadfast',
|
||||
});
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux') as typeof import('react-redux'),
|
||||
useSelector: (selector: (state: typeof mockState) => unknown) => selector(mockState),
|
||||
useDispatch: () => mockDispatch,
|
||||
}));
|
||||
|
||||
describe('Components/SidebarHeader', () => {
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
showNewChannelModal: jest.fn(),
|
||||
showMoreChannelsModal: jest.fn(),
|
||||
invitePeopleModal: jest.fn(),
|
||||
showCreateCategoryModal: jest.fn(),
|
||||
canCreateChannel: true,
|
||||
canJoinPublicChannel: true,
|
||||
handleOpenDirectMessagesModal: jest.fn(),
|
||||
unreadFilterEnabled: true,
|
||||
showCreateUserGroupModal: jest.fn(),
|
||||
canCreateCustomGroups: true,
|
||||
};
|
||||
|
||||
mockState = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {},
|
||||
const initialState = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {},
|
||||
},
|
||||
preferences: {
|
||||
myPreferences: {},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: team.id,
|
||||
teams: {
|
||||
[team.id]: team,
|
||||
},
|
||||
preferences: {
|
||||
myPreferences: {},
|
||||
myMembers: {
|
||||
[team.id]: {
|
||||
roles: 'team_user',
|
||||
},
|
||||
},
|
||||
},
|
||||
users: {
|
||||
profiles: {
|
||||
uid: {
|
||||
id: 'uid',
|
||||
roles: 'system_user system_admin',
|
||||
},
|
||||
},
|
||||
currentUserId: 'uid',
|
||||
},
|
||||
roles: {
|
||||
roles: {
|
||||
system_admin: {
|
||||
permissions: [Permissions.MANAGE_TEAM],
|
||||
},
|
||||
system_user: {
|
||||
permissions: [],
|
||||
},
|
||||
team_user: {
|
||||
permissions: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
usage: {
|
||||
integrations: {
|
||||
enabled: 11,
|
||||
enabledLoaded: true,
|
||||
},
|
||||
messages: {
|
||||
history: 10000,
|
||||
historyLoaded: true,
|
||||
},
|
||||
files: {
|
||||
totalStorage: FileSizes.Gigabyte,
|
||||
totalStorageLoaded: true,
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: 'currentteam',
|
||||
teams: {
|
||||
currentteam: {
|
||||
id: 'currentteam',
|
||||
description: 'et iste illum reprehenderit aliquid in rem itaque in maxime eius.',
|
||||
},
|
||||
active: 1,
|
||||
teamsLoaded: true,
|
||||
},
|
||||
boards: {
|
||||
cards: 500,
|
||||
cardsLoaded: true,
|
||||
},
|
||||
},
|
||||
cloud: {
|
||||
subscription: {
|
||||
product_id: 'test_prod_1',
|
||||
trial_end_at: 1652807380,
|
||||
is_free_trial: 'false',
|
||||
},
|
||||
products: {
|
||||
test_prod_1: {
|
||||
id: 'test_prod_1',
|
||||
sku: CloudProducts.STARTER,
|
||||
price_per_seat: 0,
|
||||
},
|
||||
},
|
||||
users: {
|
||||
profiles: {
|
||||
uid: {},
|
||||
},
|
||||
currentUserId: 'uid',
|
||||
},
|
||||
usage: {
|
||||
integrations: {
|
||||
enabled: 11,
|
||||
enabledLoaded: true,
|
||||
},
|
||||
messages: {
|
||||
history: 10000,
|
||||
historyLoaded: true,
|
||||
},
|
||||
files: {
|
||||
totalStorage: FileSizes.Gigabyte,
|
||||
totalStorageLoaded: true,
|
||||
},
|
||||
teams: {
|
||||
active: 1,
|
||||
teamsLoaded: true,
|
||||
},
|
||||
boards: {
|
||||
cards: 500,
|
||||
cardsLoaded: true,
|
||||
},
|
||||
},
|
||||
cloud: {
|
||||
subscription: {
|
||||
product_id: 'test_prod_1',
|
||||
trial_end_at: 1652807380,
|
||||
is_free_trial: 'false',
|
||||
},
|
||||
products: {
|
||||
test_prod_1: {
|
||||
id: 'test_prod_1',
|
||||
sku: CloudProducts.STARTER,
|
||||
price_per_seat: 0,
|
||||
},
|
||||
},
|
||||
limits: {
|
||||
limitsLoaded: true,
|
||||
limits: {
|
||||
limitsLoaded: true,
|
||||
limits: {
|
||||
integrations: {
|
||||
enabled: 10,
|
||||
},
|
||||
messages: {
|
||||
history: 10000,
|
||||
},
|
||||
files: {
|
||||
total_storage: FileSizes.Gigabyte,
|
||||
},
|
||||
teams: {
|
||||
active: 1,
|
||||
},
|
||||
boards: {
|
||||
cards: 500,
|
||||
views: 5,
|
||||
},
|
||||
integrations: {
|
||||
enabled: 10,
|
||||
},
|
||||
messages: {
|
||||
history: 10000,
|
||||
},
|
||||
files: {
|
||||
total_storage: FileSizes.Gigabyte,
|
||||
},
|
||||
teams: {
|
||||
active: 1,
|
||||
},
|
||||
boards: {
|
||||
cards: 500,
|
||||
views: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
addChannelDropdown: {
|
||||
isOpen: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
test('should render the team menu button', () => {
|
||||
renderWithContext(<SidebarHeader {...defaultProps}/>, initialState);
|
||||
|
||||
expect(screen.getByText('Steadfast')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {name: team.display_name})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show BrowserOrAddChannelMenu', () => {
|
||||
const wrapper = shallow(<SidebarHeader {...props}/>);
|
||||
expect(wrapper.find(BrowserOrAddChannelMenu).length).toBe(1);
|
||||
test('should render the \'Browse or create channels\' menu button', () => {
|
||||
renderWithContext(<SidebarHeader {...defaultProps}/>, initialState);
|
||||
|
||||
expect(screen.getByRole('button', {name: /Browse or create channels/i})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should embed teams menu dropdown into heading', () => {
|
||||
const wrapper = shallow(<SidebarHeader {...props}/>);
|
||||
expect(wrapper.find(IconButton).length).toBe(0);
|
||||
expect(wrapper.find('i').prop('className')).toBe('icon icon-chevron-down');
|
||||
test('should not render anything when team is empty', () => {
|
||||
const state = {...initialState};
|
||||
state.entities.teams.currentTeamId = '';
|
||||
renderWithContext(<SidebarHeader {...defaultProps}/>, state);
|
||||
|
||||
expect(screen.queryByRole('button', {name: team.display_name})).toBeNull();
|
||||
expect(screen.queryByRole('button', {name: /Add Channel Dropdown/i})).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Ссылка в новой задаче
Block a user