mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-03 20:40:00 -05:00
MM-45255 Update web app to React 18 (#33858)
* Dependencies: Remove unused dependency on @mattermost/dynamic-virtualized-list * Update Components package to React 18 and new version of RTL * Upgrade React, React Redux, RTL, and associated libraries I had to upgrade React Redux for the new version of React, and that brought with it new versions of associated packges (Redux, Reselect, Redux Thunk). A few other libraries needed to be updated or have their explicit dependencies overridden for the new version of Redux as well. To note for future dependency upgrades, redux-mock-store is deprecated, and redux-batched-actions and redux-persist are inactive. For RTL: 1. `@testing-library/react-hooks` has been rolled into `@testing-library/react`, and its interface has changed. 2. I had to make some changes to get TS to use the types for the new methods added to `expect`. * Dependencies: Fix dom-accessibility-api patch from #33553 * Tests: Fix tests that use jest.spyOn with react-redux * Functional: Remove usage of defaultProps on function components * Tests: Remove usage of react-dom/test-utils * Functional: Rename conflicting context prop on Apps components * Tests: Always await on userEvent calls * Functional: Patch react-overlays to fix pressing escape in unit tests I did something similar in React Bootstrap a few weeks ago. See https://github.com/mattermost/react-bootstrap/pull/5 * Tests: Prevent tests from fetching from real URLs * Tests: Update snapshots changed by upgrading react-redux and styled-components * Functional: Stop passing deprecated pure parameter to connect * Tests: Change how we intercept console errors in tests to preserve stack traces * Tests: Fix incorrect usage of act in Enzyme tests These tests are a mix of: 1. Not calling act when performing something that will update the DOM (like clicking on a button or invoking a method) which either caused warnings or failed snapshots as changes weren't visible. 2. Calling act in weird ways (such as wrapping mount in an async act) which caused Enzyme to not function Some of these changes just silence warnings, but most of them are required to make the test even run * Tests: Fix incorrect usage of act in RTL tests * Tests: Fix a few minor issues in tests * Functional: Add note for why we're not using ReactDOM.createRoot * Functional: Fix focus trap infinite recursion in test * Types: Replace removed React.SFC * Types: Fix type of functions in FormattedMessage values prop * Functional: Fix DropdownInputHybrid placeholder * Types: Patch type definitions of react-batched-actions * Types: Fix mattermost-redux build failing due to type check in Redux Dev Tools * Dependencies: Add type definitions for react-is * Types: Update types around ReactNode and ReactElement React's gotten more strict with these, so we need to be more careful with what we return from a render method. In some of these places, we also misused some types, so hopefully I've sorted those out. * Types: Explicitly added types to all instances of useCallback * Types/Tests: Update typing of Redux actions and hooks useDispatch is sort of stricter now, but it doesn't seem to rely on the global type of `Dispatch` any more, so I ended up having to add an extra overload to make that work. * Tests: Update new tests in useChannelSystemPolicies for new version of RTL These were added on master after I updated RTL on the branch * Tests: Update action used to test initial store state * Functional: Remove remnants of code for hot reloading Redux store * Types/Tests: Update typing around React Router * Types/Functional: Update typing involving the FormattedMessage values prop There's a couple functional changes to ensure that the value passed is either a valid string or Date * Types: Misc fixes * Functional: Don't pass unused props to ChannelHeader * Functional: Ensure plugin setting text is rendered before passing to Setting component The previous version might've allowed MessageDescriptors to be passed unformatted into the Setting component (which would then be rendered in the DOM). As best as I can tell, we never actually did that, so this shouldn't change anything in practice. * Tests: Make tests for identifyElementRegion more reliable * Tests: Update recent tests for new version of RTL * E2E: Make editLastPostWithNewMessage more reliable * Downgrade React to 18.2.0 and manually dedupe React versions 18.2.0 and 18.3.0 are nearly identical to one another, except 18.3.x includes warnings when using any API that will be removed in React 19. I don't want to flood the console and test logs with warnings for things we're not addressing for the time being. Also, they didn't export act from React itself until 18.3.1 for some reason (despite the old import path printing a warning), so I needed to revert the changes to its import path. To get this all to work, for some reason, I had to manually delete all the entries for `react` and `react-dom` from the lockfile to get NPM to use a single version of React and ReactDOM everywhere. I did discover `npm dedupe` in the process, but that didn't solve this problem where I was trying to consistently downgrade everything. * Revert changes to notice file build tool to speed up CI * Add explicit version of types/scheduler The version of `@types/react` that we use says it works with any version of `@types/scheduler` which causes NPM to install a newer version of it which is missing a file of types that it needs. * Update new test to await on userEvent * Fix Playwright test that relied on autogenerated class name * Tests: Disable test for identifyElementRegion * Functional: Change DynamicVirtualizedList ListItem to use useLayoutEffect In a previous commit, I changed the RHS to use the monorepo DynamicVirtualizedList instead of the old version that lived in its own repo. That caused the RHS to no longer scroll to the bottom on initial mount or be able to render additional pages (even though the posts are loaded). This seems like it has to do with the improved size calculation that Zubair made because that's the main difference in the monorepo version of that component. For some reason I don't entirely understand, changing to useLayoutEffect seems to fix that. I think that's because the old measurement code is written as a class component, and useLayoutEffect fires at the same time as componentDidMount/componentDidUpdate. * Types: Revert some type changes to reduce test log output * Functional: Fix places where useSelector returned new results when called with the same arguments I wasn't planning on fixing this now, but I think the increased length of the warning logs in the tests are causing the GitHub action for the unit tests to abort as it reaches around 10000 lines long. * Tests: Fix place where mocked selector returned new results when called with the same arguments Same reason as before, but this one only occurred because of a test's mocked selector. I replaced it with a real one to get around that. * Tests: Fail tests when selector returns new results when called with the same arguments * Attempt to fix web app unit tests failing in CI * Change CI tests to set workerIdleMemoryLimit instead of reducing maxWorkers * Increase workerIdleMemoryLimit in CI * Revert changes to test-ci command and revert changes to how unit test logs are collected * Unrevert changes to test logging, re-add workerIdleMemoryLimit, and try disabling test coverage * Actually disable coverage * Fix flaky test * Update a couple new tests to fit PR and remove an unnecessary act * Replace bad mock in new unit test * Fix types of new code * Remove mock from new unit test
This commit is contained in:
parent
ff3cc4e837
commit
535d93ee98
344 changed files with 3259 additions and 4192 deletions
|
|
@ -71,7 +71,7 @@ function editLastPostWithNewMessage(message: string) {
|
|||
|
||||
// # Update the post message and click Save
|
||||
cy.get('#edit_textbox').clear().type(message)
|
||||
cy.get('#create_post').findByText('Save').should('be.visible').click();
|
||||
cy.get('#create_post').findByText('Save').scrollIntoView().click();
|
||||
}
|
||||
Cypress.Commands.add('editLastPostWithNewMessage', editLastPostWithNewMessage);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ export default class MessagePriority {
|
|||
// Priority menu that opens when clicking the icon
|
||||
this.priorityMenu = container.locator('[role="menu"]');
|
||||
|
||||
// Standard priority option in the menu (id comes from webapp implementation)
|
||||
this.standardPriorityOption = this.priorityMenu.locator('#menu-item-priority-standard');
|
||||
|
||||
// Priority dialog elements
|
||||
this.priorityDialog = container.page().getByRole('menu');
|
||||
this.dialogHeader = container.page().locator('h4.modal-title');
|
||||
|
||||
// Standard priority option in the menu
|
||||
this.standardPriorityOption = this.priorityDialog.getByRole('menuitemradio', {name: 'Standard'});
|
||||
}
|
||||
|
||||
async clickPriorityIcon() {
|
||||
|
|
@ -67,10 +67,4 @@ export default class MessagePriority {
|
|||
await expect(this.priorityDialog).toBeVisible();
|
||||
await expect(this.dialogHeader).toHaveText('Message priority');
|
||||
}
|
||||
|
||||
async verifyStandardOptionSelected() {
|
||||
const standardOption = this.priorityDialog.getByRole('menuitemradio', {name: 'Standard'});
|
||||
await expect(standardOption).toBeVisible();
|
||||
await expect(standardOption.locator('svg.StyledCheckIcon-dFKfoY')).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ test(
|
|||
|
||||
// * Verify priority dialog appears with standard option selected
|
||||
await channelsPage.messagePriority.verifyPriorityDialog();
|
||||
await channelsPage.messagePriority.verifyStandardOptionSelected();
|
||||
await channelsPage.messagePriority.verifyStandardPrioritySelected();
|
||||
|
||||
// # Close priority menu
|
||||
await channelsPage.messagePriority.closePriorityMenu();
|
||||
|
|
|
|||
|
|
@ -6,20 +6,21 @@
|
|||
"version": "10.12.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "0.26.28",
|
||||
"@floating-ui/react": "0.26.6",
|
||||
"@giphy/js-fetch-api": "5.1.0",
|
||||
"@giphy/react-components": "8.1.0",
|
||||
"@guyplusplus/turndown-plugin-gfm": "1.0.7",
|
||||
"@mattermost/client": "10.12.0",
|
||||
"@mattermost/desktop-api": "5.10.0-2",
|
||||
"@mattermost/dynamic-virtualized-list": "github:mattermost/dynamic-virtualized-list#08dde0c34a12d0384740db27d55e398d139d7a51",
|
||||
"@mattermost/types": "10.12.0",
|
||||
"@mui/base": "5.0.0-alpha.127",
|
||||
"@mui/material": "5.11.16",
|
||||
"@mui/styled-engine-sc": "5.11.11",
|
||||
"@redux-devtools/extension": "3.3.0",
|
||||
"@tanstack/react-table": "8.10.7",
|
||||
"@tippyjs/react": "4.2.6",
|
||||
"@types/color-hash": "1.0.2",
|
||||
"@types/react-is": "18.2.1",
|
||||
"@types/turndown": "5.0.5",
|
||||
"bootstrap": "3.4.1",
|
||||
"buffer": "6.0.3",
|
||||
|
|
@ -58,16 +59,16 @@
|
|||
"pdfjs-dist": "4.4.168",
|
||||
"process": "0.11.10",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "17.0.2",
|
||||
"react": "18.2.0",
|
||||
"react-beautiful-dnd": "13.1.1",
|
||||
"react-bootstrap": "github:mattermost/react-bootstrap#7d8660f06188a6433bb0f69b5816a97dc9ebe48c",
|
||||
"react-bootstrap": "github:mattermost/react-bootstrap#05559f4c61c5a314783c390d2d82906ee8c7e558",
|
||||
"react-color": "2.19.3",
|
||||
"react-day-picker": "8.3.6",
|
||||
"react-dom": "17.0.2",
|
||||
"react-dom": "18.2.0",
|
||||
"react-intl": "*",
|
||||
"react-is": "17.0.2",
|
||||
"react-is": "18.2.0",
|
||||
"react-overlays": "0.9.3",
|
||||
"react-redux": "7.2.4",
|
||||
"react-redux": "9.2.0",
|
||||
"react-router-dom": "5.3.4",
|
||||
"react-select": "5.9.0",
|
||||
"react-transition-group": "4.4.5",
|
||||
|
|
@ -75,10 +76,10 @@
|
|||
"react-window": "1.8.11",
|
||||
"react-window-infinite-loader": "1.0.10",
|
||||
"rebound": "0.1.0",
|
||||
"redux": "4.2.0",
|
||||
"redux": "5.0.1",
|
||||
"redux-batched-actions": "0.5.0",
|
||||
"redux-persist": "6.0.0",
|
||||
"redux-thunk": "2.4.2",
|
||||
"redux-thunk": "3.1.0",
|
||||
"regenerator-runtime": "0.13.10",
|
||||
"semver": "7.6.3",
|
||||
"serialize-error": "11.0.3",
|
||||
|
|
@ -94,16 +95,16 @@
|
|||
"zen-observable": "0.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cfaester/enzyme-adapter-react-18": "0.8.0",
|
||||
"@deanwhillier/jest-matchmedia-mock": "1.2.0",
|
||||
"@mattermost/calls-common": "0.27.0",
|
||||
"@mattermost/eslint-plugin": "*",
|
||||
"@mattermost/mmjstool": "github:mattermost/mattermost-utilities#7b63833d208d482ba4a1c12230bb3e68dd9c5e5e",
|
||||
"@redux-devtools/extension": "3.2.3",
|
||||
"@stylistic/stylelint-plugin": "3.1.2",
|
||||
"@testing-library/jest-dom": "5.16.4",
|
||||
"@testing-library/react": "12.1.4",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@testing-library/user-event": "13.5.0",
|
||||
"@testing-library/dom": "10.4.1",
|
||||
"@testing-library/jest-dom": "6.8.0",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/country-list": "2.1.0",
|
||||
"@types/enzyme": "3.10.11",
|
||||
"@types/jest": "28.1.8",
|
||||
|
|
@ -112,33 +113,32 @@
|
|||
"@types/luxon": "3.0.2",
|
||||
"@types/mark.js": "8.11.6",
|
||||
"@types/marked": "0.7.4",
|
||||
"@types/react": "17.0.83",
|
||||
"@types/react": "18.2.64",
|
||||
"@types/react-beautiful-dnd": "13.1.2",
|
||||
"@types/react-bootstrap": "0.32.35",
|
||||
"@types/react-color": "3.0.6",
|
||||
"@types/react-custom-scrollbars": "4.0.10",
|
||||
"@types/react-dom": "17.0.25",
|
||||
"@types/react-is": "17.0.2",
|
||||
"@types/react-dom": "18.2.25",
|
||||
"@types/react-overlays": "1.1.3",
|
||||
"@types/react-redux": "7.1.31",
|
||||
"@types/react-router-dom": "5.3.3",
|
||||
"@types/react-transition-group": "4.4.5",
|
||||
"@types/react-virtualized-auto-sizer": "1.0.1",
|
||||
"@types/react-window": "1.8.5",
|
||||
"@types/react-window-infinite-loader": "1.0.6",
|
||||
"@types/redux-mock-store": "1.0.3",
|
||||
"@types/redux-mock-store": "1.5.0",
|
||||
"@types/regenerator-runtime": "0.13.8",
|
||||
"@types/scheduler": "0.16.8",
|
||||
"@types/semver": "7.5.8",
|
||||
"@types/shallow-equals": "1.0.3",
|
||||
"@types/styled-components": "5.1.32",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/zen-observable": "0.8.7",
|
||||
"babel-plugin-styled-components": "2.1.4",
|
||||
"copy-webpack-plugin": "11.0.0",
|
||||
"emoji-datasource": "6.1.1",
|
||||
"emoji-datasource-apple": "6.1.1",
|
||||
"emoji-datasource-google": "6.1.1",
|
||||
"enzyme": "3.11.0",
|
||||
"enzyme-adapter-react-17-updated": "1.0.2",
|
||||
"enzyme-to-json": "3.6.2",
|
||||
"eslint-plugin-no-only-tests": "3.1.0",
|
||||
"external-remotes-plugin": "1.0.0",
|
||||
|
|
@ -185,7 +185,7 @@
|
|||
"test:watch": "cross-env TZ=Etc/UTC LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 jest --watch",
|
||||
"test:updatesnapshot": "cross-env TZ=Etc/UTC LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 jest --updateSnapshot",
|
||||
"test:debug": "cross-env TZ=Etc/UTC LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 jest --forceExit --detectOpenHandles --verbose",
|
||||
"test-ci": "cross-env TZ=Etc/UTC LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 jest --ci --maxWorkers=100% --coverage",
|
||||
"test-ci": "cross-env TZ=Etc/UTC LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 jest --ci --maxWorkers=100% --workerIdleMemoryLimit=4096MB",
|
||||
"clean": "rm -rf dist node_modules .eslintcache .stylelintcache tsconfig.tsbuildinfo",
|
||||
"stats": "cross-env NODE_ENV=production webpack --profile --json > webpack_stats.json",
|
||||
"mmjstool": "mmjstool",
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ export function openAppsModal(form: AppForm, context: AppContext): AnyAction {
|
|||
dialogType: AppsForm,
|
||||
dialogProps: {
|
||||
form,
|
||||
context,
|
||||
appContext: context,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ describe('components/AboutBuildModal', () => {
|
|||
expect(screen.getByRole('link', {name: 'mobile'})).toHaveAttribute('href', 'https://github.com/mattermost/mattermost-mobile/blob/master/NOTICE.txt');
|
||||
});
|
||||
|
||||
test('should call onExited callback when the modal is hidden', () => {
|
||||
test('should call onExited callback when the modal is hidden', async () => {
|
||||
const onExited = jest.fn();
|
||||
const state = {
|
||||
entities: {
|
||||
|
|
@ -185,7 +185,7 @@ describe('components/AboutBuildModal', () => {
|
|||
state,
|
||||
);
|
||||
|
||||
userEvent.click(screen.getByText('Close'));
|
||||
await userEvent.click(screen.getByText('Close'));
|
||||
expect(onExited).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
|
@ -252,7 +252,7 @@ describe('components/AboutBuildModal', () => {
|
|||
|
||||
test('should handle API errors gracefully', async () => {
|
||||
// Temporarily suppress console.error for this test
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
console.error = jest.fn();
|
||||
|
||||
// Mock the API call to throw an error
|
||||
jest.spyOn(Client4, 'getLicenseLoadMetric').mockRejectedValue(new Error('API error'));
|
||||
|
|
|
|||
|
|
@ -129,13 +129,16 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
|
|||
};
|
||||
|
||||
handleOpenMarketplace = (): void => {
|
||||
const openMarketplaceData = {
|
||||
modalId: ModalIdentifiers.PLUGIN_MARKETPLACE,
|
||||
dialogType: MarketplaceModal,
|
||||
};
|
||||
this.props.actions.openModal(openMarketplaceData);
|
||||
|
||||
this.closeDropdown();
|
||||
|
||||
// Wait for the menu to close to avoid clashing between the menu's focus trap and the modal's
|
||||
requestAnimationFrame(() => {
|
||||
const openMarketplaceData = {
|
||||
modalId: ModalIdentifiers.PLUGIN_MARKETPLACE,
|
||||
dialogType: MarketplaceModal,
|
||||
};
|
||||
this.props.actions.openModal(openMarketplaceData);
|
||||
});
|
||||
};
|
||||
|
||||
onClickAppBinding = async (binding: AppBinding) => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {Permissions} from 'mattermost-redux/constants';
|
|||
import ActionsMenu from 'components/actions_menu';
|
||||
import ModalController from 'components/modal_controller';
|
||||
|
||||
import {act, renderWithContext, screen, userEvent, waitFor} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, screen, userEvent, waitFor} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
function ActionsMenuTestWrapper(props: Omit<React.ComponentProps<typeof ActionsMenu>, 'isMenuOpen' | 'handleDropdownOpened'>) {
|
||||
|
|
@ -72,7 +72,7 @@ describe('ActionsMenu', () => {
|
|||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
|
||||
// Open the menu
|
||||
screen.getByRole('button').click();
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
// The dialog should open up
|
||||
await waitFor(() => {
|
||||
|
|
@ -82,17 +82,17 @@ describe('ActionsMenu', () => {
|
|||
});
|
||||
|
||||
// The focus starts on the dialog itself, so pressing tab should move it to the button
|
||||
userEvent.tab();
|
||||
await userEvent.tab();
|
||||
|
||||
expect(screen.queryByRole('button', {name: 'Visit the Marketplace'})).toHaveFocus();
|
||||
|
||||
// The focus should be trapped, so hitting tab again shouldn't change the focus
|
||||
userEvent.tab();
|
||||
await userEvent.tab();
|
||||
|
||||
expect(screen.queryByRole('button', {name: 'Visit the Marketplace'})).toHaveFocus();
|
||||
|
||||
// Pressing enter should open the marketplace modal and close the menu
|
||||
userEvent.keyboard('{Enter}');
|
||||
await userEvent.keyboard('{Enter}');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('dialog', {name: 'actions'})).not.toBeInTheDocument();
|
||||
|
|
@ -100,16 +100,14 @@ describe('ActionsMenu', () => {
|
|||
});
|
||||
|
||||
// Pressing escape should close the marketplace modal
|
||||
act(() => {
|
||||
userEvent.keyboard('{Escape}');
|
||||
});
|
||||
await userEvent.type(screen.getByRole('dialog'), '{Escape}');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('dialog', {name: 'App Marketplace'})).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Reopen the menu
|
||||
screen.getByRole('button').click();
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('dialog')).toBeVisible();
|
||||
|
|
@ -117,7 +115,7 @@ describe('ActionsMenu', () => {
|
|||
expect(screen.queryByRole('dialog')).toHaveAccessibleName('actions');
|
||||
|
||||
// Pressing escape should close the dialog
|
||||
userEvent.keyboard('{Escape}');
|
||||
await userEvent.keyboard('{Escape}');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('dialog', {name: 'actions'})).not.toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ function TestResultsModal({
|
|||
onExited,
|
||||
actions,
|
||||
}: Props): JSX.Element {
|
||||
const dispatch = useDispatch<any>();
|
||||
const dispatch = useDispatch();
|
||||
const [term, setTerm] = useState<string>('');
|
||||
const [users, setUsers] = useState<UserProfile[]>([]);
|
||||
const [total, setTotal] = useState<number>(0);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const USERS_PER_PAGE = 10;
|
|||
// - improve search
|
||||
|
||||
export const SyncedUserList = ({userIds, noResultsMessageId, noResultsDefaultMessage, actions}: SyncedUserListProps): JSX.Element => {
|
||||
const dispatch = useDispatch<any>();
|
||||
const dispatch = useDispatch();
|
||||
const [users, setUsers] = useState<UserProfile[]>([]);
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {act} from 'react-dom/test-utils';
|
||||
|
||||
import type {AccessControlPolicy} from '@mattermost/types/access_control';
|
||||
|
||||
|
|
@ -11,6 +10,8 @@ import type {ActionResult} from 'mattermost-redux/types/actions';
|
|||
|
||||
import type {Column} from 'components/admin_console/data_grid/data_grid';
|
||||
|
||||
import {act} from 'tests/react_testing_utils';
|
||||
|
||||
import PolicyList from './policies';
|
||||
|
||||
const mockHistoryPushInternal = jest.fn();
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {act} from 'react-dom/test-utils';
|
||||
|
||||
import type {ChannelWithTeamData} from '@mattermost/types/channels';
|
||||
|
||||
import {useChannelAccessControlActions} from 'hooks/useChannelAccessControlActions';
|
||||
import {act} from 'tests/react_testing_utils';
|
||||
|
||||
import PolicyDetails from './policy_details';
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ type Props = {
|
|||
text: string | React.ReactNode;
|
||||
};
|
||||
|
||||
export const MenuItemBlockableLinkImpl: React.SFC<Props> = (props: Props): JSX.Element => {
|
||||
export const MenuItemBlockableLinkImpl = (props: Props): JSX.Element => {
|
||||
const {to, text} = props;
|
||||
return (
|
||||
<BlockableLink to={to}>{text}</BlockableLink>
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ function mapDispatchToProps(dispatch: Dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps, null, {pure: false});
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ exports[`components/admin_console/billing_subscription/CloudTrialBanner should m
|
|||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"getServerState": undefined,
|
||||
"identityFunctionCheck": "once",
|
||||
"stabilityCheck": "once",
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
|
|
@ -12,22 +15,14 @@ exports[`components/admin_console/billing_subscription/CloudTrialBanner should m
|
|||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"subscription": Subscription {
|
||||
"subscription": Object {
|
||||
"addNestedSub": [Function],
|
||||
"getListeners": [Function],
|
||||
"handleChangeWrapper": [Function],
|
||||
"listeners": Object {
|
||||
"notify": [Function],
|
||||
},
|
||||
"onStateChange": [Function],
|
||||
"parentSub": undefined,
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"unsubscribe": null,
|
||||
"isSubscribed": [Function],
|
||||
"notifyNestedSubs": [Function],
|
||||
"trySubscribe": [Function],
|
||||
"tryUnsubscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +37,9 @@ exports[`components/admin_console/billing_subscription/CloudTrialBanner should m
|
|||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"getServerState": undefined,
|
||||
"identityFunctionCheck": "once",
|
||||
"stabilityCheck": "once",
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
|
|
@ -50,22 +48,14 @@ exports[`components/admin_console/billing_subscription/CloudTrialBanner should m
|
|||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"subscription": Subscription {
|
||||
"subscription": Object {
|
||||
"addNestedSub": [Function],
|
||||
"getListeners": [Function],
|
||||
"handleChangeWrapper": [Function],
|
||||
"listeners": Object {
|
||||
"notify": [Function],
|
||||
},
|
||||
"onStateChange": [Function],
|
||||
"parentSub": undefined,
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"unsubscribe": null,
|
||||
"isSubscribed": [Function],
|
||||
"notifyNestedSubs": [Function],
|
||||
"trySubscribe": [Function],
|
||||
"tryUnsubscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -80,6 +70,9 @@ exports[`components/admin_console/billing_subscription/CloudTrialBanner should m
|
|||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"getServerState": undefined,
|
||||
"identityFunctionCheck": "once",
|
||||
"stabilityCheck": "once",
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
|
|
@ -88,22 +81,14 @@ exports[`components/admin_console/billing_subscription/CloudTrialBanner should m
|
|||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"subscription": Subscription {
|
||||
"subscription": Object {
|
||||
"addNestedSub": [Function],
|
||||
"getListeners": [Function],
|
||||
"handleChangeWrapper": [Function],
|
||||
"listeners": Object {
|
||||
"notify": [Function],
|
||||
},
|
||||
"onStateChange": [Function],
|
||||
"parentSub": undefined,
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"unsubscribe": null,
|
||||
"isSubscribed": [Function],
|
||||
"notifyNestedSubs": [Function],
|
||||
"trySubscribe": [Function],
|
||||
"tryUnsubscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export const creditCardExpiredBanner = (setShowCreditCardBanner: (value: boolean
|
|||
id='admin.billing.subscription.creditCardHasExpired.description'
|
||||
defaultMessage='Please <link>update your payment information</link> to avoid any disruption.'
|
||||
values={{
|
||||
link: (text: string) => <BlockableLink to='/admin_console/billing/payment_info'>{text}</BlockableLink>,
|
||||
link: (text) => <BlockableLink to='/admin_console/billing/payment_info'>{text}</BlockableLink>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ export const paymentFailedBanner = () => {
|
|||
id='billing.subscription.info.mostRecentPaymentFailed.description.mostRecentPaymentFailed'
|
||||
defaultMessage='It looks your most recent payment failed because the credit card on your account has expired. Please <link>update your payment information</link> to avoid any disruption.'
|
||||
values={{
|
||||
link: (text: string) => <BlockableLink to='/admin_console/billing/payment_info'>{text}</BlockableLink>,
|
||||
link: (text) => <BlockableLink to='/admin_console/billing/payment_info'>{text}</BlockableLink>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ exports[`components/admin_console/billing/plan_details/feature_list should match
|
|||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"getServerState": undefined,
|
||||
"identityFunctionCheck": "once",
|
||||
"stabilityCheck": "once",
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
|
|
@ -12,22 +15,14 @@ exports[`components/admin_console/billing/plan_details/feature_list should match
|
|||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"subscription": Subscription {
|
||||
"subscription": Object {
|
||||
"addNestedSub": [Function],
|
||||
"getListeners": [Function],
|
||||
"handleChangeWrapper": [Function],
|
||||
"listeners": Object {
|
||||
"notify": [Function],
|
||||
},
|
||||
"onStateChange": [Function],
|
||||
"parentSub": undefined,
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"unsubscribe": null,
|
||||
"isSubscribed": [Function],
|
||||
"notifyNestedSubs": [Function],
|
||||
"trySubscribe": [Function],
|
||||
"tryUnsubscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +37,9 @@ exports[`components/admin_console/billing/plan_details/feature_list should match
|
|||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"getServerState": undefined,
|
||||
"identityFunctionCheck": "once",
|
||||
"stabilityCheck": "once",
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
|
|
@ -50,22 +48,14 @@ exports[`components/admin_console/billing/plan_details/feature_list should match
|
|||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"subscription": Subscription {
|
||||
"subscription": Object {
|
||||
"addNestedSub": [Function],
|
||||
"getListeners": [Function],
|
||||
"handleChangeWrapper": [Function],
|
||||
"listeners": Object {
|
||||
"notify": [Function],
|
||||
},
|
||||
"onStateChange": [Function],
|
||||
"parentSub": undefined,
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"unsubscribe": null,
|
||||
"isSubscribed": [Function],
|
||||
"notifyNestedSubs": [Function],
|
||||
"trySubscribe": [Function],
|
||||
"tryUnsubscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -80,6 +70,9 @@ exports[`components/admin_console/billing/plan_details/feature_list should match
|
|||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"getServerState": undefined,
|
||||
"identityFunctionCheck": "once",
|
||||
"stabilityCheck": "once",
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
|
|
@ -88,22 +81,14 @@ exports[`components/admin_console/billing/plan_details/feature_list should match
|
|||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"subscription": Subscription {
|
||||
"subscription": Object {
|
||||
"addNestedSub": [Function],
|
||||
"getListeners": [Function],
|
||||
"handleChangeWrapper": [Function],
|
||||
"listeners": Object {
|
||||
"notify": [Function],
|
||||
},
|
||||
"onStateChange": [Function],
|
||||
"parentSub": undefined,
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"unsubscribe": null,
|
||||
"isSubscribed": [Function],
|
||||
"notifyNestedSubs": [Function],
|
||||
"trySubscribe": [Function],
|
||||
"tryUnsubscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -118,6 +103,9 @@ exports[`components/admin_console/billing/plan_details/feature_list should match
|
|||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"getServerState": undefined,
|
||||
"identityFunctionCheck": "once",
|
||||
"stabilityCheck": "once",
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
|
|
@ -126,22 +114,14 @@ exports[`components/admin_console/billing/plan_details/feature_list should match
|
|||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"subscription": Subscription {
|
||||
"subscription": Object {
|
||||
"addNestedSub": [Function],
|
||||
"getListeners": [Function],
|
||||
"handleChangeWrapper": [Function],
|
||||
"listeners": Object {
|
||||
"notify": [Function],
|
||||
},
|
||||
"onStateChange": [Function],
|
||||
"parentSub": undefined,
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"unsubscribe": null,
|
||||
"isSubscribed": [Function],
|
||||
"notifyNestedSubs": [Function],
|
||||
"trySubscribe": [Function],
|
||||
"tryUnsubscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import type {UserProfile} from '@mattermost/types/users';
|
|||
|
||||
import {debounce} from 'mattermost-redux/actions/helpers';
|
||||
import {getMissingProfilesByIds, searchProfiles} from 'mattermost-redux/actions/users';
|
||||
import {getUsersByIDs} from 'mattermost-redux/selectors/entities/users';
|
||||
import {makeGetUsersByIds} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
|
|
@ -75,7 +75,8 @@ export function UserSelector({id, isMulti, className, multiSelectOnChange, multi
|
|||
}
|
||||
}, [dispatch, initialValue, isMulti, multiSelectInitialValue, singleSelectInitialValue]);
|
||||
|
||||
const initialUsers = useSelector((state: GlobalState) => getUsersByIDs(state, initialValue || []));
|
||||
const getUsersByIds = useMemo(makeGetUsersByIds, []);
|
||||
const initialUsers = useSelector((state: GlobalState) => getUsersByIds(state, initialValue || []));
|
||||
const selectInitialValue = initialUsers.
|
||||
filter((userProfile) => Boolean(userProfile)).
|
||||
map((userProfile: UserProfile) => ({
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ const CustomEnableDisableGuestAccountsSetting = ({
|
|||
id='admin.guest_access.helpText'
|
||||
defaultMessage='When true, external guest can be invited to channels within teams. Please see <a>Permissions Schemes</a> for which roles can invite guests.'
|
||||
values={{
|
||||
a: (chunks: string) => <Link to='/admin_console/user_management/permissions/system_scheme'>{chunks}</Link>,
|
||||
a: (chunks) => <Link to='/admin_console/user_management/permissions/system_scheme'>{chunks}</Link>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@
|
|||
|
||||
import {screen, fireEvent} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import {act} from 'react-dom/test-utils';
|
||||
|
||||
import type {UserPropertyField, UserPropertyFieldGroupID, UserPropertyFieldType} from '@mattermost/types/properties';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
import {act, renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
import CustomProfileAttributes from './custom_profile_attributes';
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const AttributeHelpText = memo(({attributeKey, attributeName, attributeType}: At
|
|||
defaultMessage='(Optional) The attribute in the AD/LDAP server used to populate the {name} of users in Mattermost. When set, users cannot edit their {name}, since it is synchronized with the LDAP server. When left blank, users can set their {name} in <strong>Account Menu > Account Settings > Profile</strong>.'
|
||||
values={{
|
||||
name: attributeName,
|
||||
strong: (msg: string) => <strong>{msg}</strong>,
|
||||
strong: (msg) => <strong>{msg}</strong>,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -51,7 +51,7 @@ const AttributeHelpText = memo(({attributeKey, attributeName, attributeType}: At
|
|||
defaultMessage='(Warning) This attribute will be converted to a TEXT attribute, if the field is set to synchronize.'
|
||||
values={{
|
||||
name: attributeName,
|
||||
strong: (msg: string) => <strong>{msg}</strong>,
|
||||
strong: (msg) => <strong>{msg}</strong>,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -134,7 +134,7 @@ const CustomProfileAttributes: React.FC<Props> = (props: Props): JSX.Element | n
|
|||
id='admin.customProfileAttributes.subtitle'
|
||||
defaultMessage='You can add or remove custom profile attributes by going to the <link>system properties page</link>.'
|
||||
values={{
|
||||
link: (msg: string) => (
|
||||
link: (msg) => (
|
||||
<Link
|
||||
to='/admin_console/system_attributes/user_attributes'
|
||||
>
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ export default class CustomTermsOfServiceSettings extends OLDAdminSettings<Props
|
|||
<FormattedMessage
|
||||
{...messages.enableTermsOfServiceHelp}
|
||||
values={{
|
||||
a: (chunks: string) => <Link to='/admin_console/site_config/customization'>{chunks}</Link>,
|
||||
a: (chunks) => <Link to='/admin_console/site_config/customization'>{chunks}</Link>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ export default class GroupUsers extends React.PureComponent<Props, State> {
|
|||
'AD/LDAP Connector is configured to sync and manage this group and its users. <a>Click here to view</a>'
|
||||
}
|
||||
values={{
|
||||
a: (chunks: string) => (
|
||||
a: (chunks) => (
|
||||
<Link to='/admin_console/authentication/ldap'>
|
||||
{chunks}
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ const IPFiltering = () => {
|
|||
id={'admin.ip_filtering.no_filters_added'}
|
||||
defaultMessage={'Are you sure you want to apply these IP filter changes? There are currently no filters added, so <strong>all IP addresses will have access to the workspace.</strong>'}
|
||||
values={{
|
||||
strong: (content: string) => <strong>{content}</strong>,
|
||||
strong: (content) => <strong>{content}</strong>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
@ -269,7 +269,7 @@ const IPFiltering = () => {
|
|||
id={'admin.ip_filtering.turn_off_ip_filtering'}
|
||||
defaultMessage={'Are you sure you want to turn off IP Filtering? <strong>All IP addresses will have access to the workspace.</strong>'}
|
||||
values={{
|
||||
strong: (content: string) => <strong>{content}</strong>,
|
||||
strong: (content) => <strong>{content}</strong>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
@ -282,7 +282,7 @@ const IPFiltering = () => {
|
|||
id={'admin.ip_filtering.apply_ip_filter_changes_are_you_sure'}
|
||||
defaultMessage={'Are you sure you want to apply these IP Filter changes? <strong>Users with IP addresses outside of the IP ranges provided will no longer have access to the workspace.</strong>'}
|
||||
values={{
|
||||
strong: (content: string) => <strong>{content}</strong>,
|
||||
strong: (content) => <strong>{content}</strong>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const JobCancelButton = (props: Props): JSX.Element|null => {
|
|||
const intl = useIntl();
|
||||
let cancelButton = null;
|
||||
|
||||
const handleClick = useCallback((e) => {
|
||||
const handleClick = useCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
onClick(job.id);
|
||||
}, [onClick, job.id]);
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@ const LDAPCustomSetting = (props: Props) => {
|
|||
if (props.setting.showTitle) {
|
||||
return (
|
||||
<Setting
|
||||
label={props.setting.label}
|
||||
label={label}
|
||||
inputId={props.setting.key}
|
||||
helpText={props.setting.help_text}
|
||||
helpText={helpText}
|
||||
>
|
||||
{componentInstance}
|
||||
</Setting>
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ type State = {
|
|||
[x: string]: unknown;
|
||||
saveNeeded: false | 'both' | 'permissions' | 'config';
|
||||
saving: boolean;
|
||||
serverError: string | { message: string; id?: string } | null;
|
||||
serverError: string | null;
|
||||
confirmNeededId: string;
|
||||
showConfirmId: string;
|
||||
clientWarning: string | boolean;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ exports[`components/admin_console/license_settings/modals/confirm_license_remova
|
|||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"getServerState": undefined,
|
||||
"identityFunctionCheck": "once",
|
||||
"stabilityCheck": "once",
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
|
|
@ -12,22 +15,14 @@ exports[`components/admin_console/license_settings/modals/confirm_license_remova
|
|||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"subscription": Subscription {
|
||||
"subscription": Object {
|
||||
"addNestedSub": [Function],
|
||||
"getListeners": [Function],
|
||||
"handleChangeWrapper": [Function],
|
||||
"listeners": Object {
|
||||
"notify": [Function],
|
||||
},
|
||||
"onStateChange": [Function],
|
||||
"parentSub": undefined,
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"unsubscribe": null,
|
||||
"isSubscribed": [Function],
|
||||
"notifyNestedSubs": [Function],
|
||||
"trySubscribe": [Function],
|
||||
"tryUnsubscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ exports[`components/admin_console/license_settings/modals/upload_license_modal s
|
|||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"getServerState": undefined,
|
||||
"identityFunctionCheck": "once",
|
||||
"stabilityCheck": "once",
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
|
|
@ -12,22 +15,14 @@ exports[`components/admin_console/license_settings/modals/upload_license_modal s
|
|||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"subscription": Subscription {
|
||||
"subscription": Object {
|
||||
"addNestedSub": [Function],
|
||||
"getListeners": [Function],
|
||||
"handleChangeWrapper": [Function],
|
||||
"listeners": Object {
|
||||
"notify": [Function],
|
||||
},
|
||||
"onStateChange": [Function],
|
||||
"parentSub": undefined,
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"unsubscribe": null,
|
||||
"isSubscribed": [Function],
|
||||
"notifyNestedSubs": [Function],
|
||||
"trySubscribe": [Function],
|
||||
"tryUnsubscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +42,9 @@ exports[`components/admin_console/license_settings/modals/upload_license_modal s
|
|||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"getServerState": undefined,
|
||||
"identityFunctionCheck": "once",
|
||||
"stabilityCheck": "once",
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
|
|
@ -55,22 +53,14 @@ exports[`components/admin_console/license_settings/modals/upload_license_modal s
|
|||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"subscription": Subscription {
|
||||
"subscription": Object {
|
||||
"addNestedSub": [Function],
|
||||
"getListeners": [Function],
|
||||
"handleChangeWrapper": [Function],
|
||||
"listeners": Object {
|
||||
"notify": [Function],
|
||||
},
|
||||
"onStateChange": [Function],
|
||||
"parentSub": undefined,
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"unsubscribe": null,
|
||||
"isSubscribed": [Function],
|
||||
"notifyNestedSubs": [Function],
|
||||
"trySubscribe": [Function],
|
||||
"tryUnsubscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {act} from 'react-dom/test-utils';
|
||||
import * as reactRedux from 'react-redux';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
|
@ -11,6 +10,7 @@ import {General} from 'mattermost-redux/constants';
|
|||
import * as i18Selectors from 'selectors/i18n';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {act} from 'tests/react_testing_utils';
|
||||
import mockStore from 'tests/test_store';
|
||||
|
||||
import UploadLicenseModal from './upload_license_modal';
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
|
||||
import type {ReactWrapper} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {act} from 'react-dom/test-utils';
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {act} from 'tests/react_testing_utils';
|
||||
import mockStore from 'tests/test_store';
|
||||
|
||||
import RenewalLicenseCard from './renew_license_card';
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const RenewLicenseCard: React.FC<RenewLicenseCardProps> = ({license, totalUsers,
|
|||
id='admin.license.renewalCard.licenseExpiring'
|
||||
defaultMessage='License expires in {days} days on {date, date, long}.'
|
||||
values={{
|
||||
date: endOfLicense,
|
||||
date: endOfLicense.toDate(),
|
||||
days: daysToEndLicense,
|
||||
}}
|
||||
/>
|
||||
|
|
@ -51,7 +51,7 @@ const RenewLicenseCard: React.FC<RenewLicenseCardProps> = ({license, totalUsers,
|
|||
id='admin.license.renewalCard.licenseExpired'
|
||||
defaultMessage='License expired on {date, date, long}.'
|
||||
values={{
|
||||
date: endOfLicense,
|
||||
date: endOfLicense.toDate(),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ const TeamEditionRightPanel: React.FC<TeamEditionRightPanelProps> = ({
|
|||
id='admin.licenseSettings.teamEdition.teamEditionRightPanel.acceptTermsInitial'
|
||||
defaultMessage='By clicking <b>Upgrade</b>, I agree to the terms of the Mattermost '
|
||||
values={{
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
<a
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export const EmbargoedEntityTrialError = () => {
|
|||
id='admin.license.trial-request.embargoed'
|
||||
defaultMessage='We were unable to process the request due to limitations for embargoed countries. <link>Learn more in our documentation</link>, or reach out to legal@mattermost.com for questions around export limitations.'
|
||||
values={{
|
||||
link: (text: string) => (
|
||||
link: (text) => (
|
||||
<ExternalLink
|
||||
location='trial_banner'
|
||||
href={LicenseLinks.EMBARGOED_COUNTRIES}
|
||||
|
|
@ -114,7 +114,7 @@ const TrialBanner = ({
|
|||
defaultMessage: 'We were unable to process the request due to limitations for embargoed countries. <link>Learn more in our documentation</link>, or reach out to legal@mattermost.com for questions around export limitations.',
|
||||
},
|
||||
{
|
||||
link: (text: string) => (
|
||||
link: (text) => (
|
||||
<ExternalLink
|
||||
location='trial_banner'
|
||||
href={LicenseLinks.EMBARGOED_COUNTRIES}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const TrialLicenseCard: React.FC<Props> = ({license}: Props) => {
|
|||
id='admin.license.trialLicenseCard.expiringToday'
|
||||
defaultMessage='Your free trial expires <b>Today at {time}</b>. Contact sales to purchase a license and continue using advanced features after the trial ends.'
|
||||
values={{
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
time: moment(endDate).endOf('day').format('h:mm a ') + moment().tz(getBrowserTimezone()).format('z'),
|
||||
}}
|
||||
/>
|
||||
|
|
@ -45,7 +45,7 @@ const TrialLicenseCard: React.FC<Props> = ({license}: Props) => {
|
|||
id='admin.license.trialLicenseCard.expiringAfterFewDays'
|
||||
defaultMessage='Your free trial will expire in <b>{daysCount} {daysCount, plural, one {day} other {days}}</b>. Contact sales to purchase a license and continue using advanced features.'
|
||||
values={{
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
daysCount: daysToEndLicense,
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
import type {ReactWrapper} from 'enzyme';
|
||||
import {mount, shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {act} from 'react-dom/test-utils';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
||||
import ManageTeamsModal from 'components/admin_console/manage_teams_modal/manage_teams_modal';
|
||||
|
||||
import {act} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import ManageTeamsDropdown from './manage_teams_dropdown';
|
||||
|
|
|
|||
|
|
@ -330,7 +330,7 @@ export class MessageExportSettings extends OLDAdminSettings<BaseProps & WrappedC
|
|||
<FormattedMessage
|
||||
{...messages.exportFormat_description_details}
|
||||
values={{
|
||||
a: (chunks: string) => (
|
||||
a: (chunks) => (
|
||||
<Link to='/admin_console/environment/file_storage'>
|
||||
{chunks}
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -87,6 +87,9 @@ exports[`components/admin_console/permission_schemes_settings/permission_descrip
|
|||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"getServerState": undefined,
|
||||
"identityFunctionCheck": "once",
|
||||
"stabilityCheck": "once",
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
|
|
@ -95,22 +98,14 @@ exports[`components/admin_console/permission_schemes_settings/permission_descrip
|
|||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"subscription": Subscription {
|
||||
"subscription": Object {
|
||||
"addNestedSub": [Function],
|
||||
"getListeners": [Function],
|
||||
"handleChangeWrapper": [Function],
|
||||
"listeners": Object {
|
||||
"notify": [Function],
|
||||
},
|
||||
"onStateChange": [Function],
|
||||
"parentSub": undefined,
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"unsubscribe": null,
|
||||
"isSubscribed": [Function],
|
||||
"notifyNestedSubs": [Function],
|
||||
"trySubscribe": [Function],
|
||||
"tryUnsubscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export default function EditPostTimeLimitModal(props: Props) {
|
|||
id='editPost.timeLimitModal.description'
|
||||
defaultMessage='Setting a time limit <b>applies to all users</b> who have the "Edit Post" permissions in any permission scheme.'
|
||||
values={{
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
<div className='pt-3'>
|
||||
|
|
|
|||
|
|
@ -9,14 +9,13 @@ import type {Role} from '@mattermost/types/roles';
|
|||
|
||||
import WithTooltip from 'components/with_tooltip';
|
||||
|
||||
import type {AdditionalValues} from './permissions_tree/types';
|
||||
import {rolesRolesStrings} from './strings/roles';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
inherited?: Partial<Role>;
|
||||
selectRow: (id: string) => void;
|
||||
additionalValues?: AdditionalValues | AdditionalValues['edit_post'];
|
||||
additionalValues?: Record<string, unknown>;
|
||||
description: string | JSX.Element;
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +49,7 @@ const PermissionDescription = ({
|
|||
defaultMessage='Inherited from <link>{name}</link>.'
|
||||
values={{
|
||||
name: formattedName,
|
||||
link: (text: string) => (
|
||||
link: (text) => (
|
||||
<a>{text}</a>
|
||||
),
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ export default class PermissionGroup extends React.PureComponent<Props, State> {
|
|||
return true;
|
||||
};
|
||||
|
||||
renderPermission = (permission: string, additionalValues: AdditionalValues) => {
|
||||
renderPermission = (permission: string, additionalValues: Record<string, any>) => {
|
||||
if (!this.isInScope(permission)) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import type {Role} from '@mattermost/types/roles';
|
|||
|
||||
import PermissionCheckbox from './permission_checkbox';
|
||||
import PermissionDescription from './permission_description';
|
||||
import type {AdditionalValues} from './permissions_tree/types';
|
||||
import {permissionRolesStrings} from './strings/permissions';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -21,7 +20,7 @@ type Props = {
|
|||
selectRow: (id: string) => void;
|
||||
value: string;
|
||||
onChange: (id: string) => void;
|
||||
additionalValues: AdditionalValues;
|
||||
additionalValues: Record<string, any>;
|
||||
}
|
||||
|
||||
const PermissionRow = ({
|
||||
|
|
|
|||
|
|
@ -16,8 +16,4 @@ export type Group = {
|
|||
isVisible?: (license?: ClientLicense) => boolean;
|
||||
}
|
||||
|
||||
export type AdditionalValues = {
|
||||
[edit_post: string]: {
|
||||
editTimeLimitButton: JSX.Element;
|
||||
};
|
||||
}
|
||||
export type AdditionalValues = Record<string, Record<string, any>>;
|
||||
|
|
|
|||
|
|
@ -968,9 +968,9 @@ export class SchemaAdminSettings extends React.PureComponent<Props, State> {
|
|||
if (setting.showTitle) {
|
||||
return (
|
||||
<Setting
|
||||
label={setting.label}
|
||||
label={label}
|
||||
inputId={setting.key}
|
||||
helpText={setting.help_text}
|
||||
helpText={helpText}
|
||||
>
|
||||
{componentInstance}
|
||||
</Setting>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ function SecureConnectionDeleteModal({
|
|||
id={'admin.secure_connections.confirm.delete.text'}
|
||||
defaultMessage={'Are you sure you want to delete the secure connection <strong>{displayName}</strong>?'}
|
||||
values={{
|
||||
strong: (chunk: string) => <strong>{chunk}</strong>,
|
||||
strong: (chunk) => <strong>{chunk}</strong>,
|
||||
displayName,
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import {Client4} from 'mattermost-redux/client';
|
|||
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getActiveTeamsList, getTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import type {ActionFuncAsync} from 'types/store';
|
||||
|
||||
export const useRemoteClusters = () => {
|
||||
const [remoteClusters, setRemoteClusters] = useState<RemoteCluster[]>();
|
||||
const [loadingState, setLoadingState] = useState<boolean | ClientError>(true);
|
||||
|
|
@ -153,7 +151,7 @@ export const useSharedChannelRemoteRows = (remoteId: string, opts: {filter: 'hom
|
|||
}
|
||||
|
||||
setLoadingState(true);
|
||||
dispatch<ActionFuncAsync<IDMappedObjects<SharedChannelRemoteRow>>>(async (dispatch, getState) => {
|
||||
dispatch(async (dispatch, getState) => {
|
||||
const collected: IDMappedObjects<SharedChannelRemoteRow> = {};
|
||||
const missing: SharedChannelRemote[] = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ describe('components/admin_console/server_logs/Logs', () => {
|
|||
test.each(['caller', 'msg', 'worker', 'job_id', 'whatever'])('should search input be performed on %s attribute',
|
||||
async (searchString: string) => {
|
||||
const searchInput = screen.getByTestId('searchInput');
|
||||
userEvent.type(searchInput, searchString);
|
||||
await userEvent.type(searchInput, searchString);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('msg 1')).toBeInTheDocument();
|
||||
|
|
@ -77,7 +77,7 @@ describe('components/admin_console/server_logs/Logs', () => {
|
|||
test.each(['level', 'timestamp'])('should search input not be performed on %s attribute',
|
||||
async (searchString: string) => {
|
||||
const searchInput = screen.getByTestId('searchInput');
|
||||
userEvent.type(searchInput, searchString);
|
||||
await userEvent.type(searchInput, searchString);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('msg 1')).not.toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {act} from '@testing-library/react-hooks';
|
||||
import {act} from '@testing-library/react';
|
||||
|
||||
import type {DeepPartial} from '@mattermost/types/utilities';
|
||||
|
||||
|
|
@ -106,13 +106,12 @@ describe('useOperation', () => {
|
|||
|
||||
await act(async () => {
|
||||
await actionPromise;
|
||||
|
||||
const [, status3] = result.current;
|
||||
|
||||
expect(status3.loading).toBe(false);
|
||||
expect(await actionPromise).toBe('test response value');
|
||||
expect(status3.error).toBe(undefined);
|
||||
});
|
||||
|
||||
const [, status3] = result.current;
|
||||
expect(status3.loading).toBe(false);
|
||||
expect(status3.error).toBe(undefined);
|
||||
expect(await actionPromise!).toBe('test response value');
|
||||
});
|
||||
|
||||
it('should run operation on command with error and loading phases: false -> true -> false', async () => {
|
||||
|
|
|
|||
|
|
@ -75,10 +75,10 @@ describe('SystemProperties', () => {
|
|||
|
||||
expect(screen.getByRole('heading', {name: 'Configure user attributes'})).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByDisplayValue('test attribute 0')).toBeInTheDocument();
|
||||
expect(screen.queryByDisplayValue('test attribute 1')).toBeInTheDocument();
|
||||
expect(screen.queryByDisplayValue('test attribute 2')).toBeInTheDocument();
|
||||
expect(screen.queryByDisplayValue('test attribute 3')).toBeInTheDocument();
|
||||
expect(await screen.findByDisplayValue('test attribute 0')).toBeInTheDocument();
|
||||
expect(await screen.findByDisplayValue('test attribute 1')).toBeInTheDocument();
|
||||
expect(await screen.findByDisplayValue('test attribute 2')).toBeInTheDocument();
|
||||
expect(await screen.findByDisplayValue('test attribute 3')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {act} from '@testing-library/react-hooks';
|
||||
import {act} from '@testing-library/react';
|
||||
|
||||
import type {UserPropertyField, UserPropertyFieldPatch} from '@mattermost/types/properties';
|
||||
import type {DeepPartial} from '@mattermost/types/utilities';
|
||||
|
|
@ -9,7 +9,7 @@ import type {DeepPartial} from '@mattermost/types/utilities';
|
|||
import {Client4} from 'mattermost-redux/client';
|
||||
import {generateId} from 'mattermost-redux/utils/helpers';
|
||||
|
||||
import {renderHookWithContext} from 'tests/react_testing_utils';
|
||||
import {renderHookWithContext, waitFor} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
|
@ -71,7 +71,7 @@ describe('useUserPropertyFields', () => {
|
|||
getFields.mockResolvedValue([field0, field1, field2, field3]);
|
||||
|
||||
it('should return a collection', async () => {
|
||||
const {result, rerender, waitFor} = renderHookWithContext(() => {
|
||||
const {result, rerender} = renderHookWithContext(() => {
|
||||
return useUserPropertyFields();
|
||||
}, getBaseState());
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ describe('useUserPropertyFields', () => {
|
|||
});
|
||||
|
||||
it('should successfully handle edits', async () => {
|
||||
const {result, rerender, waitFor} = renderHookWithContext(() => {
|
||||
const {result, rerender} = renderHookWithContext(() => {
|
||||
return useUserPropertyFields();
|
||||
}, getBaseState());
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ describe('useUserPropertyFields', () => {
|
|||
it('should successfully handle reordering', async () => {
|
||||
patchField.mockImplementation((id: string, patch: UserPropertyFieldPatch) => Promise.resolve({...baseField, ...patch, id, update_at: Date.now()} as UserPropertyField));
|
||||
|
||||
const {result, rerender, waitFor} = renderHookWithContext(() => {
|
||||
const {result, rerender} = renderHookWithContext(() => {
|
||||
return useUserPropertyFields();
|
||||
}, getBaseState());
|
||||
|
||||
|
|
@ -202,7 +202,7 @@ describe('useUserPropertyFields', () => {
|
|||
});
|
||||
|
||||
it('should successfully handle deletes', async () => {
|
||||
const {result, rerender, waitFor} = renderHookWithContext(() => {
|
||||
const {result, rerender} = renderHookWithContext(() => {
|
||||
return useUserPropertyFields();
|
||||
}, getBaseState());
|
||||
|
||||
|
|
@ -251,7 +251,7 @@ describe('useUserPropertyFields', () => {
|
|||
it('should successfully handle creates', async () => {
|
||||
createField.mockImplementation((patch) => Promise.resolve({...baseField, ...patch, id: generateId()} as UserPropertyField));
|
||||
|
||||
const {result, rerender, waitFor} = renderHookWithContext(() => {
|
||||
const {result, rerender} = renderHookWithContext(() => {
|
||||
return useUserPropertyFields();
|
||||
}, getBaseState());
|
||||
|
||||
|
|
@ -300,7 +300,7 @@ describe('useUserPropertyFields', () => {
|
|||
});
|
||||
|
||||
it('should validate name uniqueness', async () => {
|
||||
const {result, rerender, waitFor} = renderHookWithContext(() => {
|
||||
const {result, rerender} = renderHookWithContext(() => {
|
||||
return useUserPropertyFields();
|
||||
}, getBaseState());
|
||||
|
||||
|
|
@ -330,7 +330,7 @@ describe('useUserPropertyFields', () => {
|
|||
});
|
||||
|
||||
it('should validate names already taken', async () => {
|
||||
const {result, rerender, waitFor} = renderHookWithContext(() => {
|
||||
const {result, rerender} = renderHookWithContext(() => {
|
||||
return useUserPropertyFields();
|
||||
}, getBaseState());
|
||||
|
||||
|
|
@ -370,7 +370,7 @@ describe('useUserPropertyFields', () => {
|
|||
});
|
||||
|
||||
it('should validate name required', async () => {
|
||||
const {result, rerender, waitFor} = renderHookWithContext(() => {
|
||||
const {result, rerender} = renderHookWithContext(() => {
|
||||
return useUserPropertyFields();
|
||||
}, getBaseState());
|
||||
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ export default class SystemRolePermissions extends React.PureComponent<Props, St
|
|||
id='admin.permissions.roles.system_custom_group_admin.introduction'
|
||||
defaultMessage='The built-in Custom Group Manager role can be used to delegate the administration of <a>Custom Groups</a> to users other than the System Admin.'
|
||||
values={{
|
||||
a: (chunks: string) => (
|
||||
a: (chunks) => (
|
||||
<ExternalLink
|
||||
href='https://docs.mattermost.com/welcome/manage-custom-groups.html'
|
||||
location='adminConsoleSystemRoles'
|
||||
|
|
@ -234,7 +234,7 @@ export default class SystemRolePermissions extends React.PureComponent<Props, St
|
|||
id='admin.permissions.roles.system_custom_group_admin.permissions_info'
|
||||
defaultMessage='This role has permission to create, edit, and delete custom user groups by selecting <b>User groups</b> from the Products menu.'
|
||||
values={{
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -49,9 +49,7 @@ exports[`SystemUserDetail should match default snapshot 1`] = `
|
|||
class="AdminUserCard__user-info"
|
||||
>
|
||||
<span>
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<span
|
||||
class="AdminUserCard__user-nickname"
|
||||
|
|
@ -277,9 +275,7 @@ exports[`SystemUserDetail should match snapshot if MFA is enabled 1`] = `
|
|||
class="AdminUserCard__user-info"
|
||||
>
|
||||
<span>
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<span
|
||||
class="AdminUserCard__user-nickname"
|
||||
|
|
@ -505,9 +501,7 @@ exports[`SystemUserDetail should not show manage user settings button when user
|
|||
class="AdminUserCard__user-info"
|
||||
>
|
||||
<span>
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<span
|
||||
class="AdminUserCard__user-nickname"
|
||||
|
|
@ -733,9 +727,7 @@ exports[`SystemUserDetail should show manage user settings button as activated 1
|
|||
class="AdminUserCard__user-info"
|
||||
>
|
||||
<span>
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<span
|
||||
class="AdminUserCard__user-nickname"
|
||||
|
|
@ -967,9 +959,7 @@ exports[`SystemUserDetail should show manage user settings button as disabled wh
|
|||
class="AdminUserCard__user-info"
|
||||
>
|
||||
<span>
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<span
|
||||
class="AdminUserCard__user-nickname"
|
||||
|
|
@ -1195,9 +1185,7 @@ exports[`SystemUserDetail should show the activate user button as disabled when
|
|||
class="AdminUserCard__user-info"
|
||||
>
|
||||
<span>
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
<span
|
||||
class="AdminUserCard__user-nickname"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import SystemUserDetail, {getUserAuthenticationTextField} from 'components/admin
|
|||
import type {Params, Props} from 'components/admin_console/system_user_detail/system_user_detail';
|
||||
|
||||
import type {MockIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {renderWithContext, waitFor, within} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, screen, waitFor, waitForElementToBeRemoved} from 'tests/react_testing_utils';
|
||||
import Constants from 'utils/constants';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
|
|
@ -54,21 +54,16 @@ describe('SystemUserDetail', () => {
|
|||
} as RouteComponentProps<Params>),
|
||||
};
|
||||
|
||||
const waitForLoadingToFinish = async (container: HTMLElement) => {
|
||||
const noUserBody = container.querySelector('.noUserBody');
|
||||
const spinner = within(noUserBody as HTMLElement).getByTestId('loadingSpinner');
|
||||
expect(spinner).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('[data-testid="loadingSpinner"]')).not.toBeInTheDocument();
|
||||
});
|
||||
const waitForLoadingToFinish = async () => {
|
||||
await waitForElementToBeRemoved(screen.queryAllByTitle('Loading Icon'));
|
||||
await waitFor(() => expect(screen.queryByText('No teams found')).toBeInTheDocument());
|
||||
};
|
||||
|
||||
test('should match default snapshot', async () => {
|
||||
const props = defaultProps;
|
||||
const {container} = renderWithContext(<SystemUserDetail {...props}/>);
|
||||
|
||||
await waitForLoadingToFinish(container);
|
||||
await waitForLoadingToFinish();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
|
@ -80,7 +75,7 @@ describe('SystemUserDetail', () => {
|
|||
};
|
||||
const {container} = renderWithContext(<SystemUserDetail {...props}/>);
|
||||
|
||||
await waitForLoadingToFinish(container);
|
||||
await waitForLoadingToFinish();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
|
@ -92,7 +87,7 @@ describe('SystemUserDetail', () => {
|
|||
};
|
||||
const {container} = renderWithContext(<SystemUserDetail {...props}/>);
|
||||
|
||||
await waitForLoadingToFinish(container);
|
||||
await waitForLoadingToFinish();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
|
@ -104,7 +99,7 @@ describe('SystemUserDetail', () => {
|
|||
};
|
||||
const {container} = renderWithContext(<SystemUserDetail {...props}/>);
|
||||
|
||||
await waitForLoadingToFinish(container);
|
||||
await waitForLoadingToFinish();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
|
@ -118,7 +113,7 @@ describe('SystemUserDetail', () => {
|
|||
|
||||
const {container} = renderWithContext(<SystemUserDetail {...props}/>);
|
||||
|
||||
await waitForLoadingToFinish(container);
|
||||
await waitForLoadingToFinish();
|
||||
|
||||
const activateButton = container.querySelector('button[disabled]');
|
||||
expect(activateButton).toHaveTextContent('Deactivate (Managed By LDAP)');
|
||||
|
|
@ -133,7 +128,7 @@ describe('SystemUserDetail', () => {
|
|||
};
|
||||
const {container} = renderWithContext(<SystemUserDetail {...props}/>);
|
||||
|
||||
await waitForLoadingToFinish(container);
|
||||
await waitForLoadingToFinish();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ interface Props {
|
|||
policyEnforcedToggleAvailable: boolean;
|
||||
}
|
||||
|
||||
const SyncGroupsToggle: React.SFC<Props> = (props: Props): JSX.Element => {
|
||||
const SyncGroupsToggle = (props: Props): JSX.Element => {
|
||||
const {isPublic, isSynced, isDefault, onToggle, isDisabled, policyEnforced} = props;
|
||||
return (
|
||||
<LineSwitch
|
||||
|
|
@ -61,7 +61,7 @@ const SyncGroupsToggle: React.SFC<Props> = (props: Props): JSX.Element => {
|
|||
);
|
||||
};
|
||||
|
||||
const AllowAllToggle: React.SFC<Props> = (props: Props): JSX.Element | null => {
|
||||
const AllowAllToggle = (props: Props): JSX.Element | null => {
|
||||
const {isPublic, isSynced, isDefault, onToggle, isDisabled, policyEnforced} = props;
|
||||
if (isSynced) {
|
||||
return null;
|
||||
|
|
@ -113,7 +113,7 @@ const AllowAllToggle: React.SFC<Props> = (props: Props): JSX.Element | null => {
|
|||
);
|
||||
};
|
||||
|
||||
const PolicyEnforceToggle: React.SFC<Props> = (props: Props): JSX.Element | null => {
|
||||
const PolicyEnforceToggle = (props: Props): JSX.Element | null => {
|
||||
const {isPublic, isSynced, isDefault, onToggle, isDisabled, policyEnforced, policyEnforcedToggleAvailable} = props;
|
||||
if (isSynced) {
|
||||
return null;
|
||||
|
|
@ -152,7 +152,7 @@ const PolicyEnforceToggle: React.SFC<Props> = (props: Props): JSX.Element | null
|
|||
);
|
||||
};
|
||||
|
||||
export const ChannelModes: React.SFC<Props> = (props: Props): JSX.Element => {
|
||||
export const ChannelModes = (props: Props): JSX.Element => {
|
||||
const {isPublic, isSynced, isDefault, onToggle, isDisabled, groupsSupported, policyEnforced, policyEnforcedToggleAvailable, abacSupported} = props;
|
||||
return (
|
||||
<AdminPanel
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ const SyncGroupsToggle = ({syncChecked, allAllowedChecked, allowedDomainsChecked
|
|||
id='admin.team_settings.team_details.syncGroupMembersDescr'
|
||||
defaultMessage='When enabled, adding and removing users from groups will add or remove them from this team. The only way of inviting members to this team is by adding the groups they belong to. <link>Learn More</link>'
|
||||
values={{
|
||||
link: (msg: string) => (
|
||||
link: (msg) => (
|
||||
<ExternalLink
|
||||
href='https://www.mattermost.com/pl/default-ldap-group-constrained-team-channel.html'
|
||||
location='team_modes'
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ export function TeamProfile({team, isArchived, onToggleArchive, isDisabled, save
|
|||
id='admin.teamSettings.teamDetail.teamName'
|
||||
defaultMessage='<b>Team Name</b>:'
|
||||
values={{
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
<br/>
|
||||
|
|
@ -157,7 +157,7 @@ export function TeamProfile({team, isArchived, onToggleArchive, isDisabled, save
|
|||
id='admin.teamSettings.teamDetail.teamDescription'
|
||||
defaultMessage='<b>Team Description</b>:'
|
||||
values={{
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
<br/>
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ const UsersToBeRemovedModal = ({total, scope, scopeId, users, onExited}: Props)
|
|||
defaultMessage='<b>{total, number} {total, plural, one {User} other {Users}}</b> To Be Removed'
|
||||
values={{
|
||||
total,
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import type {FileUpload} from 'components/file_upload/file_upload';
|
|||
import type Textbox from 'components/textbox/textbox';
|
||||
|
||||
import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
|
||||
import {renderWithContext, userEvent, screen, act} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, userEvent, screen} from 'tests/react_testing_utils';
|
||||
import Constants, {Locations, StoragePrefixes} from 'utils/constants';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
|
|
@ -201,9 +201,7 @@ describe('components/avanced_text_editor/advanced_text_editor', () => {
|
|||
);
|
||||
const textbox = screen.getByTestId('post_textbox');
|
||||
|
||||
await act(async () => {
|
||||
userEvent.type(textbox, 'something{esc}');
|
||||
});
|
||||
await userEvent.type(textbox, 'something{escape}');
|
||||
|
||||
expect(textbox).not.toHaveFocus();
|
||||
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||
|
|
@ -230,17 +228,15 @@ describe('components/avanced_text_editor/advanced_text_editor', () => {
|
|||
}),
|
||||
);
|
||||
const textbox = screen.getByTestId('edit_textbox');
|
||||
await act(async () => {
|
||||
userEvent.type(textbox, 'something{esc}');
|
||||
});
|
||||
await userEvent.type(textbox, 'something{escape}', {advanceTimers: jest.advanceTimersByTime});
|
||||
expect(textbox).not.toHaveFocus();
|
||||
|
||||
// save is called with a short delayed after pressing escape key
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(Constants.SAVE_DRAFT_TIMEOUT + 50);
|
||||
});
|
||||
jest.advanceTimersByTime(Constants.SAVE_DRAFT_TIMEOUT + 50);
|
||||
expect(mockedRemoveDraft).toHaveBeenCalled();
|
||||
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -269,14 +265,13 @@ describe('components/avanced_text_editor/advanced_text_editor', () => {
|
|||
|
||||
expect(screen.getByPlaceholderText('Write to Test Channel')).toHaveValue('original draft');
|
||||
|
||||
await act(async () => {
|
||||
rerender(
|
||||
<AdvancedTextEditor
|
||||
{...baseProps}
|
||||
channelId={otherChannelId}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
rerender(
|
||||
<AdvancedTextEditor
|
||||
{...baseProps}
|
||||
channelId={otherChannelId}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByPlaceholderText('Write to Other Channel')).toHaveValue('a different draft');
|
||||
});
|
||||
|
||||
|
|
@ -288,20 +283,16 @@ describe('components/avanced_text_editor/advanced_text_editor', () => {
|
|||
initialState,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
userEvent.type(screen.getByPlaceholderText('Write to Test Channel'), 'some text');
|
||||
});
|
||||
await userEvent.type(screen.getByPlaceholderText('Write to Test Channel'), 'some text');
|
||||
|
||||
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
rerender(
|
||||
<AdvancedTextEditor
|
||||
{...baseProps}
|
||||
channelId={otherChannelId}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
rerender(
|
||||
<AdvancedTextEditor
|
||||
{...baseProps}
|
||||
channelId={otherChannelId}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(mockedUpdateDraft).toHaveBeenCalled();
|
||||
expect(mockedUpdateDraft.mock.calls[0][1]).toMatchObject({
|
||||
|
|
@ -330,14 +321,12 @@ describe('components/avanced_text_editor/advanced_text_editor', () => {
|
|||
|
||||
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
rerender(
|
||||
<AdvancedTextEditor
|
||||
{...baseProps}
|
||||
channelId={otherChannelId}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
rerender(
|
||||
<AdvancedTextEditor
|
||||
{...baseProps}
|
||||
channelId={otherChannelId}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
@ -360,20 +349,16 @@ describe('components/avanced_text_editor/advanced_text_editor', () => {
|
|||
}),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
userEvent.type(screen.getByPlaceholderText('Write to Test Channel'), ' plus some new text');
|
||||
});
|
||||
await userEvent.type(screen.getByPlaceholderText('Write to Test Channel'), ' plus some new text');
|
||||
|
||||
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
rerender(
|
||||
<AdvancedTextEditor
|
||||
{...baseProps}
|
||||
channelId={otherChannelId}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
rerender(
|
||||
<AdvancedTextEditor
|
||||
{...baseProps}
|
||||
channelId={otherChannelId}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(mockedUpdateDraft).toHaveBeenCalled();
|
||||
expect(mockedUpdateDraft.mock.calls[0][1]).toMatchObject({
|
||||
|
|
@ -400,21 +385,17 @@ describe('components/avanced_text_editor/advanced_text_editor', () => {
|
|||
}),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
userEvent.clear(screen.getByPlaceholderText('Write to Test Channel'));
|
||||
});
|
||||
await userEvent.clear(screen.getByPlaceholderText('Write to Test Channel'));
|
||||
|
||||
expect(mockedRemoveDraft).not.toHaveBeenCalled();
|
||||
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
rerender(
|
||||
<AdvancedTextEditor
|
||||
{...baseProps}
|
||||
channelId={otherChannelId}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
rerender(
|
||||
<AdvancedTextEditor
|
||||
{...baseProps}
|
||||
channelId={otherChannelId}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(mockedRemoveDraft).toHaveBeenCalled();
|
||||
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||
|
|
@ -431,14 +412,12 @@ describe('components/avanced_text_editor/advanced_text_editor', () => {
|
|||
expect(mockedRemoveDraft).not.toHaveBeenCalled();
|
||||
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
rerender(
|
||||
<AdvancedTextEditor
|
||||
{...baseProps}
|
||||
channelId={otherChannelId}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
rerender(
|
||||
<AdvancedTextEditor
|
||||
{...baseProps}
|
||||
channelId={otherChannelId}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(mockedRemoveDraft).not.toHaveBeenCalled();
|
||||
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export default function EditPostFooter(props: Props) {
|
|||
defaultMessage='<strong>{key}ENTER</strong> to Save, <strong>ESC</strong> to Cancel'
|
||||
values={{
|
||||
key: sendOnCtrlEnter ? ctrlSendKey : '',
|
||||
strong: (x: string) => <strong>{x}</strong>,
|
||||
strong: (x) => <strong>{x}</strong>,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ describe('FormattingBar', () => {
|
|||
expect(screen.queryByLabelText('show hidden formatting options')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('MM-56705 should not submit form when clicking on hidden formatting button', () => {
|
||||
test('MM-56705 should not submit form when clicking on hidden formatting button', async () => {
|
||||
jest.spyOn(Hooks, 'useFormattingBarControls').mockReturnValue({wideMode: 'narrow', ...splitFormattingBarControls('narrow')});
|
||||
|
||||
const onSubmit = jest.fn();
|
||||
|
|
@ -76,13 +76,13 @@ describe('FormattingBar', () => {
|
|||
|
||||
expect(screen.queryByLabelText('heading')).toBe(null);
|
||||
|
||||
userEvent.click(screen.getByLabelText('show hidden formatting options'));
|
||||
await userEvent.click(screen.getByLabelText('show hidden formatting options'));
|
||||
|
||||
expect(screen.queryByLabelText('heading')).toBeVisible();
|
||||
expect(onSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should disable tooltip when hidden controls are shown', () => {
|
||||
test('should disable tooltip when hidden controls are shown', async () => {
|
||||
jest.spyOn(Hooks, 'useFormattingBarControls').mockReturnValue({wideMode: 'narrow', ...splitFormattingBarControls('narrow')});
|
||||
|
||||
const {container} = renderWithContext(
|
||||
|
|
@ -92,7 +92,7 @@ describe('FormattingBar', () => {
|
|||
const hiddenControlsButton = screen.getByLabelText('show hidden formatting options');
|
||||
|
||||
// Click to show hidden controls
|
||||
userEvent.click(hiddenControlsButton);
|
||||
await userEvent.click(hiddenControlsButton);
|
||||
|
||||
// Find the WithTooltip component and verify it has disabled prop
|
||||
const tooltipWrapper = container.querySelector('.tooltipContainer');
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {DotsHorizontalIcon} from '@mattermost/compass-icons/components';
|
|||
|
||||
import WithTooltip from 'components/with_tooltip';
|
||||
|
||||
import type {ApplyMarkdownOptions} from 'utils/markdown/apply_markdown';
|
||||
import type {ApplyMarkdownOptions, MarkdownMode} from 'utils/markdown/apply_markdown';
|
||||
|
||||
import FormattingIcon, {IconContainer} from './formatting_icon';
|
||||
import {useFormattingBarControls} from './hooks';
|
||||
|
|
@ -178,7 +178,7 @@ const FormattingBar = (props: FormattingBarProps): JSX.Element => {
|
|||
* function signature as if we would define it directly in the props of
|
||||
* the FormattingIcon component. This should improve render-performance
|
||||
*/
|
||||
const makeFormattingHandler = useCallback((mode) => () => {
|
||||
const makeFormattingHandler = useCallback((mode: MarkdownMode) => () => {
|
||||
// if the formatting is disabled just return without doing anything
|
||||
if (disableControls) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ function CoreMenuOptions({handleOnSelect, channelId}: Props) {
|
|||
extraProps.trailingElements = teammateTimeDisplay;
|
||||
}
|
||||
|
||||
const tomorrowClickHandler = useCallback((e) => handleOnSelect(e, tomorrow9amTime), [handleOnSelect, tomorrow9amTime]);
|
||||
const tomorrowClickHandler = useCallback((e: React.UIEvent) => handleOnSelect(e, tomorrow9amTime), [handleOnSelect, tomorrow9amTime]);
|
||||
|
||||
const optionTomorrow = (
|
||||
<Menu.Item
|
||||
|
|
@ -105,7 +105,7 @@ function CoreMenuOptions({handleOnSelect, channelId}: Props) {
|
|||
/>
|
||||
);
|
||||
|
||||
const nextMondayClickHandler = useCallback((e) => handleOnSelect(e, nextMonday), [handleOnSelect, nextMonday]);
|
||||
const nextMondayClickHandler = useCallback((e: React.UIEvent) => handleOnSelect(e, nextMonday), [handleOnSelect, nextMonday]);
|
||||
|
||||
const optionNextMonday = (
|
||||
<Menu.Item
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ function RecentUsedCustomDate({handleOnSelect, userCurrentTimezone, nextMonday,
|
|||
}
|
||||
return {};
|
||||
}, [recentlyUsedCustomDate]);
|
||||
const handleRecentlyUsedCustomTime = useCallback((e) => handleOnSelect(e, recentlyUsedCustomDateVal.timestamp!), [handleOnSelect, recentlyUsedCustomDateVal.timestamp]);
|
||||
const handleRecentlyUsedCustomTime = useCallback((e: React.UIEvent) => handleOnSelect(e, recentlyUsedCustomDateVal.timestamp!), [handleOnSelect, recentlyUsedCustomDateVal.timestamp]);
|
||||
|
||||
if (
|
||||
!shouldShowRecentlyUsedCustomTime(now.toMillis(), recentlyUsedCustomDateVal, userCurrentTimezone, tomorrow9amTime, nextMonday)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import type React from 'react';
|
||||
import {useCallback, useRef, useState} from 'react';
|
||||
import {useCallback, useMemo, useRef, useState} from 'react';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
import type {ServerError} from '@mattermost/types/errors';
|
||||
|
|
@ -12,7 +12,7 @@ import {FileTypes} from 'mattermost-redux/action_types';
|
|||
import {getChannelTimezones} from 'mattermost-redux/actions/channels';
|
||||
import {Permissions} from 'mattermost-redux/constants';
|
||||
import {getChannel, getAllChannelStats} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getFilesIdsForPost} from 'mattermost-redux/selectors/entities/files';
|
||||
import {makeGetFileIdsForPost} from 'mattermost-redux/selectors/entities/files';
|
||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles';
|
||||
|
|
@ -79,6 +79,7 @@ const useSubmit = (
|
|||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const getFilesIdsForPost = useMemo(makeGetFileIdsForPost, []);
|
||||
const postFileIds = useSelector((state: GlobalState) => getFilesIdsForPost(state, postId || ''));
|
||||
|
||||
const isDraftSubmitting = useRef(false);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {fireEvent} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import SystemAnalytics from 'components/analytics/system_analytics';
|
||||
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
|
||||
import Constants from 'utils/constants';
|
||||
|
||||
const StatTypes = Constants.StatTypes;
|
||||
|
|
@ -92,7 +91,7 @@ describe('components/analytics/system_analytics/system_analytics.tsx', () => {
|
|||
renderWithContext(<SystemAnalytics {...baseProps}/>, state, {useMockedStore: true});
|
||||
|
||||
const detailsElement = screen.getByText('Load Advanced Statistics');
|
||||
fireEvent.click(detailsElement);
|
||||
await userEvent.click(detailsElement);
|
||||
|
||||
await screen.findByTestId('totalPostsLineChart');
|
||||
|
||||
|
|
@ -237,12 +236,8 @@ describe('components/analytics/system_analytics/system_analytics.tsx', () => {
|
|||
|
||||
renderWithContext(<SystemAnalytics {...baseProps}/>, state, {useMockedStore: true});
|
||||
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
const detailsElement = screen.getByText('Load Advanced Statistics');
|
||||
fireEvent.click(detailsElement);
|
||||
|
||||
await screen.findByTestId('totalPostsLineChart');
|
||||
await userEvent.click(detailsElement);
|
||||
|
||||
expect(screen.getByTestId('totalPosts')).toHaveTextContent('45');
|
||||
expect(screen.getByTestId('totalPostsLineChart')).toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -251,8 +251,8 @@ const ConfigurationAnnouncementBar = (props: Props) => {
|
|||
}
|
||||
|
||||
if (props.canViewSystemErrors && props.config?.SiteURL === '') {
|
||||
const values: Record<string, ReactNode> = {
|
||||
linkSite: (msg: string) => (
|
||||
const values = {
|
||||
linkSite: (msg: ReactNode[]) => (
|
||||
<ExternalLink
|
||||
href={props.siteURL}
|
||||
location='configuration_announcement_bar'
|
||||
|
|
@ -260,7 +260,7 @@ const ConfigurationAnnouncementBar = (props: Props) => {
|
|||
{msg}
|
||||
</ExternalLink>
|
||||
),
|
||||
linkConsole: (msg: string) => (
|
||||
linkConsole: (msg: ReactNode[]) => (
|
||||
<Link to='/admin_console/environment/web_server'>
|
||||
{msg}
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ const NoInternetConnection: React.FC<NoInternetConnectionProps> = (props: NoInte
|
|||
id='announcement_bar.warn.contact_support_email'
|
||||
defaultMessage='<a>Contact support</a>.'
|
||||
values={{
|
||||
a: (chunks: string) => (
|
||||
a: (chunks) => (
|
||||
<ExternalLink
|
||||
href='mailto:support@mattermost.com'
|
||||
location='announcement_bar'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithContext, userEvent, screen, waitFor} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, userEvent, screen} from 'tests/react_testing_utils';
|
||||
import * as utilsNotifications from 'utils/notifications';
|
||||
|
||||
import NotificationPermissionBar from './index';
|
||||
|
|
@ -63,9 +63,7 @@ describe('NotificationPermissionBar', () => {
|
|||
|
||||
expect(screen.getByText('We need your permission to show notifications in the browser.')).toBeInTheDocument();
|
||||
|
||||
await waitFor(async () => {
|
||||
userEvent.click(screen.getByText('Manage notification preferences'));
|
||||
});
|
||||
await userEvent.click(screen.getByText('Manage notification preferences'));
|
||||
|
||||
expect(utilsNotifications.requestNotificationPermission).toHaveBeenCalled();
|
||||
expect(screen.queryByText('We need your permission to show browser notifications.')).not.toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
|
||||
import type {ReactWrapper} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {act} from 'react-dom/test-utils';
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {act} from 'tests/react_testing_utils';
|
||||
import mockStore from 'tests/test_store';
|
||||
|
||||
import RenewalLink from './renewal_link';
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ describe('AppsFormComponent', () => {
|
|||
const fields = [selectField];
|
||||
const props = {
|
||||
...baseProps,
|
||||
context: {},
|
||||
appsContext: {},
|
||||
form: {
|
||||
fields,
|
||||
},
|
||||
|
|
@ -1117,7 +1117,7 @@ describe('AppsFormComponent', () => {
|
|||
});
|
||||
|
||||
describe('onHide and Modal Behavior', () => {
|
||||
test('should call onHide prop when onHide is triggered', () => {
|
||||
test('should call onHide prop when onHide is triggered', async () => {
|
||||
const mockOnHide = jest.fn();
|
||||
|
||||
const props = {
|
||||
|
|
@ -1128,7 +1128,7 @@ describe('AppsFormComponent', () => {
|
|||
renderWithContext(<AppsForm {...props}/>);
|
||||
|
||||
const cancelButton = screen.getByRole('button', {name: /cancel/i});
|
||||
userEvent.click(cancelButton);
|
||||
await userEvent.click(cancelButton);
|
||||
|
||||
expect(mockOnHide).toHaveBeenCalled();
|
||||
});
|
||||
|
|
@ -1145,8 +1145,8 @@ describe('AppsFormComponent', () => {
|
|||
}).not.toThrow();
|
||||
|
||||
const cancelButton = screen.getByRole('button', {name: /cancel/i});
|
||||
expect(() => {
|
||||
userEvent.click(cancelButton);
|
||||
expect(async () => {
|
||||
await userEvent.click(cancelButton);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
|
|
@ -1168,8 +1168,8 @@ describe('AppsFormComponent', () => {
|
|||
expect(cancelButton).toBeInTheDocument();
|
||||
|
||||
// Clicking cancel should handle submit_on_cancel logic
|
||||
expect(() => {
|
||||
userEvent.click(cancelButton);
|
||||
expect(async () => {
|
||||
await userEvent.click(cancelButton);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {RawAppsFormContainer} from './apps_form_container';
|
|||
describe('components/apps_form/AppsFormContainer', () => {
|
||||
const emojiMap = new EmojiMap(new Map());
|
||||
|
||||
const context = {
|
||||
const appContext = {
|
||||
app_id: 'app',
|
||||
channel_id: 'channel',
|
||||
team_id: 'team',
|
||||
|
|
@ -51,7 +51,7 @@ describe('components/apps_form/AppsFormContainer', () => {
|
|||
path: '/form_url',
|
||||
},
|
||||
},
|
||||
context,
|
||||
appContext,
|
||||
actions: {
|
||||
doAppSubmit: jest.fn().mockResolvedValue({}),
|
||||
doAppFetchForm: jest.fn(),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import AppsForm from './apps_form_component';
|
|||
type Props = {
|
||||
intl: IntlShape;
|
||||
form?: AppForm;
|
||||
context?: AppContext;
|
||||
appContext?: AppContext;
|
||||
timezone?: string;
|
||||
onExited: () => void;
|
||||
onHide?: () => void;
|
||||
|
|
@ -59,11 +59,11 @@ class AppsFormContainer extends React.PureComponent<Props, State> {
|
|||
const errMsg = this.props.intl.formatMessage({id: 'apps.error.form.no_submit', defaultMessage: '`submit` is not defined'});
|
||||
return {error: makeCallErrorResponse(makeErrorMsg(errMsg))};
|
||||
}
|
||||
if (!this.props.context) {
|
||||
if (!this.props.appContext) {
|
||||
return {error: makeCallErrorResponse('unreachable: empty context')};
|
||||
}
|
||||
|
||||
const creq = createCallRequest(form.submit, this.props.context, {}, submission.values);
|
||||
const creq = createCallRequest(form.submit, this.props.appContext, {}, submission.values);
|
||||
const res = await this.props.actions.doAppSubmit(creq, this.props.intl) as DoAppCallResult<FormResponseData>;
|
||||
if (res.error) {
|
||||
return res;
|
||||
|
|
@ -127,11 +127,11 @@ class AppsFormContainer extends React.PureComponent<Props, State> {
|
|||
defaultMessage: 'Called refresh on no refresh field.',
|
||||
})))};
|
||||
}
|
||||
if (!this.props.context) {
|
||||
if (!this.props.appContext) {
|
||||
return {error: makeCallErrorResponse('unreachable: empty context')};
|
||||
}
|
||||
|
||||
const creq = createCallRequest(form.source, this.props.context, {}, values);
|
||||
const creq = createCallRequest(form.source, this.props.appContext, {}, values);
|
||||
creq.selected_field = field.name;
|
||||
|
||||
const res = await this.props.actions.doAppFetchForm(creq, this.props.intl);
|
||||
|
|
@ -180,11 +180,11 @@ class AppsFormContainer extends React.PureComponent<Props, State> {
|
|||
defaultMessage: '`lookup` is not defined.',
|
||||
})))};
|
||||
}
|
||||
if (!this.props.context) {
|
||||
if (!this.props.appContext) {
|
||||
return {error: makeCallErrorResponse('unreachable: empty context')};
|
||||
}
|
||||
|
||||
const creq = createCallRequest(field.lookup, this.props.context, {}, values);
|
||||
const creq = createCallRequest(field.lookup, this.props.appContext, {}, values);
|
||||
creq.selected_field = field.name;
|
||||
creq.query = userInput;
|
||||
|
||||
|
|
@ -194,7 +194,7 @@ class AppsFormContainer extends React.PureComponent<Props, State> {
|
|||
render() {
|
||||
const {form} = this.state;
|
||||
|
||||
if (!form?.submit || !this.props.context) {
|
||||
if (!form?.submit || !this.props.appContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {lazy, type ComponentType} from 'react';
|
||||
import React, {lazy} from 'react';
|
||||
|
||||
import type {PluggableComponentType, PluggableProps} from 'plugins/pluggable/pluggable';
|
||||
|
||||
import type {PluginsState, ProductSubComponentNames} from 'types/store/plugins';
|
||||
|
||||
export function makeAsyncComponent<ComponentProps>(displayName: string, LazyComponent: React.ComponentType<ComponentProps>, fallback: React.ReactNode = null) {
|
||||
const Component: ComponentType<ComponentProps> = (props) => (
|
||||
const Component = (props: ComponentProps & React.JSX.IntrinsicAttributes) => (
|
||||
<React.Suspense fallback={fallback}>
|
||||
<LazyComponent {...props}/>
|
||||
</React.Suspense>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ import {openDirectChannelToUserId} from 'actions/channel_actions';
|
|||
import {closeModal} from 'actions/views/modals';
|
||||
import {isModalOpen} from 'selectors/views/modals';
|
||||
|
||||
import MemberList from 'components/channel_members_rhs/member_list';
|
||||
import type {ListItem} from 'components/channel_members_rhs/member_list';
|
||||
import MemberList, {ListItemType} from 'components/channel_members_rhs/member_list';
|
||||
|
||||
import {ModalIdentifiers} from 'utils/constants';
|
||||
import {mapFeatureIdToTranslation} from 'utils/notify_admin_utils';
|
||||
|
|
@ -44,17 +45,6 @@ export interface ChannelMember {
|
|||
displayName: string;
|
||||
}
|
||||
|
||||
enum ListItemType {
|
||||
Member = 'member',
|
||||
FirstSeparator = 'first-separator',
|
||||
Separator = 'separator',
|
||||
}
|
||||
|
||||
export interface ListItem {
|
||||
type: ListItemType;
|
||||
data: ChannelMember | JSX.Element;
|
||||
}
|
||||
|
||||
const MembersContainer = styled.div`
|
||||
flex: 1 1 auto;
|
||||
padding: 0 4px 16px;
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ export default class Authorize extends React.PureComponent<Props, State> {
|
|||
defaultMessage='Authorize <b>{appName}</b> to Connect to Your <b>Mattermost</b> User Account'
|
||||
values={{
|
||||
appName: app.name,
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -145,7 +145,7 @@ export default class Authorize extends React.PureComponent<Props, State> {
|
|||
defaultMessage='The app <b>{appName}</b> would like the ability to access and modify your basic information.'
|
||||
values={{
|
||||
appName: app.name,
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
|
|
@ -155,7 +155,7 @@ export default class Authorize extends React.PureComponent<Props, State> {
|
|||
defaultMessage='Allow <b>{appName}</b> access?'
|
||||
values={{
|
||||
appName: app.name,
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
</h2>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ type Props = {
|
|||
inputClassName: string;
|
||||
helpText?: React.ReactNode | string;
|
||||
placeholder?: string;
|
||||
footer?: Node;
|
||||
disabled?: boolean;
|
||||
toggleFocus?: ((focus: boolean) => void) | null;
|
||||
listComponent: typeof SuggestionList | typeof ModalSuggestionList;
|
||||
|
|
@ -110,7 +109,6 @@ export default class AutocompleteSelector extends React.PureComponent<Props, Sta
|
|||
const {
|
||||
providers,
|
||||
placeholder,
|
||||
footer,
|
||||
label,
|
||||
labelClassName,
|
||||
helpText,
|
||||
|
|
@ -178,7 +176,6 @@ export default class AutocompleteSelector extends React.PureComponent<Props, Sta
|
|||
listPosition={listPosition}
|
||||
/>
|
||||
{helpTextContent}
|
||||
{footer}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ function BookmarkDeleteModal({
|
|||
id={'channel_bookmarks.confirm.delete.text'}
|
||||
defaultMessage={'Are you sure you want to delete the bookmark <strong>{displayName}</strong>?'}
|
||||
values={{
|
||||
strong: (chunk: string) => <strong>{chunk}</strong>,
|
||||
strong: (chunk) => <strong>{chunk}</strong>,
|
||||
displayName,
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {ComponentProps} from 'react';
|
||||
import React, {useCallback, useRef} from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import styled from 'styled-components';
|
||||
|
|
@ -89,7 +88,7 @@ const CreateModalNameInput = ({
|
|||
setEmoji('');
|
||||
};
|
||||
|
||||
const handleInputChange: ComponentProps<typeof Input>['onChange'] = useCallback((e) => {
|
||||
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDisplayName(e.currentTarget.value);
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ describe('components/channel_header/components/HeaderIconWrapper', () => {
|
|||
expect(screen.getByLabelText('Recent mentions')).toBeVisible();
|
||||
expect(screen.queryByText('Recent mentions')).not.toBeInTheDocument();
|
||||
|
||||
userEvent.hover(screen.getByLabelText('Recent mentions'));
|
||||
await userEvent.hover(screen.getByLabelText('Recent mentions'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Recent mentions')).toBeInTheDocument();
|
||||
|
|
@ -55,7 +55,7 @@ describe('components/channel_header/components/HeaderIconWrapper', () => {
|
|||
expect(screen.queryByText('b')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('c')).not.toBeInTheDocument();
|
||||
|
||||
userEvent.hover(screen.getByLabelText('Recent mentions'));
|
||||
await userEvent.hover(screen.getByLabelText('Recent mentions'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Recent mentions')).toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
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';
|
||||
|
||||
|
|
@ -127,4 +126,4 @@ const connector = connect(makeMapStateToProps, mapDispatchToProps);
|
|||
|
||||
export type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||
|
||||
export default withRouter(connector(ChannelHeader));
|
||||
export default connector(ChannelHeader);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {fireEvent, screen} from '@testing-library/react';
|
||||
import {fireEvent, screen, waitFor} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import {act} from 'react-dom/test-utils';
|
||||
|
||||
import {GenericModal} from '@mattermost/components';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
|
|
@ -18,7 +17,7 @@ import ChannelInviteModal from 'components/channel_invite_modal/channel_invite_m
|
|||
import type {Value} from 'components/multiselect/multiselect';
|
||||
|
||||
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
import {act, renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
type UserProfileValue = Value & UserProfile;
|
||||
|
||||
|
|
@ -291,41 +290,33 @@ describe('components/channel_invite_modal', () => {
|
|||
membersInTeam: {'user-1': {user_id: 'user-1', team_id: channel.team_id, roles: '', delete_at: 0, scheme_admin: false, scheme_guest: false, scheme_user: true, mention_count: 0, mention_count_root: 0, msg_count: 0, msg_count_root: 0} as TeamMembership},
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
const {getByText} = renderWithContext(
|
||||
<ChannelInviteModal
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
const {getByText} = renderWithContext(
|
||||
<ChannelInviteModal
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
// First, we need to simulate selecting a user
|
||||
const input = screen.getByRole('combobox', {name: /search for people/i});
|
||||
// First, we need to simulate selecting a user
|
||||
const input = screen.getByRole('combobox', {name: /search for people/i});
|
||||
|
||||
// Type the search term
|
||||
await userEvent.type(input, 'user-1');
|
||||
// Type the search term
|
||||
await userEvent.type(input, 'user-1');
|
||||
|
||||
// Wait for the promise to resolve
|
||||
await act(async () => {
|
||||
// Wait for the dropdown option to appear
|
||||
const option = await screen.findByText('user-1');
|
||||
// Wait for the dropdown option to appear
|
||||
const option = await screen.findByText('user-1', {selector: '.more-modal__name > span'});
|
||||
|
||||
// Click the option
|
||||
userEvent.click(option);
|
||||
// Click the option
|
||||
await userEvent.click(option);
|
||||
|
||||
// Confirm that the user is now displayed in the selected users
|
||||
expect(screen.getByText('user-1')).toBeInTheDocument();
|
||||
// Confirm that the user is now displayed in the selected users
|
||||
expect(screen.getByText('user-1')).toBeInTheDocument();
|
||||
|
||||
// Find and click the save button
|
||||
const saveButton = getByText('Add');
|
||||
fireEvent.click(saveButton);
|
||||
});
|
||||
// Find and click the save button
|
||||
const saveButton = getByText('Add');
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
// Wait for the promise to resolve
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
// Check that addUsersToChannel was called
|
||||
// Check that addUsersToChannel was called
|
||||
await waitFor(() => {
|
||||
expect(addUsersToChannelMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -347,41 +338,33 @@ describe('components/channel_invite_modal', () => {
|
|||
membersInTeam: {'user-1': {user_id: 'user-1', team_id: channel.team_id, roles: '', delete_at: 0, scheme_admin: false, scheme_guest: false, scheme_user: true, mention_count: 0, mention_count_root: 0, msg_count: 0, msg_count_root: 0} as TeamMembership},
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
const {getByText} = renderWithContext(
|
||||
<ChannelInviteModal
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
const {getByText} = renderWithContext(
|
||||
<ChannelInviteModal
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
// First, we need to simulate selecting a user
|
||||
const input = screen.getByRole('combobox', {name: /search for people/i});
|
||||
// First, we need to simulate selecting a user
|
||||
const input = screen.getByRole('combobox', {name: /search for people/i});
|
||||
|
||||
// Type the search term
|
||||
await userEvent.type(input, 'user-1');
|
||||
// Type the search term
|
||||
await userEvent.type(input, 'user-1');
|
||||
|
||||
// Wait for the promise to resolve
|
||||
await act(async () => {
|
||||
// Wait for the dropdown option to appear
|
||||
const option = await screen.findByText('user-1');
|
||||
// Wait for the dropdown option to appear
|
||||
const option = await screen.findByText('user-1', {selector: '.more-modal__name > span'});
|
||||
|
||||
// Click the option
|
||||
userEvent.click(option);
|
||||
// Click the option
|
||||
await userEvent.click(option);
|
||||
|
||||
// Confirm that the user is now displayed in the selected users
|
||||
expect(screen.getByText('user-1')).toBeInTheDocument();
|
||||
// Confirm that the user is now displayed in the selected users
|
||||
expect(screen.getByText('user-1')).toBeInTheDocument();
|
||||
|
||||
// Find and click the save button
|
||||
const saveButton = getByText('Add');
|
||||
fireEvent.click(saveButton);
|
||||
});
|
||||
// Find and click the save button
|
||||
const saveButton = getByText('Add');
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
// Wait for the promise to resolve
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
// Check that addUsersToChannel was called
|
||||
// Check that addUsersToChannel was called
|
||||
await waitFor(() => {
|
||||
expect(addUsersToChannelMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -399,33 +382,29 @@ describe('components/channel_invite_modal', () => {
|
|||
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
const {getByText} = renderWithContext(
|
||||
<ChannelInviteModal
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
const {getByText} = renderWithContext(
|
||||
<ChannelInviteModal
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
// First, we need to simulate selecting a user
|
||||
const input = screen.getByRole('combobox', {name: /search for people/i});
|
||||
// First, we need to simulate selecting a user
|
||||
const input = screen.getByRole('combobox', {name: /search for people/i});
|
||||
|
||||
await userEvent.type(input, 'user-1');
|
||||
await userEvent.type(input, 'user-1');
|
||||
|
||||
await act(async () => {
|
||||
const option = await screen.findByText('user-1');
|
||||
const option = await screen.findByText('user-1', {selector: '.more-modal__name > span'});
|
||||
|
||||
userEvent.click(option);
|
||||
await userEvent.click(option);
|
||||
|
||||
expect(screen.getByText('user-1')).toBeInTheDocument();
|
||||
expect(screen.getByText('user-1')).toBeInTheDocument();
|
||||
|
||||
const saveButton = getByText('Add');
|
||||
fireEvent.click(saveButton);
|
||||
});
|
||||
const saveButton = getByText('Add');
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
// Check that onAddCallback was called and addUsersToChannel was not
|
||||
expect(onAddCallback).toHaveBeenCalled();
|
||||
expect(props.actions.addUsersToChannel).not.toHaveBeenCalled();
|
||||
});
|
||||
// Check that onAddCallback was called and addUsersToChannel was not
|
||||
expect(onAddCallback).toHaveBeenCalled();
|
||||
expect(props.actions.addUsersToChannel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should trim the search term', async () => {
|
||||
|
|
@ -441,25 +420,22 @@ describe('components/channel_invite_modal', () => {
|
|||
},
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
renderWithContext(
|
||||
<ChannelInviteModal
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
renderWithContext(
|
||||
<ChannelInviteModal
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Find the search input
|
||||
const input = screen.getByRole('combobox', {name: /search for people/i});
|
||||
// Find the search input
|
||||
const input = screen.getByRole('combobox', {name: /search for people/i});
|
||||
|
||||
// Directly trigger the change event with a value that has spaces
|
||||
// Directly trigger the change event with a value that has spaces
|
||||
act(() => {
|
||||
fireEvent.change(input, {target: {value: ' something '}});
|
||||
});
|
||||
|
||||
// Wait for the search timeout plus some extra time
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
});
|
||||
|
||||
// Verify the search was called with the trimmed term
|
||||
// Verify the search was called with the trimmed term
|
||||
await waitFor(() => {
|
||||
expect(searchProfilesMock).toHaveBeenCalledWith(
|
||||
expect.stringContaining('something'),
|
||||
expect.any(Object),
|
||||
|
|
|
|||
|
|
@ -573,7 +573,7 @@ const ChannelInviteModalComponent = (props: Props) => {
|
|||
id='channel_invite.no_options_message'
|
||||
defaultMessage='No matches found - <InvitationModalLink>Invite them to the team</InvitationModalLink>'
|
||||
values={{
|
||||
InvitationModalLink: (chunks: string) => (
|
||||
InvitationModalLink: (chunks) => (
|
||||
<InviteModalLink
|
||||
id='customNoOptionsMessageLink'
|
||||
abacChannelPolicyEnforced={props.channel.policy_enforced}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
|
|
@ -41,9 +41,9 @@ const GroupOption = (props: Props) => {
|
|||
addUserProfile,
|
||||
} = props;
|
||||
|
||||
const getProfilesByIdsAndUsernames = makeGetProfilesByIdsAndUsernames();
|
||||
const getProfilesByIdsAndUsernames = useMemo(makeGetProfilesByIdsAndUsernames, []);
|
||||
|
||||
const profiles = useSelector((state: GlobalState) => getProfilesByIdsAndUsernames(state, {allUserIds: group.member_ids || [], allUsernames: []}) as UserProfileValue[]);
|
||||
const profiles = useSelector((state: GlobalState) => getProfilesByIdsAndUsernames(state, {allUserIds: group.member_ids}) as UserProfileValue[]);
|
||||
const overflowNames = useSelector((state: GlobalState) => {
|
||||
if (group?.member_ids) {
|
||||
return group?.member_ids.map((userId) => displayNameGetter(state, true)(getUser(state, userId))).join(', ');
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import TestHelper from 'packages/mattermost-redux/test/test_helper';
|
|||
import mockStore from 'tests/test_store';
|
||||
import {joinPrivateChannelPrompt} from 'utils/channel_utils';
|
||||
|
||||
import type {Match} from './channel_identifier_router';
|
||||
|
||||
jest.mock('actions/global_actions', () => ({
|
||||
emitChannelClickEvent: jest.fn(),
|
||||
}));
|
||||
|
|
@ -137,7 +139,7 @@ describe('Actions', () => {
|
|||
const testStore = await mockStore(initialState);
|
||||
const history = {replace: jest.fn()};
|
||||
|
||||
await testStore.dispatch((goToChannelByChannelId({params: {team: 'team1', identifier: 'channel_id3', path: '/'}, url: ''}, history as any) as any));
|
||||
await testStore.dispatch((goToChannelByChannelId({params: {team: 'team1', identifier: 'channel_id3', path: '/'}, url: ''} as Match, history as any)));
|
||||
expect(joinChannel).toHaveBeenCalledWith('current_user_id', 'team_id1', 'channel_id3', '');
|
||||
expect(history.replace).toHaveBeenCalledWith('/team1/channels/achannel3');
|
||||
});
|
||||
|
|
@ -147,14 +149,14 @@ describe('Actions', () => {
|
|||
test('switch to channel on different team with same name', async () => {
|
||||
const testStore = await mockStore(initialState);
|
||||
|
||||
await testStore.dispatch((goToChannelByChannelName({params: {team: 'team2', identifier: 'achannel', path: '/'}, url: ''}, {} as any) as any));
|
||||
await testStore.dispatch((goToChannelByChannelName({params: {team: 'team2', identifier: 'achannel', path: '/'}, url: ''} as Match, {} as any)));
|
||||
expect(emitChannelClickEvent).toHaveBeenCalledWith(channel2);
|
||||
});
|
||||
|
||||
test('switch to public channel we have locally but need to join', async () => {
|
||||
const testStore = await mockStore(initialState);
|
||||
|
||||
await testStore.dispatch((goToChannelByChannelName({params: {team: 'team1', identifier: 'achannel3', path: '/'}, url: ''}, {} as any) as any));
|
||||
await testStore.dispatch((goToChannelByChannelName({params: {team: 'team1', identifier: 'achannel3', path: '/'}, url: ''} as Match, {} as any)));
|
||||
expect(joinChannel).toHaveBeenCalledWith('current_user_id', 'team_id1', 'channel_id3', 'achannel3');
|
||||
expect(emitChannelClickEvent).toHaveBeenCalledWith(channel3);
|
||||
});
|
||||
|
|
@ -165,7 +167,7 @@ describe('Actions', () => {
|
|||
const channel = {id: 'channel_id3a', name: 'achannel3a', team_id: 'team_id1', type: 'O'};
|
||||
(joinChannel as jest.Mock).mockReturnValueOnce({type: '', data: {channel}});
|
||||
(getChannelByNameAndTeamName as jest.Mock).mockReturnValueOnce({type: '', data: channel});
|
||||
await testStore.dispatch((goToChannelByChannelName({params: {team: 'team1', identifier: channel.name, path: '/'}, url: ''}, {} as any) as any));
|
||||
await testStore.dispatch((goToChannelByChannelName({params: {team: 'team1', identifier: channel.name, path: '/'}, url: ''} as Match, {} as any)));
|
||||
expect(joinChannel).toHaveBeenCalledWith('current_user_id', 'team_id1', 'channel_id3a', 'achannel3a');
|
||||
expect(emitChannelClickEvent).toHaveBeenCalledWith(channel);
|
||||
});
|
||||
|
|
@ -187,7 +189,7 @@ describe('Actions', () => {
|
|||
const channel = {id: 'channel_id6', name: 'achannel6', team_id: 'team_id1', type: 'P'};
|
||||
(joinChannel as jest.Mock).mockReturnValueOnce({type: '', data: {channel}});
|
||||
(getChannelByNameAndTeamName as jest.Mock).mockReturnValueOnce({type: '', data: channel});
|
||||
await testStore.dispatch((goToChannelByChannelName({params: {team: 'team1', identifier: channel.name, path: '/'}, url: ''}, {} as any) as any));
|
||||
await testStore.dispatch((goToChannelByChannelName({params: {team: 'team1', identifier: channel.name, path: '/'}, url: ''} as Match, {} as any)));
|
||||
expect(getChannelByNameAndTeamName).toHaveBeenCalledWith('team1', channel.name, true);
|
||||
expect(getChannelMember).toHaveBeenCalledWith(channel.id, 'current_user_id');
|
||||
expect(joinPrivateChannelPrompt).toHaveBeenCalled();
|
||||
|
|
@ -227,7 +229,7 @@ describe('Actions', () => {
|
|||
const channel = {id: 'channel_id6', name: 'achannel6', team_id: 'team_id1', type: 'P'};
|
||||
(joinChannel as jest.Mock).mockReturnValueOnce({type: '', data: {channel}});
|
||||
(getChannelByNameAndTeamName as jest.Mock).mockReturnValueOnce({type: '', data: channel});
|
||||
await testStore.dispatch((goToChannelByChannelName({params: {team: 'team1', identifier: channel.name, path: '/'}, url: ''}, {} as any) as any));
|
||||
await testStore.dispatch((goToChannelByChannelName({params: {team: 'team1', identifier: channel.name, path: '/'}, url: ''} as Match, {} as any)));
|
||||
expect(getChannelByNameAndTeamName).toHaveBeenCalledWith('team1', channel.name, true);
|
||||
expect(getChannelMember).toHaveBeenCalledWith(channel.id, 'current_user_id');
|
||||
expect(joinPrivateChannelPrompt).toHaveBeenCalled();
|
||||
|
|
@ -240,7 +242,7 @@ describe('Actions', () => {
|
|||
const testStore = await mockStore(initialState);
|
||||
const history = {replace: jest.fn()};
|
||||
|
||||
await testStore.dispatch((goToDirectChannelByUserId({params: {team: 'team1', identifier: 'channel', path: '/'}, url: ''}, history as any, 'user_id2') as any));
|
||||
await testStore.dispatch((goToDirectChannelByUserId({params: {team: 'team1', identifier: 'channel', path: '/'}, url: ''} as Match, history as any, 'user_id2') as any));
|
||||
expect(history.replace).toHaveBeenCalledWith('/team1/messages/@user2');
|
||||
});
|
||||
|
||||
|
|
@ -248,7 +250,7 @@ describe('Actions', () => {
|
|||
const testStore = await mockStore(initialState);
|
||||
const history = {replace: jest.fn()};
|
||||
|
||||
await testStore.dispatch((goToDirectChannelByUserId({params: {team: 'team2', identifier: 'channel', path: '/'}, url: ''}, history as any, 'user_id2') as any));
|
||||
await testStore.dispatch((goToDirectChannelByUserId({params: {team: 'team2', identifier: 'channel', path: '/'}, url: ''} as Match, history as any, 'user_id2') as any));
|
||||
expect(history.replace).toHaveBeenCalledWith('/team2/messages/@user2');
|
||||
});
|
||||
});
|
||||
|
|
@ -258,7 +260,7 @@ describe('Actions', () => {
|
|||
const testStore = await mockStore(initialState);
|
||||
const history = {replace: jest.fn()};
|
||||
|
||||
await testStore.dispatch((goToDirectChannelByUserIds({params: {team: 'team1', identifier: 'current_user_id__user_id2', path: '/'}, url: ''}, history as any) as any));
|
||||
await testStore.dispatch((goToDirectChannelByUserIds({params: {team: 'team1', identifier: 'current_user_id__user_id2', path: '/'}, url: ''} as Match, history as any)));
|
||||
expect(history.replace).toHaveBeenCalledWith('/team1/messages/@user2');
|
||||
});
|
||||
|
||||
|
|
@ -266,7 +268,7 @@ describe('Actions', () => {
|
|||
const testStore = await mockStore(initialState);
|
||||
const history = {replace: jest.fn()};
|
||||
|
||||
await testStore.dispatch((goToDirectChannelByUserIds({params: {team: 'team2', identifier: 'current_user_id__user_id2', path: '/'}, url: ''}, history as any) as any));
|
||||
await testStore.dispatch((goToDirectChannelByUserIds({params: {team: 'team2', identifier: 'current_user_id__user_id2', path: '/'}, url: ''} as Match, history as any)));
|
||||
expect(history.replace).toHaveBeenCalledWith('/team2/messages/@user2');
|
||||
});
|
||||
});
|
||||
|
|
@ -276,7 +278,7 @@ describe('Actions', () => {
|
|||
const testStore = await mockStore(initialState);
|
||||
const history = {replace: jest.fn()};
|
||||
|
||||
await testStore.dispatch((goToDirectChannelByEmail({params: {team: 'team1', identifier: 'user2@bladekick.com', path: '/'}, url: ''}, history as any) as any));
|
||||
await testStore.dispatch((goToDirectChannelByEmail({params: {team: 'team1', identifier: 'user2@bladekick.com', path: '/'}, url: ''} as Match, history as any)));
|
||||
expect(getUserByEmail).not.toHaveBeenCalled();
|
||||
expect(history.replace).toHaveBeenCalledWith('/team1/messages/@user2');
|
||||
});
|
||||
|
|
@ -285,7 +287,7 @@ describe('Actions', () => {
|
|||
const testStore = await mockStore(initialState);
|
||||
const history = {replace: jest.fn()};
|
||||
|
||||
await testStore.dispatch((goToDirectChannelByEmail({params: {team: 'team1', identifier: 'user3@bladekick.com', path: '/'}, url: ''}, history as any) as any));
|
||||
await testStore.dispatch((goToDirectChannelByEmail({params: {team: 'team1', identifier: 'user3@bladekick.com', path: '/'}, url: ''} as Match, history as any)));
|
||||
expect(getUserByEmail).toHaveBeenCalledWith('user3@bladekick.com');
|
||||
expect(history.replace).toHaveBeenCalledWith('/team1/messages/@user3');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import type {History} from 'history';
|
||||
import React from 'react';
|
||||
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
|
|
@ -14,18 +15,20 @@ describe('components/channel_layout/CenterChannel', () => {
|
|||
const baseProps = {
|
||||
|
||||
match: {
|
||||
isExact: false,
|
||||
params: {
|
||||
identifier: 'identifier',
|
||||
team: 'team',
|
||||
path: '/path',
|
||||
},
|
||||
path: '/team/channel/identifier',
|
||||
url: '/team/channel/identifier',
|
||||
},
|
||||
|
||||
actions: {
|
||||
onChannelByIdentifierEnter: jest.fn(),
|
||||
},
|
||||
history: [],
|
||||
history: [] as unknown as History,
|
||||
};
|
||||
|
||||
test('should call onChannelByIdentifierEnter on props change', () => {
|
||||
|
|
@ -36,11 +39,13 @@ describe('components/channel_layout/CenterChannel', () => {
|
|||
|
||||
const props2 = {
|
||||
match: {
|
||||
isExact: false,
|
||||
params: {
|
||||
identifier: 'identifier2',
|
||||
team: 'team2',
|
||||
path: '/path2',
|
||||
},
|
||||
path: '/team2/channel/identifier2',
|
||||
url: '/team2/channel/identifier2',
|
||||
},
|
||||
};
|
||||
|
|
@ -62,12 +67,14 @@ describe('components/channel_layout/CenterChannel', () => {
|
|||
const props = {
|
||||
...baseProps,
|
||||
match: {
|
||||
isExact: false,
|
||||
params: {
|
||||
identifier: 'identifier',
|
||||
team: 'team',
|
||||
path: '/path',
|
||||
postid: 'abcd',
|
||||
},
|
||||
path: '/team/channel/identifier/abcd',
|
||||
url: '/team/channel/identifier/abcd',
|
||||
},
|
||||
};
|
||||
|
|
@ -80,12 +87,14 @@ describe('components/channel_layout/CenterChannel', () => {
|
|||
const props = {
|
||||
...baseProps,
|
||||
match: {
|
||||
isExact: false,
|
||||
params: {
|
||||
identifier: 'identifier1',
|
||||
team: 'team1',
|
||||
path: '/path1',
|
||||
postid: 'abcd',
|
||||
},
|
||||
path: '/team1/channel/identifier1/abcd',
|
||||
url: '/team1/channel/identifier1/abcd',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,31 +1,30 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {History} from 'history';
|
||||
import React from 'react';
|
||||
import type {match} from 'react-router-dom';
|
||||
|
||||
import ChannelView from 'components/channel_view/index';
|
||||
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
import Constants from 'utils/constants';
|
||||
|
||||
export interface Match {
|
||||
params: {
|
||||
identifier: string;
|
||||
team: string;
|
||||
postid?: string;
|
||||
path: string;
|
||||
};
|
||||
url: string;
|
||||
}
|
||||
export type Match = match<{
|
||||
identifier: string;
|
||||
team: string;
|
||||
postid?: string;
|
||||
path: string;
|
||||
}>;
|
||||
|
||||
export type MatchAndHistory = Pick<Props, 'match' | 'history'>
|
||||
|
||||
type Props = {
|
||||
match: Match;
|
||||
actions: {
|
||||
onChannelByIdentifierEnter: (props: MatchAndHistory) => any;
|
||||
onChannelByIdentifierEnter: (props: MatchAndHistory) => void;
|
||||
};
|
||||
history: any;
|
||||
history: History;
|
||||
};
|
||||
|
||||
export default class ChannelIdentifierRouter extends React.PureComponent<Props> {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import MenuWrapper from 'components/widgets/menu/menu_wrapper';
|
|||
import {Constants, ModalIdentifiers} from 'utils/constants';
|
||||
|
||||
import type {ModalData} from 'types/actions';
|
||||
import type {MMAction} from 'types/store';
|
||||
|
||||
const ROWS_FROM_BOTTOM_TO_OPEN_UP = 2;
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ export interface Props {
|
|||
updateChannelMemberSchemeRoles: (channelId: string, userId: string, isSchemeUser: boolean, isSchemeAdmin: boolean) => Promise<ActionResult>;
|
||||
removeChannelMember: (channelId: string, userId: string) => Promise<ActionResult>;
|
||||
getChannelMember: (channelId: string, userId: string) => void;
|
||||
openModal: <P>(modalData: ModalData<P>) => void;
|
||||
openModal: <P>(modalData: ModalData<P>) => MMAction;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export interface Props {
|
|||
const ActionBar = ({className, channelType, membersCount, canManageMembers, editing, actions}: Props) => {
|
||||
const showManageButton = channelType !== Constants.GM_CHANNEL && membersCount > 1;
|
||||
|
||||
const handleShortcut = useCallback((e) => {
|
||||
const handleShortcut = useCallback((e: KeyboardEvent) => {
|
||||
if (isKeyPressed(e, Constants.KeyCodes.ESCAPE) && editing) {
|
||||
actions.stopEditing();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import React from 'react';
|
|||
import type {ChannelType} from '@mattermost/types/channels';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {renderWithContext, screen, waitFor, act} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, screen, waitFor} from 'tests/react_testing_utils';
|
||||
|
||||
import Member from './member';
|
||||
import type {ChannelMember} from './member_list';
|
||||
|
|
@ -197,13 +197,10 @@ describe('components/channel_members_rhs/Member', () => {
|
|||
const icon = screen.getByTestId('SharedChannelIcon');
|
||||
expect(icon).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.hover(icon);
|
||||
jest.advanceTimersByTime(1000);
|
||||
await userEvent.hover(icon, {advanceTimers: jest.advanceTimersByTime});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Shared with: Remote Organization')).toBeInTheDocument();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Shared with: Remote Organization')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -229,13 +226,10 @@ describe('components/channel_members_rhs/Member', () => {
|
|||
const icon = screen.getByTestId('SharedChannelIcon');
|
||||
expect(icon).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.hover(icon);
|
||||
jest.advanceTimersByTime(1000);
|
||||
await userEvent.hover(icon, {advanceTimers: jest.advanceTimersByTime});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Shared with trusted organizations')).toBeInTheDocument();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Shared with trusted organizations')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ export enum ListItemType {
|
|||
Separator = 'separator',
|
||||
}
|
||||
|
||||
export interface ListItem {
|
||||
export type ListItem = {
|
||||
type: ListItemType;
|
||||
data: ChannelMember | JSX.Element;
|
||||
}
|
||||
};
|
||||
export interface Props {
|
||||
channel: Channel;
|
||||
members: ListItem[];
|
||||
|
|
@ -120,7 +120,7 @@ const MemberList = ({
|
|||
key={index}
|
||||
style={style}
|
||||
>
|
||||
{members[index].data}
|
||||
{members[index].data as JSX.Element}
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {act, screen, waitFor} from '@testing-library/react';
|
||||
import {screen, waitFor} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
|
|
@ -70,9 +70,7 @@ describe('ChannelNotificationsModal', () => {
|
|||
expect(screen.queryByText('Mobile Notifications')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Follow all threads in this channel')).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
|
|
@ -99,17 +97,13 @@ describe('ChannelNotificationsModal', () => {
|
|||
);
|
||||
|
||||
const ignoreChannel = screen.getByTestId('ignoreMentions');
|
||||
await act(async () => {
|
||||
await userEvent.click(ignoreChannel);
|
||||
});
|
||||
await userEvent.click(ignoreChannel);
|
||||
expect(ignoreChannel).toBeChecked();
|
||||
|
||||
// Verify the checkbox label is present
|
||||
expect(screen.getByText('Ignore mentions for @channel, @here and @all')).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
await waitFor(() =>
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
'current_user_id',
|
||||
|
|
@ -146,20 +140,14 @@ describe('ChannelNotificationsModal', () => {
|
|||
expect(nothingRadio).toBeInTheDocument();
|
||||
|
||||
// Test clicking through the options
|
||||
await act(async () => {
|
||||
await userEvent.click(allRadio);
|
||||
});
|
||||
await userEvent.click(allRadio);
|
||||
expect(allRadio).toBeChecked();
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(mentionsRadio);
|
||||
});
|
||||
await userEvent.click(mentionsRadio);
|
||||
expect(mentionsRadio).toBeChecked();
|
||||
expect(allRadio).not.toBeChecked();
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(nothingRadio);
|
||||
});
|
||||
await userEvent.click(nothingRadio);
|
||||
expect(nothingRadio).toBeChecked();
|
||||
expect(mentionsRadio).not.toBeChecked();
|
||||
|
||||
|
|
@ -168,9 +156,7 @@ describe('ChannelNotificationsModal', () => {
|
|||
expect(screen.getByText(/Mentions, direct messages, and keywords only/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Nothing/)).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
await waitFor(() =>
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
'current_user_id',
|
||||
|
|
@ -194,14 +180,10 @@ describe('ChannelNotificationsModal', () => {
|
|||
renderWithContext(<ChannelNotificationsModal {...baseProps}/>);
|
||||
|
||||
// Since the default value is checked, we will uncheck the checkbox
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByTestId('desktopNotificationSoundsCheckbox'));
|
||||
});
|
||||
await userEvent.click(screen.getByTestId('desktopNotificationSoundsCheckbox'));
|
||||
expect(screen.getByTestId('desktopNotificationSoundsCheckbox')).not.toBeChecked();
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
await waitFor(() => {
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
'current_user_id',
|
||||
|
|
@ -252,9 +234,7 @@ describe('ChannelNotificationsModal', () => {
|
|||
// When "same as desktop" is checked, mobile-specific options should not be visible
|
||||
expect(screen.queryByTestId('mobile-notify-me-radio-section')).not.toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
await waitFor(() =>
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
'current_user_id',
|
||||
|
|
@ -300,26 +280,18 @@ describe('ChannelNotificationsModal', () => {
|
|||
const noneRadio = screen.getByTestId('MobileNotification-none');
|
||||
|
||||
// Test clicking through the mobile notification options
|
||||
await act(async () => {
|
||||
await userEvent.click(allRadio);
|
||||
});
|
||||
await userEvent.click(allRadio);
|
||||
expect(allRadio).toBeChecked();
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(mentionRadio);
|
||||
});
|
||||
await userEvent.click(mentionRadio);
|
||||
expect(mentionRadio).toBeChecked();
|
||||
expect(allRadio).not.toBeChecked();
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(noneRadio);
|
||||
});
|
||||
await userEvent.click(noneRadio);
|
||||
expect(noneRadio).toBeChecked();
|
||||
expect(mentionRadio).not.toBeChecked();
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
await waitFor(() =>
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
'current_user_id',
|
||||
|
|
@ -356,9 +328,7 @@ describe('ChannelNotificationsModal', () => {
|
|||
expect(screen.getByText('Mobile Notifications')).toBeInTheDocument();
|
||||
expect(screen.getByText('Mute or ignore')).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ export class ChannelSelectorModal extends React.PureComponent<Props, State> {
|
|||
id='channelSelectorModal.title'
|
||||
defaultMessage='Add Channels to <b>Channel Selection</b> List'
|
||||
values={{
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
</Modal.Title>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@ import React from 'react';
|
|||
import type {UserPropertyField} from '@mattermost/types/properties';
|
||||
|
||||
import TableEditor from 'components/admin_console/access_control/editors/table_editor/table_editor';
|
||||
import SaveChangesPanel from 'components/widgets/modals/components/save_changes_panel';
|
||||
|
||||
import {useChannelAccessControlActions} from 'hooks/useChannelAccessControlActions';
|
||||
import {useChannelSystemPolicies} from 'hooks/useChannelSystemPolicies';
|
||||
import {renderWithContext, screen, waitFor, userEvent} from 'tests/react_testing_utils';
|
||||
import {act, renderWithContext, screen, waitFor, userEvent} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import ChannelSettingsAccessRulesTab from './channel_settings_access_rules_tab';
|
||||
|
|
@ -25,32 +24,9 @@ jest.mock('components/admin_console/access_control/editors/table_editor/table_ed
|
|||
return jest.fn(() => React.createElement('div', {'data-testid': 'table-editor'}, 'TableEditor'));
|
||||
});
|
||||
|
||||
// Mock SaveChangesPanel
|
||||
jest.mock('components/widgets/modals/components/save_changes_panel', () => {
|
||||
const React = require('react');
|
||||
return jest.fn((props) => {
|
||||
return React.createElement('div', {
|
||||
'data-testid': 'save-changes-panel',
|
||||
'data-state': props.state,
|
||||
}, [
|
||||
React.createElement('button', {
|
||||
key: 'save',
|
||||
'data-testid': 'SaveChangesPanel__save-btn',
|
||||
onClick: props.handleSubmit,
|
||||
}, 'Save'),
|
||||
React.createElement('button', {
|
||||
key: 'cancel',
|
||||
'data-testid': 'SaveChangesPanel__cancel-btn',
|
||||
onClick: props.handleCancel,
|
||||
}, 'Reset'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
const mockUseChannelAccessControlActions = useChannelAccessControlActions as jest.MockedFunction<typeof useChannelAccessControlActions>;
|
||||
const mockUseChannelSystemPolicies = useChannelSystemPolicies as jest.MockedFunction<typeof useChannelSystemPolicies>;
|
||||
const MockedTableEditor = TableEditor as jest.MockedFunction<typeof TableEditor>;
|
||||
const MockedSaveChangesPanel = SaveChangesPanel as jest.MockedFunction<typeof SaveChangesPanel>;
|
||||
|
||||
describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () => {
|
||||
const mockActions = {
|
||||
|
|
@ -181,9 +157,9 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
},
|
||||
});
|
||||
|
||||
// Suppress console methods for tests
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
// Console methods are suppressed in these tests. They'll be restored by setup_jest.ts after the test.
|
||||
console.error = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
jest.spyOn(window, 'alert').mockImplementation(() => {});
|
||||
});
|
||||
|
|
@ -274,13 +250,15 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
expect(screen.getByText('Select user attributes and values as rules to restrict channel membership')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should call useChannelAccessControlActions hook', () => {
|
||||
test('should call useChannelAccessControlActions hook', async () => {
|
||||
renderWithContext(
|
||||
<ChannelSettingsAccessRulesTab {...baseProps}/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
expect(mockUseChannelAccessControlActions).toHaveBeenCalledTimes(2); // Once for the hook call and once for the mock return
|
||||
await waitFor(() => {
|
||||
expect(mockUseChannelAccessControlActions).toHaveBeenCalledTimes(2); // Once for the hook call and once for the mock return
|
||||
});
|
||||
});
|
||||
|
||||
test('should load user attributes on mount', async () => {
|
||||
|
|
@ -363,7 +341,9 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
const onChangeCallback = MockedTableEditor.mock.calls[0][0].onChange;
|
||||
|
||||
// Simulate expression change
|
||||
onChangeCallback('user.attributes.department == "Engineering"');
|
||||
act(() => {
|
||||
onChangeCallback('user.attributes.department == "Engineering"');
|
||||
});
|
||||
|
||||
expect(setAreThereUnsavedChanges).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
|
@ -808,7 +788,7 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
expect(screen.getByTestId('table-editor')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.queryByTestId('save-changes-panel')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('You have unsaved changes')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show SaveChangesPanel when expression changes', async () => {
|
||||
|
|
@ -828,7 +808,7 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
onChangeCallback('user.attributes.department == "Engineering"');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('save-changes-panel')).toBeInTheDocument();
|
||||
expect(screen.getByText('You have unsaved changes')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -856,7 +836,7 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
await userEvent.click(checkbox);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('save-changes-panel')).toBeInTheDocument();
|
||||
expect(screen.getByText('You have unsaved changes')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -904,11 +884,11 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
|
||||
// Wait for SaveChangesPanel to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('save-changes-panel')).toBeInTheDocument();
|
||||
expect(screen.getByText('You have unsaved changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click Save button
|
||||
const saveButton = screen.getByTestId('SaveChangesPanel__save-btn');
|
||||
const saveButton = screen.getByText('Save');
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
// Wait for confirmation modal to appear
|
||||
|
|
@ -985,11 +965,6 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
await userEvent.click(saveButton);
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
// Wait for async operations to complete
|
||||
await waitFor(() => {
|
||||
expect(mockActions.saveChannelPolicy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Should only have been called once due to duplicate prevention
|
||||
expect(mockActions.saveChannelPolicy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
|
@ -1015,16 +990,16 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
|
||||
// Wait for SaveChangesPanel to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('save-changes-panel')).toBeInTheDocument();
|
||||
expect(screen.getByText('You have unsaved changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click Reset button
|
||||
const resetButton = screen.getByTestId('SaveChangesPanel__cancel-btn');
|
||||
const resetButton = screen.getByText('Reset');
|
||||
await userEvent.click(resetButton);
|
||||
|
||||
// Verify panel disappears
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('save-changes-panel')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('You have unsaved changes')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Verify checkbox is reset
|
||||
|
|
@ -1050,9 +1025,8 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
onChangeCallback('invalid expression');
|
||||
|
||||
await waitFor(() => {
|
||||
const panel = screen.getByTestId('save-changes-panel');
|
||||
const panel = screen.getByText('Invalid expression format');
|
||||
expect(panel).toBeInTheDocument();
|
||||
expect(panel).toHaveAttribute('data-state', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1086,53 +1060,11 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
await userEvent.click(checkbox);
|
||||
|
||||
await waitFor(() => {
|
||||
const panel = screen.getByTestId('save-changes-panel');
|
||||
expect(panel).toBeInTheDocument();
|
||||
expect(panel).toHaveAttribute('data-state', 'error');
|
||||
const panel = screen.getByText('You have unsaved changes');
|
||||
expect(panel).toHaveClass('error');
|
||||
});
|
||||
});
|
||||
|
||||
test('should pass correct props to SaveChangesPanel', async () => {
|
||||
renderWithContext(
|
||||
<ChannelSettingsAccessRulesTab {...baseProps}/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
// Wait for initial loading to complete
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('table-editor')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Add an expression first to enable the checkbox
|
||||
const onChangeCallback = MockedTableEditor.mock.calls[0][0].onChange;
|
||||
onChangeCallback('user.attributes.department == "Engineering"');
|
||||
|
||||
await waitFor(() => {
|
||||
const checkbox = screen.getByRole('checkbox');
|
||||
expect(checkbox).not.toBeDisabled();
|
||||
});
|
||||
|
||||
// Toggle auto-sync to show panel
|
||||
const checkbox = screen.getByRole('checkbox');
|
||||
await userEvent.click(checkbox);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('save-changes-panel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(MockedSaveChangesPanel).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
handleSubmit: expect.any(Function),
|
||||
handleCancel: expect.any(Function),
|
||||
handleClose: expect.any(Function),
|
||||
tabChangeError: false,
|
||||
state: undefined,
|
||||
cancelButtonText: 'Reset',
|
||||
}),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
test('should update SaveChangesPanel state to saved after successful save', async () => {
|
||||
// Ensure the searchUsers mock returns current user for validation to pass
|
||||
mockActions.searchUsers.mockResolvedValue({
|
||||
|
|
@ -1170,11 +1102,11 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
await userEvent.click(checkbox);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('save-changes-panel')).toBeInTheDocument();
|
||||
expect(screen.getByText('You have unsaved changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click Save button
|
||||
const saveButton = screen.getByTestId('SaveChangesPanel__save-btn');
|
||||
const saveButton = screen.getByText('Save');
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
// Wait for confirmation modal to appear
|
||||
|
|
@ -1189,8 +1121,8 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
|
||||
// Wait for save to complete and panel to show saved state
|
||||
await waitFor(() => {
|
||||
const panel = screen.getByTestId('save-changes-panel');
|
||||
expect(panel).toHaveAttribute('data-state', 'saved');
|
||||
const panel = screen.getByText('Settings saved');
|
||||
expect(panel).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1358,10 +1290,10 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
|
||||
// Click Save to trigger membership calculation
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('save-changes-panel')).toBeInTheDocument();
|
||||
expect(screen.getByText('You have unsaved changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const saveButton = screen.getByTestId('SaveChangesPanel__save-btn');
|
||||
const saveButton = screen.getByText('Save');
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
// Wait for confirmation modal to appear
|
||||
|
|
@ -1433,10 +1365,10 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
|
||||
// Click Save to trigger self-exclusion validation
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('save-changes-panel')).toBeInTheDocument();
|
||||
expect(screen.getByText('You have unsaved changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const saveButton = screen.getByTestId('SaveChangesPanel__save-btn');
|
||||
const saveButton = screen.getByText('Save');
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
// Wait for confirmation modal to appear
|
||||
|
|
@ -1488,10 +1420,10 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
|
||||
// Click Save to trigger membership calculation
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('save-changes-panel')).toBeInTheDocument();
|
||||
expect(screen.getByText('You have unsaved changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const saveButton = screen.getByTestId('SaveChangesPanel__save-btn');
|
||||
const saveButton = screen.getByText('Save');
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
// Wait for confirmation modal to appear
|
||||
|
|
@ -1559,10 +1491,10 @@ describe('components/channel_settings_modal/ChannelSettingsAccessRulesTab', () =
|
|||
|
||||
// Click Save to trigger membership calculation
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('save-changes-panel')).toBeInTheDocument();
|
||||
expect(screen.getByText('You have unsaved changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const saveButton = screen.getByTestId('SaveChangesPanel__save-btn');
|
||||
const saveButton = screen.getByText('Save');
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
// Wait for confirmation modal to appear
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ function ChannelSettingsArchiveTab({
|
|||
defaultMessage='Are you sure you wish to archive the <strong>{display_name}</strong> channel?'
|
||||
values={{
|
||||
display_name: channel.display_name,
|
||||
strong: (chunks: string) => <strong>{chunks}</strong>,
|
||||
strong: (chunks) => <strong>{chunks}</strong>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {act, screen} from '@testing-library/react';
|
||||
import {screen} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
|
|
@ -93,9 +93,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
expect(toggle).not.toHaveClass('active');
|
||||
|
||||
// Click the toggle to enable the banner
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
});
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
|
||||
// Banner text and color inputs should be visible when banner is enabled
|
||||
expect(screen.getByTestId('channel_banner_banner_text_textbox')).toBeInTheDocument();
|
||||
|
|
@ -130,9 +128,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
expect(screen.queryByTestId('channel_banner_banner_text_textbox')).not.toBeInTheDocument();
|
||||
|
||||
// Click the toggle to enable the banner
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
});
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
|
||||
// Banner settings should now be visible
|
||||
expect(screen.getByTestId('channel_banner_banner_text_textbox')).toBeInTheDocument();
|
||||
|
|
@ -146,9 +142,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
expect(screen.queryByRole('button', {name: 'Save'})).not.toBeInTheDocument();
|
||||
|
||||
// Enable the banner
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
});
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
|
||||
// Add a small delay to ensure all state updates are processed
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
|
@ -164,28 +158,20 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
renderWithContext(<ChannelSettingsConfigurationTab {...baseProps}/>);
|
||||
|
||||
// Enable the banner
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
});
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
|
||||
// Enter banner text
|
||||
await act(async () => {
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, 'New banner text');
|
||||
});
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, 'New banner text');
|
||||
|
||||
// Set banner color
|
||||
const colorInput = screen.getByTestId('color-inputColorValue');
|
||||
await act(async () => {
|
||||
await userEvent.clear(colorInput);
|
||||
await userEvent.type(colorInput, '#AA00AA');
|
||||
});
|
||||
await userEvent.clear(colorInput);
|
||||
await userEvent.type(colorInput, '#AA00AA');
|
||||
|
||||
// Click the Save button
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// Verify patchChannel was called with the updated values
|
||||
expect(patchChannel).toHaveBeenCalledWith('channel1', {
|
||||
|
|
@ -202,11 +188,9 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
renderWithContext(<ChannelSettingsConfigurationTab {...{...baseProps, channel: mockChannelWithBanner}}/>);
|
||||
|
||||
// Change the banner text
|
||||
await act(async () => {
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, 'Changed banner text');
|
||||
});
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, 'Changed banner text');
|
||||
|
||||
// Add a small delay to ensure all state updates are processed
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
|
@ -215,9 +199,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
expect(screen.getByRole('button', {name: 'Save'})).toBeInTheDocument();
|
||||
|
||||
// Click the Reset button
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Reset'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Reset'}));
|
||||
|
||||
// Form should be reset to original values
|
||||
expect(screen.getByTestId('channel_banner_banner_text_textbox')).toHaveValue('Test banner text');
|
||||
|
|
@ -230,20 +212,14 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
renderWithContext(<ChannelSettingsConfigurationTab {...baseProps}/>);
|
||||
|
||||
// Enable the banner
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
});
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
|
||||
// Leave banner text empty
|
||||
await act(async () => {
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
});
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
|
||||
// Click the Save button
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// SaveChangesPanel should show error state
|
||||
const errorMessage = screen.getByText(/Banner text is required/);
|
||||
|
|
@ -255,19 +231,15 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
renderWithContext(<ChannelSettingsConfigurationTab {...baseProps}/>);
|
||||
|
||||
// Enable the banner
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
});
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
|
||||
// Create a string that exceeds the allowed character limit
|
||||
const longText = 'a'.repeat(1025);
|
||||
|
||||
// Enter long banner text
|
||||
await act(async () => {
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, longText);
|
||||
});
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, longText);
|
||||
|
||||
// Add a small delay to ensure all state updates are processed
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
|
@ -286,9 +258,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
expect(previewButton).not.toHaveClass('active');
|
||||
|
||||
// Click the preview button
|
||||
await act(async () => {
|
||||
await userEvent.click(previewButton);
|
||||
});
|
||||
await userEvent.click(previewButton);
|
||||
|
||||
// Preview should now be active
|
||||
expect(previewButton).toHaveClass('active');
|
||||
|
|
@ -301,9 +271,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
expect(screen.getByTestId('channel_banner_banner_text_textbox')).toBeInTheDocument();
|
||||
|
||||
// Click the toggle to disable the banner
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
});
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
|
||||
// Banner settings should now be hidden
|
||||
expect(screen.queryByTestId('channel_banner_banner_text_textbox')).not.toBeInTheDocument();
|
||||
|
|
@ -316,21 +284,15 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
renderWithContext(<ChannelSettingsConfigurationTab {...baseProps}/>);
|
||||
|
||||
// Enable the banner
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
});
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
|
||||
// Enter banner text but leave color empty
|
||||
await act(async () => {
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, 'New banner text');
|
||||
});
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, 'New banner text');
|
||||
|
||||
// Click the Save button
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// SaveChangesPanel should show error state
|
||||
const errorMessage = screen.getByText(/Banner color is required/);
|
||||
|
|
@ -345,28 +307,20 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
renderWithContext(<ChannelSettingsConfigurationTab {...baseProps}/>);
|
||||
|
||||
// Enable the banner
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
});
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
|
||||
// Enter banner text
|
||||
await act(async () => {
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, 'New banner text');
|
||||
});
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, 'New banner text');
|
||||
|
||||
// Enter a valid hex color
|
||||
await act(async () => {
|
||||
const colorInput = screen.getByTestId('color-inputColorValue');
|
||||
await userEvent.clear(colorInput);
|
||||
await userEvent.type(colorInput, '#ff0000');
|
||||
});
|
||||
const colorInput = screen.getByTestId('color-inputColorValue');
|
||||
await userEvent.clear(colorInput);
|
||||
await userEvent.type(colorInput, '#ff0000');
|
||||
|
||||
// Click the Save button
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// Verify patchChannel was called with the correct color
|
||||
expect(patchChannel).toHaveBeenCalledWith('channel1', expect.objectContaining({
|
||||
|
|
@ -402,18 +356,14 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
);
|
||||
|
||||
// Enter a invalid hex color
|
||||
await act(async () => {
|
||||
const colorInput = screen.getByTestId('color-inputColorValue');
|
||||
await userEvent.clear(colorInput);
|
||||
await userEvent.type(colorInput, 'not-a-color');
|
||||
});
|
||||
const colorInput = screen.getByTestId('color-inputColorValue');
|
||||
await userEvent.clear(colorInput);
|
||||
await userEvent.type(colorInput, 'not-a-color');
|
||||
|
||||
// Do another action to trigger blur on this input so color is validated
|
||||
await act(async () => {
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, 'Test text');
|
||||
});
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, 'Test text');
|
||||
|
||||
// if invalid, the color automatically returns to the original color
|
||||
expect(screen.getByTestId('color-inputColorValue')).toHaveValue(originalColor);
|
||||
|
|
@ -422,11 +372,8 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
expect(screen.queryByRole('button', {name: 'Save'})).not.toBeInTheDocument();
|
||||
|
||||
// Modify the color to a valid one
|
||||
await act(async () => {
|
||||
const colorInput = screen.getByTestId('color-inputColorValue');
|
||||
await userEvent.clear(colorInput);
|
||||
await userEvent.type(colorInput, '#123456');
|
||||
});
|
||||
await userEvent.clear(colorInput);
|
||||
await userEvent.type(colorInput, '#123456');
|
||||
|
||||
// Add a small delay to ensure all state updates are processed
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
|
@ -442,23 +389,17 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
renderWithContext(<ChannelSettingsConfigurationTab {...{...baseProps, channel: mockChannelWithBanner}}/>);
|
||||
|
||||
// Add whitespace to the banner text
|
||||
await act(async () => {
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, ' Banner text with whitespace ');
|
||||
});
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
await userEvent.clear(textInput);
|
||||
await userEvent.type(textInput, ' Banner text with whitespace ');
|
||||
|
||||
// Add whitespace to the banner color
|
||||
await act(async () => {
|
||||
const colorInput = screen.getByTestId('color-inputColorValue');
|
||||
await userEvent.clear(colorInput);
|
||||
await userEvent.type(colorInput, ' #00FF00 ');
|
||||
});
|
||||
const colorInput = screen.getByTestId('color-inputColorValue');
|
||||
await userEvent.clear(colorInput);
|
||||
await userEvent.type(colorInput, ' #00FF00 ');
|
||||
|
||||
// Click the Save button
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// Verify patchChannel was called with the trimmed values
|
||||
expect(patchChannel).toHaveBeenCalledWith('channel1', {
|
||||
|
|
@ -475,7 +416,6 @@ describe('ChannelSettingsConfigurationTab', () => {
|
|||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
// The text input should now have the trimmed value
|
||||
const textInput = screen.getByTestId('channel_banner_banner_text_textbox');
|
||||
expect(textInput).toHaveValue('Banner text with whitespace');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -199,9 +199,7 @@ describe('ChannelSettingsInfoTab', () => {
|
|||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
// Click the Save button in the SaveChangesPanel.
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// Verify patchChannel was called with the updated values (without type change).
|
||||
// Note: URL should remain unchanged when editing existing channels
|
||||
|
|
@ -242,9 +240,7 @@ describe('ChannelSettingsInfoTab', () => {
|
|||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
// Click the Save button
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// Verify patchChannel was called with the trimmed values
|
||||
expect(patchChannel).toHaveBeenCalledWith('channel1', {
|
||||
|
|
@ -286,9 +282,7 @@ describe('ChannelSettingsInfoTab', () => {
|
|||
expect(screen.getByRole('button', {name: 'Save'})).toBeInTheDocument();
|
||||
|
||||
// Click the Save button
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// Add a small delay to ensure all state updates are processed
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
|
@ -315,9 +309,7 @@ describe('ChannelSettingsInfoTab', () => {
|
|||
expect(screen.queryByRole('button', {name: 'Save'})).toBeInTheDocument();
|
||||
|
||||
// Click the Reset button.
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Reset'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Reset'}));
|
||||
|
||||
// Form should be reset to original values.
|
||||
expect(screen.getByRole('textbox', {name: 'Channel name'})).toHaveValue('Test Channel');
|
||||
|
|
@ -344,9 +336,7 @@ describe('ChannelSettingsInfoTab', () => {
|
|||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
// Click the Save button.
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// SaveChangesPanel should show 'error' state.
|
||||
const errorMessage = screen.getByText(/There are errors in the form above/);
|
||||
|
|
@ -527,9 +517,7 @@ describe('ChannelSettingsInfoTab', () => {
|
|||
await userEvent.click(privateButton);
|
||||
|
||||
// Click Save button
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// Verify the modal is shown
|
||||
expect(screen.getByTestId('convert-confirm-modal')).toBeInTheDocument();
|
||||
|
|
@ -548,14 +536,10 @@ describe('ChannelSettingsInfoTab', () => {
|
|||
await userEvent.click(privateButton);
|
||||
|
||||
// Click Save button to show modal
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// Click confirm button in modal
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByText(/Yes, Convert Channel/i));
|
||||
});
|
||||
await userEvent.click(screen.getByText(/Yes, Convert Channel/i));
|
||||
|
||||
// Verify updateChannelPrivacy was called
|
||||
expect(updateChannelPrivacy).toHaveBeenCalledWith('channel1', 'P');
|
||||
|
|
@ -574,14 +558,10 @@ describe('ChannelSettingsInfoTab', () => {
|
|||
await userEvent.click(privateButton);
|
||||
|
||||
// Click Save button to show modal
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// Click cancel button in modal
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByText(/Cancel/i));
|
||||
});
|
||||
await userEvent.click(screen.getByText(/Cancel/i));
|
||||
|
||||
// Verify updateChannelPrivacy was not called
|
||||
expect(updateChannelPrivacy).not.toHaveBeenCalled();
|
||||
|
|
@ -603,14 +583,10 @@ describe('ChannelSettingsInfoTab', () => {
|
|||
await userEvent.click(privateButton);
|
||||
|
||||
// Click Save button to show modal
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
});
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
|
||||
// Click confirm button in modal
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByText(/Yes, Convert Channel/i));
|
||||
});
|
||||
await userEvent.click(screen.getByText(/Yes, Convert Channel/i));
|
||||
|
||||
// Verify error state is shown
|
||||
expect(screen.getByText(/There are errors in the form above/)).toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ import {screen, waitFor} from '@testing-library/react';
|
|||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import type {DeepPartial} from '@mattermost/types/utilities';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import ChannelSettingsModal from './channel_settings_modal';
|
||||
|
||||
|
|
@ -18,35 +17,6 @@ let mockPrivateChannelPermission = true;
|
|||
let mockPublicChannelPermission = true;
|
||||
let mockManageChannelAccessRulesPermission = false;
|
||||
|
||||
// Variable to control group-constrained status in tests
|
||||
let mockGroupConstrained = false;
|
||||
|
||||
// Mock the redux selectors
|
||||
jest.mock('mattermost-redux/selectors/entities/channels', () => ({
|
||||
getChannel: jest.fn().mockImplementation((state, channelId) => {
|
||||
// Return a mock channel based on the channelId
|
||||
return {
|
||||
id: channelId,
|
||||
team_id: 'team1',
|
||||
display_name: 'Test Channel',
|
||||
name: channelId === 'default_channel' ? 'town-square' : 'test-channel',
|
||||
purpose: 'Testing purpose',
|
||||
header: 'Channel header',
|
||||
type: mockChannelType, // Use a variable to control the channel type
|
||||
create_at: 0,
|
||||
update_at: 0,
|
||||
delete_at: 0,
|
||||
last_post_at: 0,
|
||||
total_msg_count: 0,
|
||||
extra_update_at: 0,
|
||||
creator_id: 'creator1',
|
||||
last_root_post_at: 0,
|
||||
scheme_id: '',
|
||||
group_constrained: mockGroupConstrained,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock the channel banner selector
|
||||
jest.mock('mattermost-redux/selectors/entities/channel_banner', () => ({
|
||||
selectChannelBannerEnabled: jest.fn().mockImplementation((state) => {
|
||||
|
|
@ -140,9 +110,6 @@ type TabType = {
|
|||
display?: boolean;
|
||||
};
|
||||
|
||||
// Variable to control the channel type in tests
|
||||
let mockChannelType = 'O';
|
||||
|
||||
// Mock the settings sidebar
|
||||
jest.mock('components/settings_sidebar', () => {
|
||||
return function MockSettingsSidebar({tabs, activeTab, updateTab}: {tabs: TabType[]; activeTab: string; updateTab: (tab: string) => void}): JSX.Element {
|
||||
|
|
@ -165,30 +132,58 @@ jest.mock('components/settings_sidebar', () => {
|
|||
};
|
||||
});
|
||||
|
||||
const baseProps = {
|
||||
channelId: 'channel1',
|
||||
isOpen: true,
|
||||
onExited: jest.fn(),
|
||||
focusOriginElement: 'button1',
|
||||
};
|
||||
|
||||
describe('ChannelSettingsModal', () => {
|
||||
const channelId = 'channel1';
|
||||
|
||||
const baseProps = {
|
||||
channelId,
|
||||
isOpen: true,
|
||||
onExited: jest.fn(),
|
||||
focusOriginElement: 'button1',
|
||||
};
|
||||
|
||||
function makeTestState() {
|
||||
return {
|
||||
entities: {
|
||||
channels: {
|
||||
channels: {
|
||||
[channelId]: TestHelper.getChannelMock({
|
||||
id: channelId,
|
||||
type: General.OPEN_CHANNEL,
|
||||
purpose: 'Testing purpose',
|
||||
header: 'Channel header',
|
||||
group_constrained: false,
|
||||
}),
|
||||
},
|
||||
},
|
||||
general: {
|
||||
license: {
|
||||
SkuShortName: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockChannelType = 'O'; // Default to public channel
|
||||
mockPrivateChannelPermission = true;
|
||||
mockPublicChannelPermission = true;
|
||||
mockManageChannelAccessRulesPermission = false; // Default to no access rules permission
|
||||
mockGroupConstrained = false; // Default to not group-constrained
|
||||
});
|
||||
|
||||
it('should render the modal with correct header text', async () => {
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
expect(screen.getByText('Channel Settings')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render Info tab by default', async () => {
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the lazy-loaded components
|
||||
await waitFor(() => {
|
||||
|
|
@ -197,7 +192,9 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should switch tabs when clicked', async () => {
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -216,11 +213,11 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should not show archive tab for default channel', async () => {
|
||||
renderWithContext(
|
||||
<ChannelSettingsModal
|
||||
{...{...baseProps, channelId: 'default_channel'}}
|
||||
/>,
|
||||
);
|
||||
const testState = makeTestState();
|
||||
|
||||
testState.entities.channels.channels[channelId].name = 'town-square';
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -235,10 +232,11 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should show archive tab for public channel when user has permission', async () => {
|
||||
mockChannelType = 'O';
|
||||
mockPublicChannelPermission = true;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -250,10 +248,11 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should not show archive tab for public channel when user does not have permission', async () => {
|
||||
mockChannelType = 'O';
|
||||
mockPublicChannelPermission = false;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -265,10 +264,12 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should show archive tab for private channel when user has permission', async () => {
|
||||
mockChannelType = 'P';
|
||||
mockPrivateChannelPermission = true;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
testState.entities.channels.channels[channelId].type = General.PRIVATE_CHANNEL;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -280,10 +281,12 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should not show archive tab for private channel when user does not have permission', async () => {
|
||||
mockChannelType = 'P';
|
||||
mockPrivateChannelPermission = false;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
testState.entities.channels.channels[channelId].type = General.PRIVATE_CHANNEL;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -295,67 +298,44 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should not show configuration tab with no license', async () => {
|
||||
const baseState: DeepPartial<GlobalState> = {
|
||||
entities: {
|
||||
general: {
|
||||
license: {
|
||||
SkuShortName: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, baseState as GlobalState);
|
||||
const testState = makeTestState();
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
expect(screen.queryByTestId('configuration-tab-button')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show configuration tab with professional license', async () => {
|
||||
const baseState: DeepPartial<GlobalState> = {
|
||||
entities: {
|
||||
general: {
|
||||
license: {
|
||||
SkuShortName: 'professional',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, baseState as GlobalState);
|
||||
const testState = makeTestState();
|
||||
testState.entities.general.license.SkuShortName = 'professional';
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
expect(screen.queryByTestId('configuration-tab-button')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show configuration tab with enterprise license', async () => {
|
||||
const baseState: DeepPartial<GlobalState> = {
|
||||
entities: {
|
||||
general: {
|
||||
license: {
|
||||
SkuShortName: 'enterprise',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, baseState as GlobalState);
|
||||
const testState = makeTestState();
|
||||
testState.entities.general.license.SkuShortName = 'enterprise';
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
expect(screen.queryByTestId('configuration-tab-button')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show configuration tab when enterprise advanced license', async () => {
|
||||
const baseState: DeepPartial<GlobalState> = {
|
||||
entities: {
|
||||
general: {
|
||||
license: {
|
||||
SkuShortName: 'advanced',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, baseState as GlobalState);
|
||||
const testState = makeTestState();
|
||||
testState.entities.general.license.SkuShortName = 'advanced';
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
expect(screen.getByTestId('configuration-tab-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Access Control tab visibility', () => {
|
||||
it('should show Access Control tab for private channel when user has permission', async () => {
|
||||
mockChannelType = 'P';
|
||||
mockManageChannelAccessRulesPermission = true;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
testState.entities.channels.channels[channelId].type = General.PRIVATE_CHANNEL;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -368,10 +348,12 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should not show Access Control tab for private channel when user lacks permission', async () => {
|
||||
mockChannelType = 'P';
|
||||
mockManageChannelAccessRulesPermission = false;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
testState.entities.channels.channels[channelId].type = General.PRIVATE_CHANNEL;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -384,10 +366,11 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should not show Access Control tab for public channel even with permission', async () => {
|
||||
mockChannelType = 'O';
|
||||
mockManageChannelAccessRulesPermission = true;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -400,10 +383,11 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should not show Access Control tab for public channel without permission', async () => {
|
||||
mockChannelType = 'O';
|
||||
mockManageChannelAccessRulesPermission = false;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -416,10 +400,12 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should be able to navigate to Access Control tab when visible', async () => {
|
||||
mockChannelType = 'P';
|
||||
mockManageChannelAccessRulesPermission = true;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
testState.entities.channels.channels[channelId].type = General.PRIVATE_CHANNEL;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -439,10 +425,12 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should show correct tab label as "Access Control"', async () => {
|
||||
mockChannelType = 'P';
|
||||
mockManageChannelAccessRulesPermission = true;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
testState.entities.channels.channels[channelId].type = General.PRIVATE_CHANNEL;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -455,14 +443,13 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should show Access Control tab for default channel if private and user has permission', async () => {
|
||||
mockChannelType = 'P';
|
||||
mockManageChannelAccessRulesPermission = true;
|
||||
|
||||
renderWithContext(
|
||||
<ChannelSettingsModal
|
||||
{...{...baseProps, channelId: 'default_channel'}}
|
||||
/>,
|
||||
);
|
||||
const testState = makeTestState();
|
||||
testState.entities.channels.channels[channelId].name = 'town-square';
|
||||
testState.entities.channels.channels[channelId].type = General.PRIVATE_CHANNEL;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -474,11 +461,13 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should not show Access Control tab for group-constrained private channel even with permission', async () => {
|
||||
mockChannelType = 'P';
|
||||
mockManageChannelAccessRulesPermission = true;
|
||||
mockGroupConstrained = true;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
testState.entities.channels.channels[channelId].type = General.PRIVATE_CHANNEL;
|
||||
testState.entities.channels.channels[channelId].group_constrained = true;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -491,11 +480,13 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should not show Access Control tab for group-constrained private channel without permission', async () => {
|
||||
mockChannelType = 'P';
|
||||
mockManageChannelAccessRulesPermission = false;
|
||||
mockGroupConstrained = true;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
testState.entities.channels.channels[channelId].type = General.PRIVATE_CHANNEL;
|
||||
testState.entities.channels.channels[channelId].group_constrained = true;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -508,11 +499,12 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should not show Access Control tab for group-constrained public channel', async () => {
|
||||
mockChannelType = 'O';
|
||||
mockManageChannelAccessRulesPermission = true;
|
||||
mockGroupConstrained = true;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
const testState = makeTestState();
|
||||
testState.entities.channels.channels[channelId].group_constrained = true;
|
||||
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, testState);
|
||||
|
||||
// Wait for the sidebar to load
|
||||
await waitFor(() => {
|
||||
|
|
@ -531,7 +523,7 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should close immediately when no unsaved changes exist', async () => {
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, makeTestState());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
|
|
@ -546,7 +538,7 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should prevent close on first attempt with unsaved changes', async () => {
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, makeTestState());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
|
|
@ -571,7 +563,7 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should allow close on second attempt (warn-once behavior)', async () => {
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, makeTestState());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
|
|
@ -598,7 +590,7 @@ describe('ChannelSettingsModal', () => {
|
|||
});
|
||||
|
||||
it('should reset warning state when changes are saved', async () => {
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>);
|
||||
renderWithContext(<ChannelSettingsModal {...baseProps}/>, makeTestState());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -9,30 +9,7 @@ exports[`components/channel_view Should match snapshot if channel is archived 1`
|
|||
id="centerChannelFileDropOverlay"
|
||||
overlayType="center"
|
||||
/>
|
||||
<ChannelHeader
|
||||
canRestrictDirectMessage={false}
|
||||
channelId="channelId"
|
||||
channelIsArchived={true}
|
||||
deactivatedChannel={false}
|
||||
enableOnboardingFlow={true}
|
||||
enableWebSocketEventScope={false}
|
||||
fetchIsRestrictedDM={[MockFunction]}
|
||||
goToLastViewedChannel={[MockFunction]}
|
||||
history={Object {}}
|
||||
isChannelBookmarksEnabled={false}
|
||||
isCloud={false}
|
||||
isFirstAdmin={false}
|
||||
location={Object {}}
|
||||
match={
|
||||
Object {
|
||||
"params": Object {},
|
||||
"url": "/team/channel/channelId",
|
||||
}
|
||||
}
|
||||
missingChannelRole={false}
|
||||
restrictDirectMessage={false}
|
||||
teamUrl="/team"
|
||||
/>
|
||||
<ChannelHeader />
|
||||
<ChannelBanner
|
||||
channelId="channelId"
|
||||
/>
|
||||
|
|
@ -79,30 +56,7 @@ exports[`components/channel_view Should match snapshot if channel is deactivated
|
|||
id="centerChannelFileDropOverlay"
|
||||
overlayType="center"
|
||||
/>
|
||||
<ChannelHeader
|
||||
canRestrictDirectMessage={false}
|
||||
channelId="channelId"
|
||||
channelIsArchived={false}
|
||||
deactivatedChannel={true}
|
||||
enableOnboardingFlow={true}
|
||||
enableWebSocketEventScope={false}
|
||||
fetchIsRestrictedDM={[MockFunction]}
|
||||
goToLastViewedChannel={[MockFunction]}
|
||||
history={Object {}}
|
||||
isChannelBookmarksEnabled={false}
|
||||
isCloud={false}
|
||||
isFirstAdmin={false}
|
||||
location={Object {}}
|
||||
match={
|
||||
Object {
|
||||
"params": Object {},
|
||||
"url": "/team/channel/channelId",
|
||||
}
|
||||
}
|
||||
missingChannelRole={false}
|
||||
restrictDirectMessage={false}
|
||||
teamUrl="/team"
|
||||
/>
|
||||
<ChannelHeader />
|
||||
<ChannelBanner
|
||||
channelId="channelId"
|
||||
/>
|
||||
|
|
@ -148,30 +102,7 @@ exports[`components/channel_view Should match snapshot with base props 1`] = `
|
|||
id="centerChannelFileDropOverlay"
|
||||
overlayType="center"
|
||||
/>
|
||||
<ChannelHeader
|
||||
canRestrictDirectMessage={false}
|
||||
channelId="channelId"
|
||||
channelIsArchived={false}
|
||||
deactivatedChannel={false}
|
||||
enableOnboardingFlow={true}
|
||||
enableWebSocketEventScope={false}
|
||||
fetchIsRestrictedDM={[MockFunction]}
|
||||
goToLastViewedChannel={[MockFunction]}
|
||||
history={Object {}}
|
||||
isChannelBookmarksEnabled={false}
|
||||
isCloud={false}
|
||||
isFirstAdmin={false}
|
||||
location={Object {}}
|
||||
match={
|
||||
Object {
|
||||
"params": Object {},
|
||||
"url": "/team/channel/channelId",
|
||||
}
|
||||
}
|
||||
missingChannelRole={false}
|
||||
restrictDirectMessage={false}
|
||||
teamUrl="/team"
|
||||
/>
|
||||
<ChannelHeader />
|
||||
<ChannelBanner
|
||||
channelId="channelId"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ export default class ChannelView extends React.PureComponent<Props, State> {
|
|||
id='channelView.archivedChannelWithDeactivatedUser'
|
||||
defaultMessage='You are viewing an archived channel with a <b>deactivated user</b>. New messages cannot be posted.'
|
||||
values={{
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
|
|
@ -151,7 +151,7 @@ export default class ChannelView extends React.PureComponent<Props, State> {
|
|||
id='channelView.archivedChannel'
|
||||
defaultMessage='You are viewing an <b>archived channel</b>. New messages cannot be posted.'
|
||||
values={{
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
|
|
@ -180,7 +180,7 @@ export default class ChannelView extends React.PureComponent<Props, State> {
|
|||
id='channelView.noSharedTeam'
|
||||
defaultMessage='You no longer have any teams in common with this user. New messages cannot be posted.'
|
||||
values={{
|
||||
b: (chunks: string) => <b>{chunks}</b>,
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
|
|
@ -221,7 +221,7 @@ export default class ChannelView extends React.PureComponent<Props, State> {
|
|||
overlayType='center'
|
||||
id={DropOverlayIdCenterChannel}
|
||||
/>
|
||||
<ChannelHeader {...this.props}/>
|
||||
<ChannelHeader/>
|
||||
{this.props.isChannelBookmarksEnabled && <ChannelBookmarks channelId={this.props.channelId}/>}
|
||||
<ChannelBanner channelId={this.props.channelId}/>
|
||||
<DeferredPostView
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ function CloudInvoicePreview(props: Props) {
|
|||
<FormattedMessage
|
||||
id='cloud.invoice_pdf_preview.download'
|
||||
values={{
|
||||
downloadLink: (msg: string) => (
|
||||
downloadLink: (msg) => (
|
||||
<ExternalLink
|
||||
href={props.url || ''}
|
||||
location='cloud_invoice_preview'
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue