From 0a3bb8fdb10f2ce72e5e975a35fc7d22637265f9 Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Tue, 4 Jul 2017 08:00:17 +0100 Subject: Refactor system console buttons into RequestButton component. (#6808) Since I was going to make yet another button for the ElasticSearch test config button, I refactored all of them to use a single common component and tidied that component up and gave it some unit tests. --- .../admin_console/configuration_settings.jsx | 76 +++- .../components/admin_console/database_settings.jsx | 52 ++- webapp/components/admin_console/ldap_settings.jsx | 50 ++- .../components/admin_console/ldap_test_button.jsx | 146 ------ webapp/components/admin_console/purge_caches.jsx | 108 ----- webapp/components/admin_console/recycle_db.jsx | 111 ----- webapp/components/admin_console/reload_config.jsx | 111 ----- .../request_button/request_button.jsx | 234 ++++++++++ .../components/admin_console/sync_now_button.jsx | 115 ----- webapp/i18n/en.json | 11 +- .../__snapshots__/request_button.test.jsx.snap | 496 +++++++++++++++++++++ .../request_button/request_button.test.jsx | 215 +++++++++ 12 files changed, 1121 insertions(+), 604 deletions(-) delete mode 100644 webapp/components/admin_console/ldap_test_button.jsx delete mode 100644 webapp/components/admin_console/purge_caches.jsx delete mode 100644 webapp/components/admin_console/recycle_db.jsx delete mode 100644 webapp/components/admin_console/reload_config.jsx create mode 100644 webapp/components/admin_console/request_button/request_button.jsx delete mode 100644 webapp/components/admin_console/sync_now_button.jsx create mode 100644 webapp/tests/components/admin_console/request_button/__snapshots__/request_button.test.jsx.snap create mode 100644 webapp/tests/components/admin_console/request_button/request_button.test.jsx diff --git a/webapp/components/admin_console/configuration_settings.jsx b/webapp/components/admin_console/configuration_settings.jsx index 449b4f549..72bd0e330 100644 --- a/webapp/components/admin_console/configuration_settings.jsx +++ b/webapp/components/admin_console/configuration_settings.jsx @@ -9,12 +9,12 @@ import ErrorStore from 'stores/error_store.jsx'; import {ErrorBarTypes} from 'utils/constants.jsx'; import * as Utils from 'utils/utils.jsx'; +import {invalidateAllCaches, reloadConfig} from 'actions/admin_actions.jsx'; import AdminSettings from './admin_settings.jsx'; import BooleanSetting from './boolean_setting.jsx'; import {ConnectionSecurityDropdownSettingWebserver} from './connection_security_dropdown_setting.jsx'; -import PurgeCachesButton from './purge_caches.jsx'; -import ReloadConfigButton from './reload_config.jsx'; import SettingsGroup from './settings_group.jsx'; +import RequestButton from './request_button/request_button'; import TextSetting from './text_setting.jsx'; import WebserverModeDropdownSetting from './webserver_mode_dropdown_setting.jsx'; @@ -83,6 +83,54 @@ export default class ConfigurationSettings extends AdminSettings { } renderSettings() { + const reloadConfigurationHelpText = ( + + + + ), + recycleDatabaseConnections: ( + + + + + + ) + }} + /> + ); + + let reloadConfigButton =
; + if (global.window.mm_license.IsLicensed === 'true') { + reloadConfigButton = ( + + } + showSuccessMessage={false} + errorMessage={{ + id: 'admin.reload.reloadFail', + defaultMessage: 'Reload unsuccessful: {error}' + }} + /> + ); + } + return (
@@ -261,8 +309,28 @@ export default class ConfigurationSettings extends AdminSettings { onChange={this.handleChange} disabled={false} /> - - + {reloadConfigButton} + + } + buttonText={ + + } + showSuccessMessage={false} + includeDetailedError={true} + errorMessage={{ + id: 'admin.purge.purgeFail', + defaultMessage: 'Purging unsuccessful: {error}' + }} + /> ); } diff --git a/webapp/components/admin_console/database_settings.jsx b/webapp/components/admin_console/database_settings.jsx index e182db70e..303865d91 100644 --- a/webapp/components/admin_console/database_settings.jsx +++ b/webapp/components/admin_console/database_settings.jsx @@ -3,6 +3,7 @@ import React from 'react'; +import {recycleDatabaseConnection} from 'actions/admin_actions.jsx'; import * as Utils from 'utils/utils.jsx'; import AdminSettings from './admin_settings.jsx'; @@ -11,7 +12,7 @@ import {FormattedMessage} from 'react-intl'; import GeneratedSetting from './generated_setting.jsx'; import SettingsGroup from './settings_group.jsx'; import TextSetting from './text_setting.jsx'; -import RecycleDbButton from './recycle_db.jsx'; +import RequestButton from './request_button/request_button.jsx'; export default class DatabaseSettings extends AdminSettings { constructor(props) { @@ -58,6 +59,53 @@ export default class DatabaseSettings extends AdminSettings { renderSettings() { const dataSource = '**********' + this.state.dataSource.substring(this.state.dataSource.indexOf('@')); + let recycleDbButton =
; + if (global.window.mm_license.IsLicensed === 'true') { + recycleDbButton = ( + + + + ), + reloadConfiguration: ( + + + + + + ) + }} + /> + } + buttonText={ + + } + showSuccessMessage={false} + errorMessage={{ + id: 'admin.recycle.reloadFail', + defaultMessage: 'Recycling unsuccessful: {error}' + }} + includeDetailedError={true} + /> + ); + } + return (

@@ -183,7 +231,7 @@ export default class DatabaseSettings extends AdminSettings { value={this.state.trace} onChange={this.handleChange} /> - + {recycleDbButton} ); } diff --git a/webapp/components/admin_console/ldap_settings.jsx b/webapp/components/admin_console/ldap_settings.jsx index 87eab8004..9ffbe3b0e 100644 --- a/webapp/components/admin_console/ldap_settings.jsx +++ b/webapp/components/admin_console/ldap_settings.jsx @@ -7,13 +7,13 @@ import {ConnectionSecurityDropdownSettingLdap} from './connection_security_dropd import SettingsGroup from './settings_group.jsx'; import TextSetting from './text_setting.jsx'; -import SyncNowButton from './sync_now_button.jsx'; -import LdapTestButton from './ldap_test_button.jsx'; +import {ldapSyncNow, ldapTest} from 'actions/admin_actions.jsx'; import * as Utils from 'utils/utils.jsx'; import React from 'react'; import {FormattedMessage} from 'react-intl'; +import RequestButton from './request_button/request_button.jsx'; export default class LdapSettings extends AdminSettings { constructor(props) { @@ -450,13 +450,53 @@ export default class LdapSettings extends AdminSettings { onChange={this.handleChange} disabled={!this.state.enable} /> - + } + buttonText={ + + } disabled={!this.state.enable} + showSuccessMessage={false} + errorMessage={{ + id: 'admin.ldap.syncFailure', + defaultMessage: 'Sync Failure: {error}' + }} + includeDetailedError={true} /> - + } + buttonText={ + + } disabled={!this.state.enable} - submitFunction={this.doSubmit} saveNeeded={this.state.saveNeeded} + saveConfigAction={this.doSubmit} + errorMessage={{ + id: 'admin.ldap.testFailure', + defaultMessage: 'AD/LDAP Test Failure: {error}' + }} + successMessage={{ + id: 'admin.ldap.testSuccess', + defaultMessage: 'AD/LDAP Test Successful' + }} /> ); diff --git a/webapp/components/admin_console/ldap_test_button.jsx b/webapp/components/admin_console/ldap_test_button.jsx deleted file mode 100644 index e785d0f78..000000000 --- a/webapp/components/admin_console/ldap_test_button.jsx +++ /dev/null @@ -1,146 +0,0 @@ -import PropTypes from 'prop-types'; - -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import * as Utils from 'utils/utils.jsx'; - -import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; - -import {ldapTest} from 'actions/admin_actions.jsx'; - -export default class LdapTestButton extends React.Component { - static get propTypes() { - return { - disabled: PropTypes.bool, - submitFunction: PropTypes.func, - saveNeeded: PropTypes.bool - }; - } - constructor(props) { - super(props); - - this.handleLdapTest = this.handleLdapTest.bind(this); - - this.state = { - buisy: false, - fail: null, - success: false - }; - } - - handleLdapTest(e) { - e.preventDefault(); - - this.setState({ - buisy: true, - fail: null, - success: false - }); - - const doRequest = () => { //eslint-disable-line func-style - ldapTest( - () => { - this.setState({ - buisy: false, - success: true - }); - }, - (err) => { - this.setState({ - buisy: false, - fail: err.message - }); - } - ); - }; - - // If we need to run the save function then run it with our request function as callback - if (this.props.saveNeeded) { - this.props.submitFunction(doRequest); - } else { - doRequest(); - } - } - - render() { - let message = null; - if (this.state.fail) { - message = ( -

