Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

Commit

Permalink
MM-13566 - Throw an error accessing Set.length or Map.length in dev m…
Browse files Browse the repository at this point in the history
…ode (#2545)
  • Loading branch information
alifarooq0 authored and hmhealey committed Mar 28, 2019
1 parent d64400e commit af4b07b
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 13 deletions.
9 changes: 1 addition & 8 deletions actions/diagnostics_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
// See LICENSE.txt for license information.

import {Client4} from 'mattermost-redux/client';
import {getConfig} from 'mattermost-redux/selectors/entities/general';

import store from 'stores/redux_store.jsx';
import {isDevMode} from 'utils/utils';

const SUPPORTS_CLEAR_MARKS = isSupported([performance.clearMarks]);
const SUPPORTS_MARK = isSupported([performance.mark]);
Expand Down Expand Up @@ -103,9 +102,3 @@ function isSupported(checks) {
}
return true;
}

function isDevMode() {
const config = getConfig(store.getState());

return config.EnableDeveloper === 'true';
}
5 changes: 5 additions & 0 deletions components/root/root.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import loadCreateTeam from 'bundle-loader?lazy!components/create_team';
import loadMfa from 'bundle-loader?lazy!components/mfa/mfa_controller';
import store from 'stores/redux_store.jsx';
import {getSiteURL} from 'utils/url.jsx';
import {enableDevModeFeatures, isDevMode} from 'utils/utils';

const CreateTeam = makeAsyncComponent(loadCreateTeam);
const ErrorPage = makeAsyncComponent(loadErrorPage);
Expand Down Expand Up @@ -142,6 +143,10 @@ export default class Root extends React.Component {
}

onConfigLoaded = () => {
if (isDevMode()) {
enableDevModeFeatures();
}

const segmentKey = Constants.DIAGNOSTICS_SEGMENT_KEY;
const diagnosticId = this.props.diagnosticId;

Expand Down
53 changes: 53 additions & 0 deletions components/root/root.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {shallow} from 'enzyme';

import Root from 'components/root/root';
import * as GlobalActions from 'actions/global_actions.jsx';
import * as Utils from 'utils/utils';

jest.mock('fastclick', () => ({
attach: () => {}, // eslint-disable-line no-empty-function
Expand All @@ -19,6 +20,12 @@ jest.mock('actions/global_actions', () => ({
redirectUserToDefaultTeam: jest.fn(),
}));

jest.mock('utils/utils', () => ({
localizeMessage: () => {},
isDevMode: jest.fn(),
enableDevModeFeatures: jest.fn(),
}));

describe('components/Root', () => {
const baseProps = {
diagnosticsEnabled: true,
Expand Down Expand Up @@ -96,4 +103,50 @@ describe('components/Root', () => {

shallow(<MockedRoot {...props}/>);
});

test('should load config and enable dev mode features', () => {
const props = {
...baseProps,
actions: {
...baseProps.actions,
loadMeAndConfig: jest.fn(async () => [{}, {}, {}]),
},
};
Utils.isDevMode.mockReturnValue(true);

const wrapper = shallow(<Root {...props}/>);

expect(props.actions.loadMeAndConfig).toHaveBeenCalledTimes(1);

// Must be invoked in onConfigLoaded
expect(Utils.isDevMode).not.toHaveBeenCalled();
expect(Utils.enableDevModeFeatures).not.toHaveBeenCalled();

wrapper.instance().onConfigLoaded();
expect(Utils.isDevMode).toHaveBeenCalledTimes(1);
expect(Utils.enableDevModeFeatures).toHaveBeenCalledTimes(1);
});

test('should load config and not enable dev mode features', () => {
const props = {
...baseProps,
actions: {
...baseProps.actions,
loadMeAndConfig: jest.fn(async () => [{}, {}, {}]),
},
};
Utils.isDevMode.mockReturnValue(false);

const wrapper = shallow(<Root {...props}/>);

expect(props.actions.loadMeAndConfig).toHaveBeenCalledTimes(1);

// Must be invoked in onConfigLoaded
expect(Utils.isDevMode).not.toHaveBeenCalled();
expect(Utils.enableDevModeFeatures).not.toHaveBeenCalled();

wrapper.instance().onConfigLoaded();
expect(Utils.isDevMode).toHaveBeenCalledTimes(1);
expect(Utils.enableDevModeFeatures).not.toHaveBeenCalled();
});
});
7 changes: 2 additions & 5 deletions root.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {Router, Route} from 'react-router-dom';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {logError} from 'mattermost-redux/actions/errors';
import PDFJS from 'pdfjs-dist';

Expand All @@ -17,7 +16,7 @@ import 'sass/styles.scss';
import 'katex/dist/katex.min.css';

import {browserHistory} from 'utils/browser_history';
import {setCSRFFromCookie} from 'utils/utils';
import {isDevMode, setCSRFFromCookie} from 'utils/utils';
import {makeAsyncComponent} from 'components/async_load';
import store from 'stores/redux_store.jsx';
import loadRoot from 'bundle-loader?lazy!components/root';
Expand All @@ -42,9 +41,7 @@ function preRenderSetup(callwhendone) {
req.setRequestHeader('Content-Type', 'application/json');
req.send(JSON.stringify(l));

const state = store.getState();
const config = getConfig(state);
if (config.EnableDeveloper === 'true') {
if (isDevMode()) {
store.dispatch(logError({type: 'developer', message: 'DEVELOPER MODE: A JavaScript error has occurred. Please use the JavaScript console to capture and report the error (row: ' + line + ' col: ' + column + ').'}, true));
}
};
Expand Down
28 changes: 28 additions & 0 deletions utils/utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {FormattedMessage} from 'react-intl';
import {Client4} from 'mattermost-redux/client';
import {Posts} from 'mattermost-redux/constants';
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getTeammateNameDisplaySetting, getBool} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
import {
Expand Down Expand Up @@ -1649,3 +1650,30 @@ export function setCSRFFromCookie() {
}
}
}

/**
* Returns true if in dev mode, false otherwise.
*/
export function isDevMode() {
const config = getConfig(store.getState());
return config.EnableDeveloper === 'true';
}

/**
* Enables dev mode features.
*/
export function enableDevModeFeatures() {
/*eslint no-extend-native: ["error", { "exceptions": ["Set", "Map"] }]*/
Object.defineProperty(Set.prototype, 'length', {
configurable: true, // needed for testing
get: () => {
throw new Error('Set.length is not supported. Use Set.size instead.');
},
});
Object.defineProperty(Map.prototype, 'length', {
configurable: true, // needed for testing
get: () => {
throw new Error('Map.length is not supported. Use Map.size instead.');
},
});
}
136 changes: 136 additions & 0 deletions utils/utils.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -569,3 +569,139 @@ describe('Utils.localizeMessage', () => {
});
});
});

