Skip to content

Commit 93286c2

Browse files
authored
Consolidate app initialization (#2053)
1 parent e3c7d59 commit 93286c2

File tree

11 files changed

+176
-53
lines changed

11 files changed

+176
-53
lines changed

__mocks__/firebase/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ export const auth = Object.assign(() => ({}), {
1212
});
1313

1414
export const initializeApp = constant({});
15+
16+
export const remoteConfig = constant({});

__mocks__/i18next.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
export default {
2+
init: jest.fn(),
3+
24
t(key) {
35
if (key === 'utility.or') {
46
return ' or ';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const install = jest.fn();

src/application.js

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,13 @@ import './init/DOMParserShim';
66

77
import React from 'react';
88
import ReactDOM from 'react-dom';
9-
import Immutable from 'immutable';
10-
import installDevTools from 'immutable-devtools';
11-
import {install as installOfflinePlugin} from 'offline-plugin/runtime';
129

13-
import {bugsnagClient} from './util/bugsnag';
10+
import init from './init';
1411
import Application from './components/Application';
15-
import initI18n from './util/initI18n';
16-
import {initMixpanel} from './clients/mixpanel';
1712

18-
installDevTools(Immutable);
19-
installOfflinePlugin({
20-
onUpdateFailed() {
21-
bugsnagClient.notify('ServiceWorker update failed');
22-
},
23-
});
24-
25-
initI18n();
26-
initMixpanel();
13+
const {store} = init();
2714

2815
ReactDOM.render(
29-
React.createElement(Application),
16+
React.createElement(Application, {store}),
3017
document.getElementById('main'),
3118
);

src/components/Application.jsx

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
import mapValues from 'lodash-es/mapValues';
22
import React from 'react';
33
import {Provider} from 'react-redux';
4+
import PropTypes from 'prop-types';
45

56
import bowser from '../services/bowser';
6-
import createApplicationStore from '../createApplicationStore';
7-
import {ErrorBoundary, includeStoreInBugReports} from '../util/bugsnag';
7+
import {ErrorBoundary} from '../util/bugsnag';
88
import supportedBrowsers from '../../config/browsers.json';
99
import Workspace from '../containers/Workspace';
1010

1111
import BrowserError from './BrowserError';
1212
import IEBrowserError from './IEBrowserError';
1313

14-
class Application extends React.Component {
15-
constructor() {
16-
super();
17-
const store = createApplicationStore();
18-
this.state = {store};
19-
includeStoreInBugReports(store);
20-
}
21-
14+
export default class Application extends React.Component {
2215
_isIEOrEdge() {
2316
return bowser.some(['Internet Explorer', 'Microsoft Edge']);
2417
}
@@ -43,12 +36,14 @@ class Application extends React.Component {
4336

4437
return (
4538
<ErrorBoundary>
46-
<Provider store={this.state.store}>
39+
<Provider store={this.props.store}>
4740
<Workspace />
4841
</Provider>
4942
</ErrorBoundary>
5043
);
5144
}
5245
}
5346

54-
export default Application;
47+
Application.propTypes = {
48+
store: PropTypes.object.isRequired,
49+
};

src/components/Workspace.jsx

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@ import i18next from 'i18next';
1515
import classnames from 'classnames';
1616

1717
import prefix from '../services/inlineStylePrefixer';
18-
import {getQueryParameters, setQueryParameters} from '../util/queryParams';
1918
import {LANGUAGES} from '../util/editor';
2019
import {RIGHT_COLUMN_COMPONENTS} from '../util/ui';
21-
import {dehydrateProject, rehydrateProject} from '../clients/localStorage';
20+
import {dehydrateProject} from '../clients/localStorage';
2221

2322
import {isPristineProject} from '../util/projectUtils';
2423

@@ -48,21 +47,6 @@ export default class Workspace extends React.Component {
4847
}
4948

5049
componentDidMount() {
51-
const {onApplicationLoaded} = this.props;
52-
const {gistId, snapshotKey, isExperimental} = getQueryParameters(
53-
location.search,
54-
);
55-
const rehydratedProject = rehydrateProject();
56-
57-
setQueryParameters({isExperimental});
58-
59-
onApplicationLoaded({
60-
snapshotKey,
61-
gistId,
62-
isExperimental,
63-
rehydratedProject,
64-
});
65-
6650
addEventListener('beforeunload', this._handleUnload);
6751
}
6852

@@ -346,7 +330,6 @@ Workspace.propTypes = {
346330
resizableFlexRefs: PropTypes.array.isRequired,
347331
shouldRenderOutput: PropTypes.bool.isRequired,
348332
title: PropTypes.string.isRequired,
349-
onApplicationLoaded: PropTypes.func.isRequired,
350333
onClickInstructionsEditButton: PropTypes.func.isRequired,
351334
onComponentToggle: PropTypes.func.isRequired,
352335
onResizableFlexDividerDrag: PropTypes.func.isRequired,

src/containers/Workspace.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
} from '../selectors';
1717
import {
1818
toggleComponent,
19-
applicationLoaded,
2019
startDragColumnDivider,
2120
stopDragColumnDivider,
2221
startEditingInstructions,
@@ -52,10 +51,6 @@ function mapStateToProps(state) {
5251

5352
function mapDispatchToProps(dispatch) {
5453
return {
55-
onApplicationLoaded(payload) {
56-
dispatch(applicationLoaded(payload));
57-
},
58-
5954
onComponentToggle(projectKey, componentName) {
6055
dispatch(toggleComponent(projectKey, componentName));
6156
},

src/init/__tests__/index.test.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import findLast from 'lodash-es/findLast';
2+
import get from 'lodash-es/get';
3+
import mixpanel from 'mixpanel-browser';
4+
import uuid from 'uuid/v4';
5+
6+
import {rehydrateProject} from '../../clients/localStorage';
7+
import createApplicationStore from '../../createApplicationStore';
8+
import config from '../../config';
9+
10+
import {applicationLoaded} from '../../actions';
11+
12+
import {firebaseProjectFactory} from '../../../__factories__/data/firebase';
13+
14+
import i18next from 'i18next';
15+
16+
import init from '..';
17+
18+
jest.mock('../../clients/localStorage');
19+
jest.mock('../../clients/firebase');
20+
jest.mock('../../createApplicationStore');
21+
22+
describe('init()', () => {
23+
let dispatch;
24+
let store;
25+
26+
beforeEach(() => {
27+
dispatch = jest.fn();
28+
store = {dispatch};
29+
createApplicationStore.mockReturnValue(store);
30+
});
31+
32+
test('initializes i18next', () => {
33+
init();
34+
expect(i18next.init).toHaveBeenCalledWith(expect.anything());
35+
});
36+
37+
test('initializes mixpanel', async () => {
38+
init();
39+
await new Promise(resolve => {
40+
mixpanel.init.mockImplementation(resolve);
41+
});
42+
expect(mixpanel.init).toHaveBeenCalledWith(config.mixpanelToken);
43+
});
44+
45+
describe('applicationLoaded', () => {
46+
let dispatchInvoked;
47+
48+
beforeEach(() => {
49+
dispatchInvoked = new Promise(resolve => {
50+
dispatch.mockImplementation(resolve);
51+
});
52+
});
53+
54+
async function dispatchedAction() {
55+
await dispatchInvoked;
56+
return get(
57+
findLast(
58+
dispatch.mock.calls,
59+
([action]) => action.type === applicationLoaded.toString(),
60+
),
61+
['0'],
62+
);
63+
}
64+
65+
test('dispatches action', async () => {
66+
init();
67+
68+
const action = await dispatchedAction();
69+
expect(action).toBeDefined();
70+
});
71+
72+
test('reads gist ID from query and removes it', async () => {
73+
history.pushState(null, '', '/?gist=12345');
74+
init();
75+
const {
76+
payload: {gistId},
77+
} = await dispatchedAction();
78+
expect(gistId).toBe('12345');
79+
expect(location.search).toBeFalsy();
80+
});
81+
82+
test('reads snapshot key from query and removes it', async () => {
83+
const snapshotKey = uuid();
84+
history.pushState(null, '', `/?snapshot=${snapshotKey}`);
85+
init();
86+
const {payload} = await dispatchedAction();
87+
expect(payload.snapshotKey).toEqual(snapshotKey);
88+
});
89+
90+
test('reads experimental mode from query and leaves it', async () => {
91+
history.pushState(null, '', '/?experimental');
92+
init();
93+
const {
94+
payload: {isExperimental},
95+
} = await dispatchedAction();
96+
expect(isExperimental).toBe(true);
97+
expect(location.search).toBe('?experimental');
98+
});
99+
100+
test('rehydrates project', async () => {
101+
const project = firebaseProjectFactory.build();
102+
rehydrateProject.mockReturnValue(project);
103+
init();
104+
const {
105+
payload: {rehydratedProject},
106+
} = await dispatchedAction();
107+
expect(rehydratedProject).toBe(project);
108+
});
109+
});
110+
});

src/init/index.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Immutable from 'immutable';
2+
import installDevTools from 'immutable-devtools';
3+
import {install as installOfflinePlugin} from 'offline-plugin/runtime';
4+
5+
import {initMixpanel} from '../clients/mixpanel';
6+
import createApplicationStore from '../createApplicationStore';
7+
import {bugsnagClient, includeStoreInBugReports} from '../util/bugsnag';
8+
import {getQueryParameters, setQueryParameters} from '../util/queryParams';
9+
import {rehydrateProject} from '../clients/localStorage';
10+
import {applicationLoaded} from '../actions';
11+
12+
import initI18n from './initI18n';
13+
14+
async function initApplication(store) {
15+
const {gistId, snapshotKey, isExperimental} = getQueryParameters(
16+
location.search,
17+
);
18+
setQueryParameters({isExperimental});
19+
const rehydratedProject = rehydrateProject();
20+
21+
store.dispatch(
22+
applicationLoaded({
23+
snapshotKey,
24+
gistId,
25+
isExperimental,
26+
rehydratedProject,
27+
}),
28+
);
29+
}
30+
31+
export default function init() {
32+
const store = createApplicationStore();
33+
includeStoreInBugReports(store);
34+
35+
initI18n();
36+
initMixpanel();
37+
38+
initApplication(store);
39+
40+
installDevTools(Immutable);
41+
installOfflinePlugin({
42+
onUpdateFailed() {
43+
bugsnagClient.notify('ServiceWorker update failed');
44+
},
45+
});
46+
47+
return {store};
48+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import i18next from 'i18next';
22

33
import resources from '../../locales';
44

5-
import applyCustomI18nFormatters from './i18nFormatting';
5+
import applyCustomI18nFormatters from '../util/i18nFormatting';
66

77
export default function initI18n() {
88
i18next.init({

0 commit comments

Comments
 (0)