-
- - -
-
- ); - } else if (this.state.success) { - message = ( -
-
- - -
-
- ); - } - - const helpText = ( - - ); - - let contents = null; - if (this.state.loading) { - contents = ( - - - {Utils.localizeMessage('admin.reload.loading', ' Loading...')} - - ); - } else { - contents = ( - - ); - } - - return ( -
-
-
- - {message} -
-
- {helpText} -
-
-
- ); - } -} diff --git a/webapp/components/admin_console/purge_caches.jsx b/webapp/components/admin_console/purge_caches.jsx deleted file mode 100644 index d2337d587..000000000 --- a/webapp/components/admin_console/purge_caches.jsx +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import {FormattedMessage} from 'react-intl'; - -import {invalidateAllCaches} from 'actions/admin_actions.jsx'; - -export default class PurgeCachesButton extends React.Component { - constructor(props) { - super(props); - - this.handlePurge = this.handlePurge.bind(this); - - this.state = { - loading: false, - fail: null - }; - } - - handlePurge(e) { - e.preventDefault(); - - this.setState({ - loading: true, - fail: null - }); - - invalidateAllCaches( - () => { - this.setState({ - loading: false - }); - }, - (err) => { - this.setState({ - loading: false, - fail: err.message + ' - ' + err.detailed_error - }); - } - ); - } - - render() { - let testMessage = null; - if (this.state.fail) { - testMessage = ( -
- - -
- ); - } - - const helpText = ( - - ); - - let contents = null; - if (this.state.loading) { - contents = ( - - - - - ); - } else { - contents = ( - - ); - } - - return ( -
-
-
- - {testMessage} -
-
- {helpText} -
-
-
- ); - } -} diff --git a/webapp/components/admin_console/recycle_db.jsx b/webapp/components/admin_console/recycle_db.jsx deleted file mode 100644 index 5e536d908..000000000 --- a/webapp/components/admin_console/recycle_db.jsx +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import * as Utils from 'utils/utils.jsx'; - -import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; - -import {recycleDatabaseConnection} from 'actions/admin_actions.jsx'; - -export default class RecycleDbButton extends React.Component { - constructor(props) { - super(props); - - this.handleRecycle = this.handleRecycle.bind(this); - - this.state = { - loading: false, - fail: null - }; - } - - handleRecycle(e) { - e.preventDefault(); - - this.setState({ - loading: true, - fail: null - }); - - recycleDatabaseConnection( - () => { - this.setState({ - loading: false - }); - }, - (err) => { - this.setState({ - loading: false, - fail: err.message + ' - ' + err.detailed_error - }); - } - ); - } - - render() { - if (global.window.mm_license.IsLicensed !== 'true') { - return
; - } - - let testMessage = null; - if (this.state.fail) { - testMessage = ( -
- - -
- ); - } - - const helpText = ( - - ); - - let contents = null; - if (this.state.loading) { - contents = ( - - - {Utils.localizeMessage('admin.recycle.loading', ' Recycling...')} - - ); - } else { - contents = ( - - ); - } - - return ( -
-
-
- - {testMessage} -
-
- {helpText} -
-
-
- ); - } -} diff --git a/webapp/components/admin_console/reload_config.jsx b/webapp/components/admin_console/reload_config.jsx deleted file mode 100644 index ad3d9cca7..000000000 --- a/webapp/components/admin_console/reload_config.jsx +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import * as Utils from 'utils/utils.jsx'; - -import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; - -import {reloadConfig} from 'actions/admin_actions.jsx'; - -export default class ReloadConfigButton extends React.Component { - constructor(props) { - super(props); - - this.handleReloadConfig = this.handleReloadConfig.bind(this); - - this.state = { - loading: false, - fail: null - }; - } - - handleReloadConfig(e) { - e.preventDefault(); - - this.setState({ - loading: true, - fail: null - }); - - reloadConfig( - () => { - this.setState({ - loading: false - }); - }, - (err) => { - this.setState({ - loading: false, - fail: err.message + ' - ' + err.detailed_error - }); - } - ); - } - - render() { - if (global.window.mm_license.IsLicensed !== 'true') { - return
; - } - - let testMessage = null; - if (this.state.fail) { - testMessage = ( -
- - -
- ); - } - - const helpText = ( - - ); - - let contents = null; - if (this.state.loading) { - contents = ( - - - {Utils.localizeMessage('admin.reload.loading', ' Loading...')} - - ); - } else { - contents = ( - - ); - } - - return ( -
-
-
- - {testMessage} -
-
- {helpText} -
-
-
- ); - } -} diff --git a/webapp/components/admin_console/request_button/request_button.jsx b/webapp/components/admin_console/request_button/request_button.jsx new file mode 100644 index 000000000..4aad287d2 --- /dev/null +++ b/webapp/components/admin_console/request_button/request_button.jsx @@ -0,0 +1,234 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import {FormattedMessage} from 'react-intl'; +import PropTypes from 'prop-types'; + +import * as Utils from 'utils/utils.jsx'; + +/** + * A button which, when clicked, performs an action and displays + * its outcome as either success, or failure accompanied by the + * `message` property of the `err` object. + */ +export default class RequestButton extends React.Component { + static propTypes = { + + /** + * The action to be called to carry out the request. + */ + requestAction: PropTypes.func.isRequired, + + /** + * A component that displays help text for the request button. + * + * Typically, this will be a . + */ + helpText: PropTypes.element.isRequired, + + /** + * A component to be displayed on the button. + * + * Typically, this will be a + */ + buttonText: PropTypes.element.isRequired, + + /** + * True if the button form control should be disabled, otherwise false. + */ + disabled: PropTypes.bool, + + /** + * True if the config needs to be saved before running the request, otherwise false. + * + * If set to true, the action provided in the `saveConfigAction` property will be + * called before the action provided in the `requestAction` property, with the later + * only being called if the former is successful. + */ + saveNeeded: PropTypes.bool, + + /** + * Action to be called to save the config, if saveNeeded is set to true. + */ + saveConfigAction: PropTypes.func, + + /** + * True if the success message should be show when the request completes successfully, + * otherwise false. + */ + showSuccessMessage: PropTypes.bool, + + /** + * The message to show when the request completes successfully. + */ + successMessage: PropTypes.shape({ + + /** + * The i18n string ID for the success message. + */ + id: PropTypes.string.isRequired, + + /** + * The i18n default value for the success message. + */ + defaultMessage: PropTypes.string.isRequired + }), + + /** + * The message to show when the request returns an error. + */ + errorMessage: PropTypes.shape({ + + /** + * The i18n string ID for the error message. + */ + id: PropTypes.string.isRequired, + + /** + * The i18n default value for the error message. + * + * The placeholder {error} may be used to include the error message returned + * by the server in response to the failed request. + */ + defaultMessage: PropTypes.string.isRequired + }), + + /** + * True if the {error} placeholder for the `errorMessage` property should include both + * the `message` and `detailed_error` properties of the error returned from the server, + * otherwise false to include only the `message` property. + */ + includeDetailedError: PropTypes.bool + } + + static defaultProps = { + disabled: false, + saveNeeded: false, + showSuccessMessage: true, + includeDetailedError: false, + successMessage: { + id: 'admin.requestButton.requestSuccess', + defaultMessage: 'Test Successful' + }, + errorMessage: { + id: 'admin.requestButton.requestFailure', + defaultMessage: 'Test Failure: {error}' + } + } + + constructor(props) { + super(props); + + this.handleRequest = this.handleRequest.bind(this); + + this.state = { + busy: false, + fail: null, + success: false + }; + } + + handleRequest(e) { + e.preventDefault(); + + this.setState({ + busy: true, + fail: null, + success: false + }); + + const doRequest = () => { //eslint-disable-line func-style + this.props.requestAction( + () => { + this.setState({ + busy: false, + success: true + }); + }, + (err) => { + let errMsg = err.message; + if (this.props.includeDetailedError) { + errMsg += ' - ' + err.detailed_error; + } + + this.setState({ + busy: false, + fail: errMsg + }); + } + ); + }; + + if (this.props.saveNeeded) { + this.props.saveConfigAction(doRequest); + } else { + doRequest(); + } + } + + render() { + let message = null; + if (this.state.fail) { + message = ( +
+
+ + +
+
+ ); + } else if (this.state.success && this.props.showSuccessMessage) { + message = ( +
+
+ + +
+
+ ); + } + + let contents = null; + if (this.state.busy) { + contents = ( + + + {Utils.localizeMessage('admin.requestButton.loading', ' Loading...')} + + ); + } else { + contents = this.props.buttonText; + } + + return ( +
+
+
+ + {message} +
+
+ {this.props.helpText} +
+
+
+ ); + } +} diff --git a/webapp/components/admin_console/sync_now_button.jsx b/webapp/components/admin_console/sync_now_button.jsx deleted file mode 100644 index b2a5a001d..000000000 --- a/webapp/components/admin_console/sync_now_button.jsx +++ /dev/null @@ -1,115 +0,0 @@ -import PropTypes from 'prop-types'; - -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import * as Utils from 'utils/utils.jsx'; - -import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; - -import {ldapSyncNow} from 'actions/admin_actions.jsx'; - -export default class SyncNowButton extends React.Component { - static get propTypes() { - return { - disabled: PropTypes.bool - }; - } - constructor(props) { - super(props); - - this.handleSyncNow = this.handleSyncNow.bind(this); - - this.state = { - buisy: false, - fail: null - }; - } - - handleSyncNow(e) { - e.preventDefault(); - - this.setState({ - buisy: true, - fail: null - }); - - ldapSyncNow( - () => { - this.setState({ - buisy: false - }); - }, - (err) => { - this.setState({ - buisy: false, - fail: err.message + ' - ' + err.detailed_error - }); - } - ); - } - - render() { - let failMessage = null; - if (this.state.fail) { - failMessage = ( -
- - -
- ); - } - - const helpText = ( - - ); - - let contents = null; - if (this.state.loading) { - contents = ( - - - {Utils.localizeMessage('admin.reload.loading', ' Loading...')} - - ); - } else { - contents = ( - - ); - } - - return ( -
-
-
- - {failMessage} -
-
- {helpText} -
-
-
- ); - } -} diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 5416cd206..037809d1a 100755 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -601,13 +601,20 @@ "admin.rate.title": "Rate Limit Settings", "admin.recycle.button": "Recycle Database Connections", "admin.recycle.loading": " Recycling...", - "admin.recycle.recycleDescription": "Deployments using multiple databases can switch from one master database to another without restarting the Mattermost server by updating \"config.json\" to the new desired configuration and using the Configuration > Reload Configuration from Disk feature to load the new settings while the server is running. The administrator should then use Recycle Database Connections feature to recycle the database connections based on the new settings.", + "admin.recycle.recycleDescription": "Deployments using multiple databases can switch from one master database to another without restarting the Mattermost server by updating \"config.json\" to the new desired configuration and using the {reloadConfiguration} feature to load the new settings while the server is running. The administrator should then use {featureName} feature to recycle the database connections based on the new settings.", + "admin.recycle.recycleDescription.featureName": "Recycle Database Connections", + "admin.recycle.recycleDescription.reloadConfiguration": "Configuration > Reload Configuration from Disk", "admin.recycle.reloadFail": "Recycling unsuccessful: {error}", "admin.regenerate": "Regenerate", "admin.reload.button": "Reload Configuration From Disk", "admin.reload.loading": " Loading...", - "admin.reload.reloadDescription": "Deployments using multiple databases can switch from one master database to another without restarting the Mattermost server by updating \"config.json\" to the new desired configuration and using the Reload Configuration from Disk feature to load the new settings while the server is running. The administrator should then use the Database > Recycle Database Connections feature to recycle the database connections based on the new settings.", + "admin.reload.reloadDescription": "Deployments using multiple databases can switch from one master database to another without restarting the Mattermost server by updating \"config.json\" to the new desired configuration and using the {featureName} feature to load the new settings while the server is running. The administrator should then use the {recycleDatabaseConnections} feature to recycle the database connections based on the new settings.", + "admin.reload.reloadDescription.featureName": "Reload Configuration from Disk", + "admin.reload.reloadDescription.recycleDatabaseConnections": "Database > Recycle Database Connections", "admin.reload.reloadFail": "Reloading unsuccessful: {error}", + "admin.requestButton.loading": " Loading...", + "admin.requestButton.requestSuccess": "Test Successful", + "admin.requestButton.requestFailure": "Test Failure: {error}", "admin.reset_password.close": "Close", "admin.reset_password.newPassword": "New Password", "admin.reset_password.select": "Select", diff --git a/webapp/tests/components/admin_console/request_button/__snapshots__/request_button.test.jsx.snap b/webapp/tests/components/admin_console/request_button/__snapshots__/request_button.test.jsx.snap new file mode 100644 index 000000000..108384950 --- /dev/null +++ b/webapp/tests/components/admin_console/request_button/__snapshots__/request_button.test.jsx.snap @@ -0,0 +1,496 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/admin_console/request_button/request_button.jsx should match snapshot 1`] = ` +
+
+
+ +
+
+ +
+
+
+`; + +exports[`components/admin_console/request_button/request_button.jsx should match snapshot with request error 1`] = ` + + } + disabled={false} + errorMessage={ + Object { + "defaultMessage": "Error Message: {error}", + "id": "error.message", + } + } + helpText={ + + } + includeDetailedError={true} + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object {}, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "textComponent": "span", + } + } + requestAction={[Function]} + saveNeeded={false} + showSuccessMessage={true} + successMessage={ + Object { + "defaultMessage": "Test Successful", + "id": "admin.requestButton.requestSuccess", + } + } +> +
+
+
+ +
+
+ + + + Error Message: __message__ - __detailed_error__ + + +
+
+
+
+ + + Help Text + + +
+
+
+
+`; + +exports[`components/admin_console/request_button/request_button.jsx should match snapshot with request error 2`] = ` + + } + disabled={false} + errorMessage={ + Object { + "defaultMessage": "Error Message: {error}", + "id": "error.message", + } + } + helpText={ + + } + includeDetailedError={false} + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object {}, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "textComponent": "span", + } + } + requestAction={[Function]} + saveNeeded={false} + showSuccessMessage={true} + successMessage={ + Object { + "defaultMessage": "Test Successful", + "id": "admin.requestButton.requestSuccess", + } + } +> +
+
+
+ +
+
+ + + + Error Message: __message__ + + +
+
+
+
+ + + Help Text + + +
+
+
+
+`; + +exports[`components/admin_console/request_button/request_button.jsx should match snapshot with successMessage 1`] = ` + + } + disabled={false} + errorMessage={ + Object { + "defaultMessage": "Test Failure: {error}", + "id": "admin.requestButton.requestFailure", + } + } + helpText={ + + } + includeDetailedError={false} + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object {}, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "textComponent": "span", + } + } + requestAction={[Function]} + saveNeeded={false} + showSuccessMessage={true} + successMessage={ + Object { + "defaultMessage": "Success Message", + "id": "success.message", + } + } +> +
+
+
+ +
+
+ + + + Success Message + + +
+
+
+
+ + + Help Text + + +
+
+
+
+`; + +exports[`components/admin_console/request_button/request_button.jsx should match snapshot with successMessage 2`] = ` + + } + disabled={false} + errorMessage={ + Object { + "defaultMessage": "Test Failure: {error}", + "id": "admin.requestButton.requestFailure", + } + } + helpText={ + + } + includeDetailedError={false} + intl={ + Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object {}, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "textComponent": "span", + } + } + requestAction={[Function]} + saveNeeded={false} + showSuccessMessage={false} + successMessage={ + Object { + "defaultMessage": "Success Message", + "id": "success.message", + } + } +> +
+
+
+ +
+
+ + + Help Text + + +
+
+
+
+`; diff --git a/webapp/tests/components/admin_console/request_button/request_button.test.jsx b/webapp/tests/components/admin_console/request_button/request_button.test.jsx new file mode 100644 index 000000000..3b3f4b40b --- /dev/null +++ b/webapp/tests/components/admin_console/request_button/request_button.test.jsx @@ -0,0 +1,215 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {FormattedMessage} from 'react-intl'; + +import {shallow} from 'enzyme'; +import {mountWithIntl} from 'tests/helpers/intl-test-helper.jsx'; + +import RequestButton from 'components/admin_console/request_button/request_button.jsx'; + +describe('components/admin_console/request_button/request_button.jsx', () => { + test('should match snapshot', () => { + const emptyFunction = jest.fn(); + + const wrapper = shallow( + + } + buttonText={ + + } + /> + ); + expect(wrapper).toMatchSnapshot(); + }); + + test('should call saveConfig and request actions when saveNeeded is true', () => { + const requestActionSuccess = jest.fn((success) => success()); + const saveConfigActionSuccess = jest.fn((success) => success()); + + const wrapper = mountWithIntl( + + } + buttonText={ + + } + saveNeeded={false} + saveConfigAction={saveConfigActionSuccess} + /> + ); + + wrapper.find('button').first().simulate('click'); + + expect(requestActionSuccess.mock.calls.length).toBe(1); + expect(saveConfigActionSuccess.mock.calls.length).toBe(0); + }); + + test('should call only request action when saveNeeded is false', () => { + const requestActionSuccess = jest.fn((success) => success()); + const saveConfigActionSuccess = jest.fn((success) => success()); + + const wrapper = mountWithIntl( + + } + buttonText={ + + } + saveNeeded={true} + saveConfigAction={saveConfigActionSuccess} + /> + ); + + wrapper.find('button').first().simulate('click'); + + expect(requestActionSuccess.mock.calls.length).toBe(1); + expect(saveConfigActionSuccess.mock.calls.length).toBe(1); + }); + + test('should match snapshot with successMessage', () => { + const requestActionSuccess = jest.fn((success) => success()); + + // Success & showSuccessMessage=true + const wrapper1 = mountWithIntl( + + } + buttonText={ + + } + showSuccessMessage={true} + successMessage={{ + id: 'success.message', + defaultMessage: 'Success Message' + }} + /> + ); + + wrapper1.find('button').first().simulate('click'); + expect(wrapper1).toMatchSnapshot(); + + // Success & showSuccessMessage=false + const wrapper2 = mountWithIntl( + + } + buttonText={ + + } + showSuccessMessage={false} + successMessage={{ + id: 'success.message', + defaultMessage: 'Success Message' + }} + /> + ); + + wrapper2.find('button').first().simulate('click'); + + expect(wrapper2).toMatchSnapshot(); + }); + + test('should match snapshot with request error', () => { + const requestActionFailure = jest.fn((success, error) => error({ + message: '__message__', + detailed_error: '__detailed_error__' + })); + + // Error & includeDetailedError=true + const wrapper1 = mountWithIntl( + + } + buttonText={ + + } + includeDetailedError={true} + errorMessage={{ + id: 'error.message', + defaultMessage: 'Error Message: {error}' + }} + /> + ); + + wrapper1.find('button').first().simulate('click'); + expect(wrapper1).toMatchSnapshot(); + + // Error & includeDetailedError=false + const wrapper2 = mountWithIntl( + + } + buttonText={ + + } + errorMessage={{ + id: 'error.message', + defaultMessage: 'Error Message: {error}' + }} + /> + ); + + wrapper2.find('button').first().simulate('click'); + + expect(wrapper2).toMatchSnapshot(); + }); +}); -- cgit v1.2.3-1-g7c22