describe('Utils.isDevMode', () => {
const originalGetState = store.getState;

afterAll(() => {
store.getState = originalGetState;
});

describe('dev mode off', () => {
beforeAll(() => {
store.getState = () => ({
entities: {
general: {
config: {},
},
},
});
});

test('with missing EnableDeveloper field', () => {
expect(Utils.isDevMode()).toEqual(false);
});

test('with EnableDeveloper field set to false', () => {
store.getState = () => ({
entities: {
general: {
config: {
EnableDeveloper: 'false',
},
},
},
});
expect(Utils.isDevMode()).toEqual(false);
});

test('with EnableDeveloper field set to false bool', () => {
store.getState = () => ({
entities: {
general: {
config: {
EnableDeveloper: false,
},
},
},
});
expect(Utils.isDevMode()).toEqual(false);
});

test('with EnableDeveloper field set to true bool', () => {
store.getState = () => ({
entities: {
general: {
config: {
EnableDeveloper: true,
},
},
},
});
expect(Utils.isDevMode()).toEqual(false);
});

test('with EnableDeveloper field set to null', () => {
store.getState = () => ({
entities: {
general: {
config: {
EnableDeveloper: null,
},
},
},
});
expect(Utils.isDevMode()).toEqual(false);
});
});

describe('dev mode on', () => {
beforeAll(() => {
store.getState = () => ({
entities: {
general: {
config: {},
},
},
});
});

test('with EnableDeveloper field set to true text', () => {
store.getState = () => ({
entities: {
general: {
config: {
EnableDeveloper: 'true',
},
},
},
});
expect(Utils.isDevMode()).toEqual(true);
});
});
});

describe('Utils.enableDevModeFeatures', () => {
const cleanUp = () => {
delete Map.prototype.length;
delete Set.prototype.length;
};

beforeEach(cleanUp);
afterEach(cleanUp);

describe('with DevModeFeatures', () => {
beforeEach(cleanUp);
afterEach(cleanUp);

test('invoke Map.Length', () => {
Utils.enableDevModeFeatures();
expect(() => new Map().length).toThrow(Error);
});

test('invoke Set.Length', () => {
Utils.enableDevModeFeatures();
expect(() => new Set().length).toThrow(Error);
});
});

describe('without DevModeFeatures', () => {
test('invoke Map.Length', () => {
expect(new Map().length).toEqual(undefined);
});

test('invoke Set.Length', () => {
expect(new Set().length).toEqual(undefined);
});
});
});

0 comments on commit af4b07b

Please sign in to comment.