aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2018-02-14 10:51:22 +0100
committerGitHub <noreply@github.com>2018-02-14 10:51:22 +0100
commit8053754d961994e78ed973d37005bb6b5e8ceeae (patch)
tree42dce69c3ca726158e88e95544846449c84f4469 /server/sonar-web/src/main/js
parent94a57989f8160badd5c1e7cbb66fdbceaa3bb38d (diff)
downloadsonarqube-8053754d961994e78ed973d37005bb6b5e8ceeae.tar.gz
sonarqube-8053754d961994e78ed973d37005bb6b5e8ceeae.zip
rewrite maintenance app in react (#3055)
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/api/system.ts14
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/components/App.tsx285
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/components/__tests__/App-test.tsx134
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/components/__tests__/__snapshots__/App-test.tsx.snap399
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/init.js37
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/main-view.js95
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/routes.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-failed.hbs2
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-not-supported.hbs2
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-required.hbs7
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-running.hbs11
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-succeeded.hbs4
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-no-migration.hbs4
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-down.hbs5
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-migration.hbs3
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-offline.hbs5
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-starting.hbs2
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-up.hbs5
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/templates/maintenance-main.hbs32
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx24
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js8
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js8
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js8
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js11
-rw-r--r--server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/components/lazyLoad.tsx12
-rw-r--r--server/sonar-web/src/main/js/helpers/testUtils.ts5
43 files changed, 927 insertions, 352 deletions
diff --git a/server/sonar-web/src/main/js/api/system.ts b/server/sonar-web/src/main/js/api/system.ts
index 794000c0389..597669a2dab 100644
--- a/server/sonar-web/src/main/js/api/system.ts
+++ b/server/sonar-web/src/main/js/api/system.ts
@@ -76,7 +76,7 @@ export function getSystemInfo(): Promise<SysInfo> {
return getJSON('/api/system/info').catch(throwGlobalError);
}
-export function getSystemStatus(): Promise<any> {
+export function getSystemStatus(): Promise<{ id: string; version: string; status: string }> {
return getJSON('/api/system/status');
}
@@ -87,11 +87,19 @@ export function getSystemUpgrades(): Promise<{
return getJSON('/api/system/upgrades');
}
-export function getMigrationStatus(): Promise<any> {
+export function getMigrationStatus(): Promise<{
+ message?: string;
+ startedAt?: string;
+ state: string;
+}> {
return getJSON('/api/system/db_migration_status');
}
-export function migrateDatabase() {
+export function migrateDatabase(): Promise<{
+ message?: string;
+ startedAt?: string;
+ state: string;
+}> {
return postJSON('/api/system/migrate_db');
}
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx
index a264af89975..3b2019fe566 100644
--- a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx
@@ -20,6 +20,7 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import App from '../App';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
jest.mock('../../../../api/measures', () => ({
getCustomMeasures: () =>
@@ -55,8 +56,7 @@ it('should work', async () => {
const wrapper = shallow(<App component={{ key: 'foo' }} />);
expect(wrapper).toMatchSnapshot();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
// create
@@ -65,8 +65,7 @@ it('should work', async () => {
metricKey: 'metricKey',
value: 'value'
});
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper.state().measures).toMatchSnapshot();
expect(wrapper.state().paging.total).toBe(2);
@@ -76,15 +75,13 @@ it('should work', async () => {
id: '2',
value: 'other'
});
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper.state().measures).toMatchSnapshot();
expect(wrapper.state().paging.total).toBe(2);
// delete
wrapper.find('List').prop<Function>('onDelete')('2');
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper.state().measures).toMatchSnapshot();
expect(wrapper.state().paging.total).toBe(1);
});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx
index 3af7a3c000a..e6252db478f 100644
--- a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import Form from '../Form';
-import { change, submit, click } from '../../../../helpers/testUtils';
+import { change, submit, click, waitAndUpdate } from '../../../../helpers/testUtils';
jest.mock('../../../../api/metrics', () => ({
getAllMetrics: () =>
@@ -44,8 +44,7 @@ it('should render form', async () => {
);
expect(wrapper.dive()).toMatchSnapshot();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
const form = wrapper.dive();
expect(form).toMatchSnapshot();
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx
index d4b9e0772d5..76ca8e0510f 100644
--- a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx
@@ -20,6 +20,7 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import App from '../App';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
jest.mock('../../../../api/metrics', () => ({
getMetricDomains: () => Promise.resolve(['Coverage', 'Issues']),
@@ -42,8 +43,7 @@ it('should work', async () => {
(wrapper.instance() as App).mounted = true;
expect(wrapper).toMatchSnapshot();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
// create
@@ -53,8 +53,7 @@ it('should work', async () => {
name: 'Bar',
type: 'INT'
});
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper.state().metrics).toMatchSnapshot();
expect(wrapper.state().paging.total).toBe(2);
@@ -66,15 +65,13 @@ it('should work', async () => {
name: 'Bar',
type: 'STRING'
});
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper.state().metrics).toMatchSnapshot();
expect(wrapper.state().paging.total).toBe(2);
// delete
wrapper.find('List').prop<Function>('onDelete')('bar');
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper.state().metrics).toMatchSnapshot();
expect(wrapper.state().paging.total).toBe(1);
});
diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx
new file mode 100644
index 00000000000..3bc5e06b616
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx
@@ -0,0 +1,285 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { getMigrationStatus, getSystemStatus, migrateDatabase } from '../../../api/system';
+import DateFromNow from '../../../components/intl/DateFromNow';
+import TimeFormatter from '../../../components/intl/TimeFormatter';
+import { translate } from '../../../helpers/l10n';
+import { getBaseUrl } from '../../../helpers/urls';
+import '../styles.css';
+
+interface Props {
+ // eslint-disable-next-line camelcase
+ location: { query: { return_to?: string } };
+ setup: boolean;
+}
+
+interface State {
+ message?: string;
+ startedAt?: string;
+ state?: string;
+ status?: string;
+ wasStarting?: boolean;
+}
+
+export default class App extends React.PureComponent<Props, State> {
+ interval?: number;
+ mounted = false;
+ state: State = {};
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchStatus();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ if (this.interval) {
+ window.clearInterval(this.interval);
+ }
+ }
+
+ fetchStatus = () => {
+ const request = this.props.setup ? this.fetchMigrationState() : this.fetchSystemStatus();
+ request.catch(() => {
+ if (this.mounted) {
+ this.setState({
+ message: undefined,
+ startedAt: undefined,
+ state: undefined,
+ status: 'OFFLINE'
+ });
+ }
+ });
+ };
+
+ fetchSystemStatus = () => {
+ return getSystemStatus().then(({ status }) => {
+ if (this.mounted) {
+ this.setState({ status });
+
+ if (status === 'STARTING') {
+ this.setState({ wasStarting: true });
+ this.scheduleRefresh();
+ } else if (status === 'UP') {
+ if (this.state.wasStarting) {
+ this.loadPreviousPage();
+ }
+ } else {
+ this.scheduleRefresh();
+ }
+ }
+ });
+ };
+
+ fetchMigrationState = () => {
+ return getMigrationStatus().then(({ message, startedAt, state }) => {
+ if (this.mounted) {
+ this.setState({ message, startedAt, state });
+ if (state === 'MIGRATION_SUCCEEDED') {
+ this.loadPreviousPage();
+ } else if (state !== 'NO_MIGRATION') {
+ this.scheduleRefresh();
+ }
+ }
+ });
+ };
+
+ scheduleRefresh = () => {
+ this.interval = window.setTimeout(this.fetchStatus, 5000);
+ };
+
+ loadPreviousPage = () => {
+ setInterval(() => {
+ window.location.href = this.props.location.query['return_to'] || getBaseUrl() + '/';
+ }, 2500);
+ };
+
+ handleMigrateClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ migrateDatabase().then(
+ ({ message, startedAt, state }) => {
+ if (this.mounted) {
+ this.setState({ message, startedAt, state });
+ }
+ },
+ () => {}
+ );
+ };
+
+ render() {
+ const { state, status } = this.state;
+
+ return (
+ <div className="page-wrapper-simple" id="bd">
+ <div
+ className={classNames('page-simple', { 'panel-warning': state === 'MIGRATION_REQUIRED' })}
+ id="nonav">
+ {status === 'OFFLINE' && (
+ <>
+ <h1 className="maintenance-title text-danger">
+ {translate('maintenance.sonarqube_is_offline')}
+ </h1>
+ <p className="maintenance-text">
+ {translate('maintenance.sonarqube_is_offline.text')}
+ </p>
+ <p className="maintenance-text text-center">
+ <a href={getBaseUrl() + '/'}>{translate('maintenance.try_again')}</a>
+ </p>
+ </>
+ )}
+
+ {status === 'UP' && (
+ <>
+ <h1 className="maintenance-title">{translate('maintenance.sonarqube_is_up')}</h1>
+ <p className="maintenance-text text-center">
+ {translate('maintenance.all_systems_opetational')}
+ </p>
+ <p className="maintenance-text text-center">
+ <a href={getBaseUrl() + '/'}>{translate('layout.home')}</a>
+ </p>
+ </>
+ )}
+
+ {status === 'STARTING' && (
+ <>
+ <h1 className="maintenance-title">
+ {translate('maintenance.sonarqube_is_starting')}
+ </h1>
+ <p className="maintenance-spinner">
+ <i className="spinner" />
+ </p>
+ </>
+ )}
+
+ {status === 'DOWN' && (
+ <>
+ <h1 className="maintenance-title text-danger">
+ {translate('maintenance.sonarqube_is_down')}
+ </h1>
+ <p className="maintenance-text">{translate('maintenance.sonarqube_is_down.text')}</p>
+ <p className="maintenance-text text-center">
+ <a href={getBaseUrl() + '/'}>{translate('maintenance.try_again')}</a>
+ </p>
+ </>
+ )}
+
+ {(status === 'DB_MIGRATION_NEEDED' || status === 'DB_MIGRATION_RUNNING') && (
+ <>
+ <h1 className="maintenance-title">
+ {translate('maintenance.sonarqube_is_under_maintenance')}
+ </h1>
+ <p
+ className="maintenance-text"
+ dangerouslySetInnerHTML={{
+ __html: translate('maintenance.sonarqube_is_under_maintenance.1')
+ }}
+ />
+ <p
+ className="maintenance-text"
+ dangerouslySetInnerHTML={{
+ __html: translate('maintenance.sonarqube_is_under_maintenance.2')
+ }}
+ />
+ </>
+ )}
+
+ {state === 'NO_MIGRATION' && (
+ <>
+ <h1 className="maintenance-title">
+ {translate('maintenance.database_is_up_to_date')}
+ </h1>
+ <p className="maintenance-text text-center">
+ <a href={getBaseUrl() + '/'}>{translate('layout.home')}</a>
+ </p>
+ </>
+ )}
+
+ {state === 'MIGRATION_REQUIRED' && (
+ <>
+ <h1 className="maintenance-title">{translate('maintenance.upgrade_database')}</h1>
+ <p className="maintenance-text">{translate('maintenance.upgrade_database.1')}</p>
+ <p className="maintenance-text">{translate('maintenance.upgrade_database.2')}</p>
+ <p className="maintenance-text">{translate('maintenance.upgrade_database.3')}</p>
+ <div className="maintenance-spinner">
+ <button id="start-migration" onClick={this.handleMigrateClick} type="button">
+ {translate('maintenance.upgrade')}
+ </button>
+ </div>
+ </>
+ )}
+
+ {state === 'NOT_SUPPORTED' && (
+ <>
+ <h1 className="maintenance-title text-danger">
+ {translate('maintenance.migration_not_supported')}
+ </h1>
+ <p>{translate('maintenance.migration_not_supported.text')}</p>
+ </>
+ )}
+
+ {state === 'MIGRATION_RUNNING' && (
+ <>
+ <h1 className="maintenance-title">{translate('maintenance.database_migration')}</h1>
+ {this.state.message && (
+ <p className="maintenance-text text-center">{this.state.message}</p>
+ )}
+ {this.state.startedAt && (
+ <p className="maintenance-text text-center">
+ {translate('background_tasks.table.started')}{' '}
+ <DateFromNow date={this.state.startedAt} />
+ <br />
+ <small className="text-muted">
+ <TimeFormatter date={this.state.startedAt} long={true} />
+ </small>
+ </p>
+ )}
+ <p className="maintenance-spinner">
+ <i className="spinner" />
+ </p>
+ </>
+ )}
+
+ {state === 'MIGRATION_SUCCEEDED' && (
+ <>
+ <h1 className="maintenance-title text-success">
+ {translate('maintenance.database_is_up_to_date')}
+ </h1>
+ <p className="maintenance-text text-center">
+ <a href={getBaseUrl() + '/'}>{translate('layout.home')}</a>
+ </p>
+ </>
+ )}
+
+ {state === 'MIGRATION_FAILED' && (
+ <>
+ <h1 className="maintenance-title text-danger">
+ {translate('maintenance.upgrade_failed')}
+ </h1>
+ <p className="maintenance-text">{translate('maintenance.upgrade_failed.text')}</p>
+ </>
+ )}
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx
index 38be383b830..1a84ccdcd6d 100644
--- a/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx
@@ -18,19 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import init from '../init';
-import '../styles.css';
+import App from './App';
interface Props {
+ // eslint-disable-next-line camelcase
location: { query: { return_to: string } };
}
-export default class MaintenanceAppContainer extends React.PureComponent<Props> {
- componentDidMount() {
- init(this.refs.container, false, this.props.location.query['return_to']);
- }
-
- render() {
- return <div ref="container" />;
- }
+export default function MaintenanceAppContainer(props: Props) {
+ return <App setup={false} {...props} />;
}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx
index 46d68176189..4cd1e8fe5b5 100644
--- a/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx
@@ -18,19 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import init from '../init';
-import '../styles.css';
+import App from './App';
interface Props {
+ // eslint-disable-next-line camelcase
location: { query: { return_to: string } };
}
-export default class SetupAppContainer extends React.PureComponent<Props> {
- componentDidMount() {
- init(this.refs.container, true, this.props.location.query['return_to']);
- }
-
- render() {
- return <div ref="container" />;
- }
+export default function MaintenanceAppContainer(props: Props) {
+ return <App setup={true} {...props} />;
}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/App-test.tsx
new file mode 100644
index 00000000000..8d4511d09e7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/App-test.tsx
@@ -0,0 +1,134 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+/* eslint-disable import/order */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import App from '../App';
+import { click, waitAndUpdate } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../api/system', () => ({
+ getMigrationStatus: jest.fn(),
+ getSystemStatus: jest.fn(),
+ migrateDatabase: jest.fn()
+}));
+
+jest.useFakeTimers();
+
+const getMigrationStatus = require('../../../../api/system').getMigrationStatus as jest.Mock;
+const getSystemStatus = require('../../../../api/system').getSystemStatus as jest.Mock;
+const migrateDatabase = require('../../../../api/system').migrateDatabase as jest.Mock;
+
+const location = { query: {} };
+
+beforeEach(() => {
+ getMigrationStatus.mockClear();
+ getSystemStatus.mockClear();
+ migrateDatabase.mockClear();
+});
+
+afterEach(() => {
+ jest.clearAllTimers();
+});
+
+describe('Maintenance Page', () => {
+ ['UP', 'DOWN', 'STARTING', 'DB_MIGRATION_NEEDED', 'DB_MIGRATION_RUNNING'].forEach(status => {
+ it(`should render ${status} status`, async () => {
+ getSystemStatus.mockImplementationOnce(() => Promise.resolve({ status }));
+ await checkApp(false);
+ });
+ });
+
+ it('should render OFFLINE status', async () => {
+ getSystemStatus.mockImplementationOnce(() => Promise.reject(undefined));
+ await checkApp(false);
+ });
+
+ it('should poll status', async () => {
+ getSystemStatus.mockImplementationOnce(() =>
+ Promise.resolve({ status: 'DB_MIGRATION_RUNNING' })
+ );
+ const wrapper = shallow(<App location={location} setup={false} />);
+ await waitAndUpdate(wrapper);
+ expect(getSystemStatus).toBeCalled();
+
+ getSystemStatus.mockClear();
+ getSystemStatus.mockImplementationOnce(() =>
+ Promise.resolve({ status: 'DB_MIGRATION_RUNNING' })
+ );
+ jest.runOnlyPendingTimers();
+ await waitAndUpdate(wrapper);
+ expect(getSystemStatus).toBeCalled();
+
+ getSystemStatus.mockClear();
+ getSystemStatus.mockImplementationOnce(() =>
+ Promise.resolve({ status: 'DB_MIGRATION_RUNNING' })
+ );
+ jest.runOnlyPendingTimers();
+ await waitAndUpdate(wrapper);
+ expect(getSystemStatus).toBeCalled();
+ });
+
+ it('should open previous page', async () => {
+ getSystemStatus.mockImplementationOnce(() => Promise.resolve({ status: 'STARTING' }));
+ const wrapper = shallow(<App location={location} setup={false} />);
+ const loadPreviousPage = jest.fn();
+ (wrapper.instance() as App).loadPreviousPage = loadPreviousPage;
+ await waitAndUpdate(wrapper);
+
+ getSystemStatus.mockImplementationOnce(() => Promise.resolve({ status: 'UP' }));
+ jest.runOnlyPendingTimers();
+ await waitAndUpdate(wrapper);
+ expect(loadPreviousPage).toBeCalled();
+ });
+});
+
+describe('Setup Page', () => {
+ ['NO_MIGRATION', 'NOT_SUPPORTED', 'MIGRATION_SUCCEEDED', 'MIGRATION_FAILED'].forEach(state => {
+ it(`should render ${state} state`, async () => {
+ getMigrationStatus.mockImplementationOnce(() =>
+ Promise.resolve({ message: 'message', startedAt: '2017-01-02T00:00:00.000Z', state })
+ );
+ await checkApp(true);
+ });
+ });
+
+ it('should start migration', async () => {
+ getMigrationStatus.mockImplementationOnce(() =>
+ Promise.resolve({ state: 'MIGRATION_REQUIRED' })
+ );
+ migrateDatabase.mockImplementationOnce(() =>
+ Promise.resolve({ startedAt: '2017-01-02T00:00:00.000Z', state: 'MIGRATION_RUNNING' })
+ );
+ const wrapper = shallow(<App location={location} setup={true} />);
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('button'));
+ expect(migrateDatabase).toBeCalled();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+ });
+});
+
+async function checkApp(setup: boolean) {
+ const wrapper = shallow(<App location={location} setup={setup} />);
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/__snapshots__/App-test.tsx.snap
new file mode 100644
index 00000000000..784a8eb1969
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -0,0 +1,399 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Maintenance Page should render DB_MIGRATION_NEEDED status 1`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple"
+ id="nonav"
+ >
+ <React.Fragment>
+ <h1
+ className="maintenance-title"
+ >
+ maintenance.sonarqube_is_under_maintenance
+ </h1>
+ <p
+ className="maintenance-text"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "maintenance.sonarqube_is_under_maintenance.1",
+ }
+ }
+ />
+ <p
+ className="maintenance-text"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "maintenance.sonarqube_is_under_maintenance.2",
+ }
+ }
+ />
+ </React.Fragment>
+ </div>
+</div>
+`;
+
+exports[`Maintenance Page should render DB_MIGRATION_RUNNING status 1`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple"
+ id="nonav"
+ >
+ <React.Fragment>
+ <h1
+ className="maintenance-title"
+ >
+ maintenance.sonarqube_is_under_maintenance
+ </h1>
+ <p
+ className="maintenance-text"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "maintenance.sonarqube_is_under_maintenance.1",
+ }
+ }
+ />
+ <p
+ className="maintenance-text"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "maintenance.sonarqube_is_under_maintenance.2",
+ }
+ }
+ />
+ </React.Fragment>
+ </div>
+</div>
+`;
+
+exports[`Maintenance Page should render DOWN status 1`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple"
+ id="nonav"
+ >
+ <React.Fragment>
+ <h1
+ className="maintenance-title text-danger"
+ >
+ maintenance.sonarqube_is_down
+ </h1>
+ <p
+ className="maintenance-text"
+ >
+ maintenance.sonarqube_is_down.text
+ </p>
+ <p
+ className="maintenance-text text-center"
+ >
+ <a
+ href="/"
+ >
+ maintenance.try_again
+ </a>
+ </p>
+ </React.Fragment>
+ </div>
+</div>
+`;
+
+exports[`Maintenance Page should render OFFLINE status 1`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple"
+ id="nonav"
+ >
+ <React.Fragment>
+ <h1
+ className="maintenance-title text-danger"
+ >
+ maintenance.sonarqube_is_offline
+ </h1>
+ <p
+ className="maintenance-text"
+ >
+ maintenance.sonarqube_is_offline.text
+ </p>
+ <p
+ className="maintenance-text text-center"
+ >
+ <a
+ href="/"
+ >
+ maintenance.try_again
+ </a>
+ </p>
+ </React.Fragment>
+ </div>
+</div>
+`;
+
+exports[`Maintenance Page should render STARTING status 1`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple"
+ id="nonav"
+ >
+ <React.Fragment>
+ <h1
+ className="maintenance-title"
+ >
+ maintenance.sonarqube_is_starting
+ </h1>
+ <p
+ className="maintenance-spinner"
+ >
+ <i
+ className="spinner"
+ />
+ </p>
+ </React.Fragment>
+ </div>
+</div>
+`;
+
+exports[`Maintenance Page should render UP status 1`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple"
+ id="nonav"
+ >
+ <React.Fragment>
+ <h1
+ className="maintenance-title"
+ >
+ maintenance.sonarqube_is_up
+ </h1>
+ <p
+ className="maintenance-text text-center"
+ >
+ maintenance.all_systems_opetational
+ </p>
+ <p
+ className="maintenance-text text-center"
+ >
+ <a
+ href="/"
+ >
+ layout.home
+ </a>
+ </p>
+ </React.Fragment>
+ </div>
+</div>
+`;
+
+exports[`Setup Page should render MIGRATION_FAILED state 1`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple"
+ id="nonav"
+ >
+ <React.Fragment>
+ <h1
+ className="maintenance-title text-danger"
+ >
+ maintenance.upgrade_failed
+ </h1>
+ <p
+ className="maintenance-text"
+ >
+ maintenance.upgrade_failed.text
+ </p>
+ </React.Fragment>
+ </div>
+</div>
+`;
+
+exports[`Setup Page should render MIGRATION_SUCCEEDED state 1`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple"
+ id="nonav"
+ >
+ <React.Fragment>
+ <h1
+ className="maintenance-title text-success"
+ >
+ maintenance.database_is_up_to_date
+ </h1>
+ <p
+ className="maintenance-text text-center"
+ >
+ <a
+ href="/"
+ >
+ layout.home
+ </a>
+ </p>
+ </React.Fragment>
+ </div>
+</div>
+`;
+
+exports[`Setup Page should render NO_MIGRATION state 1`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple"
+ id="nonav"
+ >
+ <React.Fragment>
+ <h1
+ className="maintenance-title"
+ >
+ maintenance.database_is_up_to_date
+ </h1>
+ <p
+ className="maintenance-text text-center"
+ >
+ <a
+ href="/"
+ >
+ layout.home
+ </a>
+ </p>
+ </React.Fragment>
+ </div>
+</div>
+`;
+
+exports[`Setup Page should render NOT_SUPPORTED state 1`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple"
+ id="nonav"
+ >
+ <React.Fragment>
+ <h1
+ className="maintenance-title text-danger"
+ >
+ maintenance.migration_not_supported
+ </h1>
+ <p>
+ maintenance.migration_not_supported.text
+ </p>
+ </React.Fragment>
+ </div>
+</div>
+`;
+
+exports[`Setup Page should start migration 1`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple panel-warning"
+ id="nonav"
+ >
+ <React.Fragment>
+ <h1
+ className="maintenance-title"
+ >
+ maintenance.upgrade_database
+ </h1>
+ <p
+ className="maintenance-text"
+ >
+ maintenance.upgrade_database.1
+ </p>
+ <p
+ className="maintenance-text"
+ >
+ maintenance.upgrade_database.2
+ </p>
+ <p
+ className="maintenance-text"
+ >
+ maintenance.upgrade_database.3
+ </p>
+ <div
+ className="maintenance-spinner"
+ >
+ <button
+ id="start-migration"
+ onClick={[Function]}
+ type="button"
+ >
+ maintenance.upgrade
+ </button>
+ </div>
+ </React.Fragment>
+ </div>
+</div>
+`;
+
+exports[`Setup Page should start migration 2`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple"
+ id="nonav"
+ >
+ <React.Fragment>
+ <h1
+ className="maintenance-title"
+ >
+ maintenance.database_migration
+ </h1>
+ <p
+ className="maintenance-text text-center"
+ >
+ background_tasks.table.started
+
+ <DateFromNow
+ date="2017-01-02T00:00:00.000Z"
+ />
+ <br />
+ <small
+ className="text-muted"
+ >
+ <TimeFormatter
+ date="2017-01-02T00:00:00.000Z"
+ long={true}
+ />
+ </small>
+ </p>
+ <p
+ className="maintenance-spinner"
+ >
+ <i
+ className="spinner"
+ />
+ </p>
+ </React.Fragment>
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/maintenance/init.js b/server/sonar-web/src/main/js/apps/maintenance/init.js
deleted file mode 100644
index 15c84c25ed9..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/init.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Backbone from 'backbone';
-import Marionette from 'backbone.marionette';
-import MainView from './main-view';
-
-const App = new Marionette.Application();
-
-App.on('start', options => {
- const viewOptions = {
- ...options,
- model: new Backbone.Model()
- };
- const mainView = new MainView(viewOptions);
- mainView.render().refresh();
-});
-
-export default function(el, setup, returnTo) {
- App.start({ el, setup, returnTo });
-}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/main-view.js b/server/sonar-web/src/main/js/apps/maintenance/main-view.js
deleted file mode 100644
index da558206ba2..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/main-view.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Marionette from 'backbone.marionette';
-import Template from './templates/maintenance-main.hbs';
-import { getSystemStatus, getMigrationStatus, migrateDatabase } from '../../api/system';
-import { getBaseUrl } from '../../helpers/urls';
-
-export default Marionette.ItemView.extend({
- template: Template,
-
- events: {
- 'click #start-migration': 'startMigration'
- },
-
- initialize() {
- this.pollingInternal = setInterval(() => {
- this.refresh();
- }, 5000);
- this.wasStarting = false;
- },
-
- getStatus() {
- return this.options.setup ? getMigrationStatus() : getSystemStatus();
- },
-
- refresh() {
- return this.getStatus().then(
- r => {
- if (r.status === 'STARTING') {
- this.wasStarting = true;
- }
- // unset `status` in case if was `OFFLINE` previously
- this.model.set({ status: undefined, ...r });
- this.render();
- if (this.model.get('status') === 'UP' || this.model.get('state') === 'NO_MIGRATION') {
- this.stopPolling();
- }
- if (this.model.get('status') === 'UP' && this.wasStarting) {
- this.loadPreviousPage();
- }
- if (this.model.get('state') === 'MIGRATION_SUCCEEDED') {
- this.loadPreviousPage();
- }
- },
- () => {
- this.model.set({ status: 'OFFLINE' });
- this.render();
- }
- );
- },
-
- stopPolling() {
- clearInterval(this.pollingInternal);
- },
-
- startMigration() {
- migrateDatabase().then(
- r => {
- this.model.set(r);
- this.render();
- },
- () => {}
- );
- },
-
- loadPreviousPage() {
- setInterval(() => {
- window.location = this.options.returnTo || getBaseUrl();
- }, 2500);
- },
-
- serializeData() {
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- setup: this.options.setup
- };
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/maintenance/routes.tsx b/server/sonar-web/src/main/js/apps/maintenance/routes.tsx
index 7ef353bbe4a..ac4c46ec21c 100644
--- a/server/sonar-web/src/main/js/apps/maintenance/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/maintenance/routes.tsx
@@ -19,9 +19,12 @@
*/
import * as React from 'react';
import { IndexRoute } from 'react-router';
-import MaintenanceAppContainer from './components/MaintenanceAppContainer';
-import SetupAppContainer from './components/SetupAppContainer';
+import { lazyLoad } from '../../components/lazyLoad';
-export const maintenanceRoutes = <IndexRoute component={MaintenanceAppContainer} />;
+export const maintenanceRoutes = (
+ <IndexRoute component={lazyLoad(() => import('./components/MaintenanceAppContainer'))} />
+);
-export const setupRoutes = <IndexRoute component={SetupAppContainer} />;
+export const setupRoutes = (
+ <IndexRoute component={lazyLoad(() => import('./components/SetupAppContainer'))} />
+);
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-failed.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-failed.hbs
deleted file mode 100644
index ab1421b6b7e..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-failed.hbs
+++ /dev/null
@@ -1,2 +0,0 @@
-<h1 class="maintenance-title text-danger">{{t 'maintenance.upgrade_failed'}}</h1>
-<p class="maintenance-text">{{t 'maintenance.upgrade_failed.text'}}</p>
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-not-supported.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-not-supported.hbs
deleted file mode 100644
index 6d023044313..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-not-supported.hbs
+++ /dev/null
@@ -1,2 +0,0 @@
-<h1 class="maintenance-title text-danger">{{t 'maintenance.migration_not_supported'}}</h1>
-<p>{{t 'maintenance.migration_not_supported.text'}}</p>
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-required.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-required.hbs
deleted file mode 100644
index 3236ff5a80f..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-required.hbs
+++ /dev/null
@@ -1,7 +0,0 @@
-<h1 class="maintenance-title">{{t 'maintenance.upgrade_database'}}</h1>
-<p class="maintenance-text">{{t 'maintenance.upgrade_database.1'}}</p>
-<p class="maintenance-text">{{t 'maintenance.upgrade_database.2'}}</p>
-<p class="maintenance-text">{{t 'maintenance.upgrade_database.3'}}</p>
-<div class="maintenance-spinner">
- <button id="start-migration">{{t 'maintenance.upgrade'}}</button>
-</div>
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-running.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-running.hbs
deleted file mode 100644
index c69baef7434..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-running.hbs
+++ /dev/null
@@ -1,11 +0,0 @@
-<h1 class="maintenance-title">{{t 'maintenance.database_migration'}}</h1>
-{{#if message}}
- <p class="maintenance-text text-center">{{message}}</p>
-{{/if}}
-{{#if startedAt}}
- <p class="maintenance-text text-center">
- {{t 'background_tasks.table.started'}} {{fromNow startedAt}}<br>
- <small class="text-muted">{{dt startedAt}}</small>
- </p>
-{{/if}}
-<p class="maintenance-spinner"><i class="spinner"></i></p>
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-succeeded.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-succeeded.hbs
deleted file mode 100644
index 7e70e68cf30..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-migration-succeeded.hbs
+++ /dev/null
@@ -1,4 +0,0 @@
-<h1 class="maintenance-title text-success">{{t 'maintenance.database_is_up_to_date'}}</h1>
-<p class="maintenance-text text-center">
- <a href="{{link '/'}}">{{t 'layout.home'}}</a>
-</p>
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-no-migration.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-no-migration.hbs
deleted file mode 100644
index 6c30631c9a5..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-state-no-migration.hbs
+++ /dev/null
@@ -1,4 +0,0 @@
-<h1 class="maintenance-title">{{t 'maintenance.database_is_up_to_date'}}</h1>
-<p class="maintenance-text text-center">
- <a href="{{link '/'}}">{{t 'layout.home'}}</a>
-</p>
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-down.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-down.hbs
deleted file mode 100644
index 811aa16ef5b..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-down.hbs
+++ /dev/null
@@ -1,5 +0,0 @@
-<h1 class="maintenance-title text-danger">{{t 'maintenance.sonarqube_is_down'}}</h1>
-<p class="maintenance-text">{{t 'maintenance.sonarqube_is_down.text'}}</p>
-<p class="maintenance-text text-center">
- <a href="{{link '/'}}">{{t 'maintenance.try_again'}}</a>
-</p>
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-migration.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-migration.hbs
deleted file mode 100644
index f51f4c72475..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-migration.hbs
+++ /dev/null
@@ -1,3 +0,0 @@
-<h1 class="maintenance-title">{{t 'maintenance.sonarqube_is_under_maintenance'}}</h1>
-<p class="maintenance-text">{{{t 'maintenance.sonarqube_is_under_maintenance.1'}}}</p>
-<p class="maintenance-text">{{{t 'maintenance.sonarqube_is_under_maintenance.2'}}}</p>
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-offline.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-offline.hbs
deleted file mode 100644
index 6e7bac35c3f..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-offline.hbs
+++ /dev/null
@@ -1,5 +0,0 @@
-<h1 class="maintenance-title text-danger">{{t 'maintenance.sonarqube_is_offline'}}</h1>
-<p class="maintenance-text">{{t 'maintenance.sonarqube_is_offline.text'}}</p>
-<p class="maintenance-text text-center">
- <a href="{{link '/'}}">{{t 'maintenance.try_again'}}</a>
-</p>
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-starting.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-starting.hbs
deleted file mode 100644
index 96f7d89f122..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-starting.hbs
+++ /dev/null
@@ -1,2 +0,0 @@
-<h1 class="maintenance-title">{{t 'maintenance.sonarqube_is_starting'}}</h1>
-<p class="maintenance-spinner"><i class="spinner"></i></p>
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-up.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-up.hbs
deleted file mode 100644
index e97b550f342..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/templates/_maintenance-status-up.hbs
+++ /dev/null
@@ -1,5 +0,0 @@
-<h1 class="maintenance-title">{{t 'maintenance.sonarqube_is_up'}}</h1>
-<p class="maintenance-text text-center">{{t 'maintenance.all_systems_opetational'}}</p>
-<p class="maintenance-text text-center">
- <a href="{{link '/'}}">{{t 'layout.home'}}</a>
-</p>
diff --git a/server/sonar-web/src/main/js/apps/maintenance/templates/maintenance-main.hbs b/server/sonar-web/src/main/js/apps/maintenance/templates/maintenance-main.hbs
deleted file mode 100644
index 44a4bc8798d..00000000000
--- a/server/sonar-web/src/main/js/apps/maintenance/templates/maintenance-main.hbs
+++ /dev/null
@@ -1,32 +0,0 @@
-<div id="bd" class="page-wrapper-simple">
- <div id="nonav" class="page-simple {{#eq state 'MIGRATION_REQUIRED'}}panel-warning{{/eq}}">
-
- {{#eq status 'OFFLINE'}}
-
- {{> '_maintenance-status-offline'}}
-
- {{else}}
-
- {{#unless setup}}
-
- {{#eq status 'UP'}}{{> '_maintenance-status-up'}}{{/eq}}
- {{#eq status 'STARTING'}}{{> '_maintenance-status-starting'}}{{/eq}}
- {{#eq status 'DOWN'}}{{> '_maintenance-status-down'}}{{/eq}}
- {{#eq status 'DB_MIGRATION_NEEDED'}}{{> '_maintenance-status-migration'}}{{/eq}}
- {{#eq status 'DB_MIGRATION_RUNNING'}}{{> '_maintenance-status-migration'}}{{/eq}}
-
- {{else}}
-
- {{#eq state 'NO_MIGRATION'}}{{> '_maintenance-state-no-migration'}}{{/eq}}
- {{#eq state 'MIGRATION_REQUIRED'}}{{> '_maintenance-state-migration-required'}}{{/eq}}
- {{#eq state 'NOT_SUPPORTED'}}{{> '_maintenance-state-migration-not-supported'}}{{/eq}}
- {{#eq state 'MIGRATION_RUNNING'}}{{> '_maintenance-state-migration-running'}}{{/eq}}
- {{#eq state 'MIGRATION_SUCCEEDED'}}{{> '_maintenance-state-migration-succeeded'}}{{/eq}}
- {{#eq state 'MIGRATION_FAILED'}}{{> '_maintenance-state-migration-failed'}}{{/eq}}
-
- {{/unless}}
-
- {{/eq}}
-
- </div>
-</div>
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx
index fbf8be8d1f7..158a52befed 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx
@@ -20,7 +20,7 @@
/* eslint-disable import/order */
import * as React from 'react';
import { shallow } from 'enzyme';
-import { change } from '../../../../helpers/testUtils';
+import { change, waitAndUpdate } from '../../../../helpers/testUtils';
import LicenseEditionSet from '../LicenseEditionSet';
jest.mock('../../../../api/marketplace', () => ({
@@ -59,8 +59,7 @@ it('should display correctly', () => {
it('should display the get license link with parameters', async () => {
const wrapper = getWrapper();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper.find('a')).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx
index fbf21407ac9..da053905930 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Subscription-test.tsx
@@ -28,7 +28,7 @@ jest.mock('../../../../api/report', () => {
import * as React from 'react';
import { mount, shallow } from 'enzyme';
import Subscription from '../Subscription';
-import { click } from '../../../../helpers/testUtils';
+import { click, waitAndUpdate } from '../../../../helpers/testUtils';
const subscribe = require('../../../../api/report').subscribe as jest.Mock<any>;
const unsubscribe = require('../../../../api/report').unsubscribe as jest.Mock<any>;
@@ -77,8 +77,7 @@ it('changes subscription', async () => {
click(wrapper.find('button'));
expect(unsubscribe).toBeCalledWith('foo');
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
click(wrapper.find('button'));
expect(subscribe).toBeCalledWith('foo');
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx
index 50a2baa79a3..d23beef1c6e 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx
@@ -24,7 +24,7 @@ import * as React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import DeleteBranchModal from '../DeleteBranchModal';
import { ShortLivingBranch, BranchType } from '../../../../app/types';
-import { submit, doAsync, click } from '../../../../helpers/testUtils';
+import { submit, doAsync, click, waitAndUpdate } from '../../../../helpers/testUtils';
import { deleteBranch } from '../../../../api/branches';
beforeEach(() => {
@@ -38,19 +38,17 @@ it('renders', () => {
expect(wrapper).toMatchSnapshot();
});
-it('deletes branch', () => {
+it('deletes branch', async () => {
(deleteBranch as jest.Mock<any>).mockImplementation(() => Promise.resolve());
const onDelete = jest.fn();
const wrapper = shallowRender(onDelete);
submitForm(wrapper);
- return doAsync().then(() => {
- wrapper.update();
- expect(wrapper.state().loading).toBe(false);
- expect(onDelete).toBeCalled();
- expect(deleteBranch).toBeCalledWith('foo', 'feature');
- });
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().loading).toBe(false);
+ expect(onDelete).toBeCalled();
+ expect(deleteBranch).toBeCalledWith('foo', 'feature');
});
it('cancels', () => {
@@ -64,19 +62,17 @@ it('cancels', () => {
});
});
-it('stops loading on WS error', () => {
+it('stops loading on WS error', async () => {
(deleteBranch as jest.Mock<any>).mockImplementation(() => Promise.reject(null));
const onDelete = jest.fn();
const wrapper = shallowRender(onDelete);
submitForm(wrapper);
- return doAsync().then(() => {
- wrapper.update();
- expect(wrapper.state().loading).toBe(false);
- expect(onDelete).not.toBeCalled();
- expect(deleteBranch).toBeCalledWith('foo', 'feature');
- });
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().loading).toBe(false);
+ expect(onDelete).not.toBeCalled();
+ expect(deleteBranch).toBeCalledWith('foo', 'feature');
});
function shallowRender(onDelete: () => void = jest.fn(), onClose: () => void = jest.fn()) {
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx
index 1c55b08314f..3d897c3a953 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx
@@ -24,7 +24,7 @@ import * as React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import RenameBranchModal from '../RenameBranchModal';
import { MainBranch } from '../../../../app/types';
-import { submit, doAsync, click, change } from '../../../../helpers/testUtils';
+import { submit, doAsync, click, change, waitAndUpdate } from '../../../../helpers/testUtils';
import { renameBranch } from '../../../../api/branches';
beforeEach(() => {
@@ -40,19 +40,17 @@ it('renders', () => {
expect(wrapper).toMatchSnapshot();
});
-it('renames branch', () => {
+it('renames branch', async () => {
(renameBranch as jest.Mock<any>).mockImplementation(() => Promise.resolve());
const onRename = jest.fn();
const wrapper = shallowRender(onRename);
fillAndSubmit(wrapper);
- return doAsync().then(() => {
- wrapper.update();
- expect(wrapper.state().loading).toBe(false);
- expect(onRename).toBeCalled();
- expect(renameBranch).toBeCalledWith('foo', 'dev');
- });
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().loading).toBe(false);
+ expect(onRename).toBeCalled();
+ expect(renameBranch).toBeCalledWith('foo', 'dev');
});
it('cancels', () => {
@@ -66,18 +64,16 @@ it('cancels', () => {
});
});
-it('stops loading on WS error', () => {
+it('stops loading on WS error', async () => {
(renameBranch as jest.Mock<any>).mockImplementation(() => Promise.reject(null));
const onRename = jest.fn();
const wrapper = shallowRender(onRename);
fillAndSubmit(wrapper);
- return doAsync().then(() => {
- wrapper.update();
- expect(wrapper.state().loading).toBe(false);
- expect(onRename).not.toBeCalled();
- });
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().loading).toBe(false);
+ expect(onRename).not.toBeCalled();
});
function shallowRender(onRename: () => void = jest.fn(), onClose: () => void = jest.fn()) {
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx
index ed9c56dbcb1..09c56289581 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx
@@ -21,7 +21,7 @@
import * as React from 'react';
import { mount, shallow } from 'enzyme';
import BulkApplyTemplateModal, { Props } from '../BulkApplyTemplateModal';
-import { click } from '../../../helpers/testUtils';
+import { click, waitAndUpdate } from '../../../helpers/testUtils';
jest.mock('react-dom');
@@ -67,8 +67,7 @@ it('bulk applies template to all results', async () => {
});
expect(wrapper).toMatchSnapshot();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx
index 1cdac62d85e..d79aa247497 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx
@@ -27,7 +27,7 @@ jest.mock('../../../api/components', () => ({
import * as React from 'react';
import { shallow } from 'enzyme';
import CreateProjectForm from '../CreateProjectForm';
-import { change, submit, click } from '../../../helpers/testUtils';
+import { change, submit, click, waitAndUpdate } from '../../../helpers/testUtils';
import { Visibility } from '../../../app/types';
const createProject = require('../../../api/components').createProject as jest.Mock<any>;
@@ -65,8 +65,7 @@ it('creates project', async () => {
});
expect(wrapper).toMatchSnapshot();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx
index a615b0e0c77..f9a5eee8406 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx
@@ -21,7 +21,7 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import ProjectRowActions, { Props } from '../ProjectRowActions';
import { Visibility } from '../../../app/types';
-import { click } from '../../../helpers/testUtils';
+import { click, waitAndUpdate } from '../../../helpers/testUtils';
jest.mock('../../../api/components', () => ({
getComponentShow: jest.fn(() => Promise.reject(undefined))
@@ -45,8 +45,7 @@ it('restores access', async () => {
expect(wrapper).toMatchSnapshot();
wrapper.prop<Function>('onToggleClick')();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
click(wrapper.find('.js-restore-access'));
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx
index 0536833ec18..08452e22416 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx
@@ -21,7 +21,7 @@
import * as React from 'react';
import { mount, shallow } from 'enzyme';
import ProfilePermissions from '../ProfilePermissions';
-import { click } from '../../../../helpers/testUtils';
+import { click, waitAndUpdate } from '../../../../helpers/testUtils';
jest.mock('../../../../api/quality-profiles', () => ({
searchUsers: jest.fn(() => Promise.resolve([])),
@@ -56,8 +56,7 @@ it('opens add users form', async () => {
);
const wrapper = shallow(<ProfilePermissions profile={profile} />);
expect(searchUsers).toHaveBeenCalled();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper.find('ProfilePermissionsForm').exists()).toBeFalsy();
click(wrapper.find('button'));
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx
index 0f75a3da1e3..fcabce9784a 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx
@@ -22,6 +22,7 @@ import { shallow } from 'enzyme';
import ProfileRules from '../ProfileRules';
import * as apiRules from '../../../../api/rules';
import * as apiQP from '../../../../api/quality-profiles';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
const PROFILE = {
activeRuleCount: 68,
@@ -86,8 +87,7 @@ it('should render the quality profiles rules with sonarway comparison', async ()
const instance = wrapper.instance() as any;
instance.mounted = true;
instance.loadRules();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(1);
expect(wrapper).toMatchSnapshot();
});
@@ -136,8 +136,7 @@ it('should not show sonarway comparison if there is no missing rules', async ()
})
);
const wrapper = shallow(<ProfileRules organization={null} profile={PROFILE} />);
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(apiQP.getQualityProfile).toHaveBeenCalledTimes(1);
expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(0);
});
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx
index f0a4baca93d..81b2176c785 100644
--- a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx
+++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/EmailAlreadyExists-test.tsx
@@ -20,6 +20,7 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import EmailAlreadyExists from '../EmailAlreadyExists';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
jest.mock('../../../../api/users', () => ({
getIdentityProviders: () =>
@@ -52,7 +53,6 @@ it('render', async () => {
const wrapper = shallow(<EmailAlreadyExists location={{ query }} />);
(wrapper.instance() as EmailAlreadyExists).mounted = true;
(wrapper.instance() as EmailAlreadyExists).fetchIdentityProviders();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx
index 49e07c3a824..0ae8979a9d7 100644
--- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx
+++ b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx
@@ -20,7 +20,7 @@
/* eslint-disable import/order */
import * as React from 'react';
import { mount, shallow } from 'enzyme';
-import { click } from '../../../../../helpers/testUtils';
+import { click, waitAndUpdate } from '../../../../../helpers/testUtils';
import SystemUpgradeNotif from '../SystemUpgradeNotif';
jest.mock('../../../../../api/system', () => ({
@@ -82,8 +82,7 @@ beforeEach(() => {
it('should display correctly', async () => {
const wrapper = shallow(<SystemUpgradeNotif />);
expect(wrapper.type()).toBeNull();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});
@@ -92,8 +91,7 @@ it('should display nothing', async () => {
return Promise.resolve({ updateCenterRefresh: '', upgrades: [] });
});
const wrapper = shallow(<SystemUpgradeNotif />);
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper.type()).toBeNull();
});
@@ -104,8 +102,7 @@ it('should fetch upgrade when mounting', () => {
it('should open the upgrade form', async () => {
const wrapper = shallow(<SystemUpgradeNotif />);
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
click(wrapper.find('button'));
expect(wrapper.find('SystemUpgradeForm').exists()).toBeTruthy();
});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js
index 3db2d2faaa6..fcfbf8d810c 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js
+++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewOrganizationForm-test.js
@@ -21,7 +21,7 @@
import React from 'react';
import { mount } from 'enzyme';
import NewOrganizationForm from '../NewOrganizationForm';
-import { change, submit } from '../../../../helpers/testUtils';
+import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils';
jest.mock('../../../../api/organizations', () => ({
createOrganization: () => Promise.resolve(),
@@ -38,8 +38,7 @@ it('creates new organization', async () => {
change(wrapper.find('input'), 'foo');
submit(wrapper.find('form'));
expect(wrapper).toMatchSnapshot(); // spinner
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
expect(onDone).toBeCalledWith('foo');
});
@@ -52,8 +51,7 @@ it('deletes organization', async () => {
wrapper.find('DeleteButton').prop('onClick')();
wrapper.update();
expect(wrapper).toMatchSnapshot(); // spinner
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
expect(onDelete).toBeCalled();
});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js
index 24f4669106f..2d290f10503 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js
+++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/NewProjectForm-test.js
@@ -21,7 +21,7 @@
import React from 'react';
import { mount } from 'enzyme';
import NewProjectForm from '../NewProjectForm';
-import { change, submit } from '../../../../helpers/testUtils';
+import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils';
jest.mock('../../../../api/components', () => ({
createProject: () => Promise.resolve(),
@@ -37,8 +37,7 @@ it('creates new project', async () => {
change(wrapper.find('input'), 'foo');
submit(wrapper.find('form'));
expect(wrapper).toMatchSnapshot(); // spinner
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
expect(onDone).toBeCalledWith('foo');
});
@@ -51,8 +50,7 @@ it('deletes project', async () => {
wrapper.find('DeleteButton').prop('onClick')();
wrapper.update();
expect(wrapper).toMatchSnapshot(); // spinner
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
expect(onDelete).toBeCalled();
});
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js
index d284ebc0461..01add62b539 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js
+++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js
@@ -21,7 +21,7 @@
import React from 'react';
import { mount } from 'enzyme';
import OrganizationStep from '../OrganizationStep';
-import { click } from '../../../../helpers/testUtils';
+import { click, waitAndUpdate } from '../../../../helpers/testUtils';
import { getOrganizations } from '../../../../api/organizations';
jest.mock('../../../../api/organizations', () => ({
@@ -70,8 +70,7 @@ it('works with existing organization', async () => {
stepNumber={1}
/>
);
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
click(wrapper.find('.js-existing'));
expect(wrapper).toMatchSnapshot();
wrapper
@@ -95,8 +94,7 @@ it('works with new organization', async () => {
stepNumber={1}
/>
);
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
click(wrapper.find('.js-new'));
wrapper.find('NewOrganizationForm').prop('onDone')('new');
wrapper.update();
diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js
index c2fc31a6ab7..d336accbca7 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js
+++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/TokenStep-test.js
@@ -21,7 +21,7 @@
import React from 'react';
import { mount } from 'enzyme';
import TokenStep from '../TokenStep';
-import { change, click, submit } from '../../../../helpers/testUtils';
+import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils';
jest.mock('../../../../api/user-tokens', () => ({
getTokens: () => Promise.resolve([{ name: 'foo' }]),
@@ -44,14 +44,12 @@ it('generates token', async () => {
stepNumber={1}
/>
);
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
change(wrapper.find('input'), 'my token');
submit(wrapper.find('form'));
expect(wrapper).toMatchSnapshot(); // spinner
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});
@@ -72,8 +70,7 @@ it('revokes token', async () => {
wrapper.find('DeleteButton').prop('onClick')();
wrapper.update();
expect(wrapper).toMatchSnapshot(); // spinner
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx
index 1c059f39f31..5adb4d2dae6 100644
--- a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx
@@ -22,6 +22,7 @@ import * as React from 'react';
import { Location } from 'history';
import { shallow } from 'enzyme';
import UsersApp from '../UsersApp';
+import { waitAndUpdate } from '../../../helpers/testUtils';
jest.mock('../../../api/users', () => ({
getIdentityProviders: jest.fn(() =>
@@ -72,8 +73,7 @@ it('should render correctly', async () => {
expect(wrapper).toMatchSnapshot();
expect(getIdentityProviders).toHaveBeenCalled();
expect(searchUsers).toHaveBeenCalled();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx
index 32d388fa739..c4ea074996c 100644
--- a/server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import SimpleModal, { ChildrenProps } from '../SimpleModal';
-import { click } from '../../../helpers/testUtils';
+import { click, waitAndUpdate } from '../../../helpers/testUtils';
it('renders', () => {
const inner = () => <div />;
@@ -64,7 +64,6 @@ it('submits', async () => {
expect(onSubmit).toBeCalled();
expect(wrapper).toMatchSnapshot();
- await new Promise(setImmediate);
- wrapper.update();
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/components/lazyLoad.tsx b/server/sonar-web/src/main/js/components/lazyLoad.tsx
index f12b4734b6f..34f6de91d6b 100644
--- a/server/sonar-web/src/main/js/components/lazyLoad.tsx
+++ b/server/sonar-web/src/main/js/components/lazyLoad.tsx
@@ -19,13 +19,15 @@
*/
import * as React from 'react';
-interface Loader {
- (): Promise<{ default: React.ComponentClass }>;
+type ReactComponent<P> = React.ComponentClass<P> | React.StatelessComponent<P>;
+
+interface Loader<P> {
+ (): Promise<{ default: ReactComponent<P> }>;
}
-export function lazyLoad(loader: Loader) {
+export function lazyLoad<P>(loader: Loader<P>) {
interface State {
- Component?: React.ComponentClass;
+ Component?: ReactComponent<P>;
}
// use `React.Component`, not `React.PureComponent` to always re-render
@@ -43,7 +45,7 @@ export function lazyLoad(loader: Loader) {
this.mounted = false;
}
- receiveComponent = (Component: React.ComponentClass) => {
+ receiveComponent = (Component: ReactComponent<P>) => {
if (this.mounted) {
this.setState({ Component });
}
diff --git a/server/sonar-web/src/main/js/helpers/testUtils.ts b/server/sonar-web/src/main/js/helpers/testUtils.ts
index 25730a2e4b1..5e7ad5f1d4c 100644
--- a/server/sonar-web/src/main/js/helpers/testUtils.ts
+++ b/server/sonar-web/src/main/js/helpers/testUtils.ts
@@ -87,3 +87,8 @@ const { intl } = intlProvider.getChildContext();
export function shallowWithIntl(node: React.ReactElement<any>, options: ShallowRendererProps = {}) {
return shallow(node, { ...options, context: { intl, ...options.context } });
}
+
+export async function waitAndUpdate(wrapper: ShallowWrapper<any, any> | ReactWrapper<any, any>) {
+ await new Promise(setImmediate);
+ wrapper.update();
+}