diff --git a/.eslintrc b/.eslintrc index b19b854b..039f3239 100644 --- a/.eslintrc +++ b/.eslintrc @@ -18,6 +18,7 @@ "func-style": "off", "react/prop-types": "off", "react/jsx-max-props-per-line": "off", - "jsx-a11y/no-marquee": "off" + "jsx-a11y/no-marquee": "off", + "no-return-assign": "off", } } diff --git a/client/package-scripts.js b/client/package-scripts.js index 16afae39..ae7d67b3 100644 --- a/client/package-scripts.js +++ b/client/package-scripts.js @@ -1,12 +1,25 @@ -const {series, rimraf, commonTags, crossEnv} = require('nps-utils') +const {series, rimraf, commonTags, crossEnv, concurrent} = require('nps-utils') module.exports = { scripts: { dev: crossEnv('PORT=8080 react-scripts start'), build: 'react-scripts build', default: 'pushstate-server build', test: { - default: 'jest --coverage', - watch: 'jest --watch', + default: concurrent.nps('test.unit', 'test.integration'), + unit: { + default: testEnv( + 'jest --config=tests/jest.config.unit.json --coverage' + ), + watch: testEnv('jest --config=tests/jest.config.unit.json --watch'), + }, + integration: { + default: testEnv( + 'jest --config=tests/jest.config.integration.json --coverage' + ), + watch: testEnv( + 'jest --config=tests/jest.config.integration.json --watch' + ), + }, }, postinstall: { description: commonTags.oneLine` @@ -22,6 +35,10 @@ module.exports = { }, } +function testEnv(script) { + return crossEnv(`NODE_ENV=test ${script}`) +} + // this is not transpiled /* eslint diff --git a/client/package.json b/client/package.json index a111c9d9..9826c46c 100644 --- a/client/package.json +++ b/client/package.json @@ -6,10 +6,12 @@ "babel-jest": "^18.0.0", "babel-preset-react-app": "^2.1.0", "cross-env": "^3.1.4", + "enzyme": "^2.7.1", "jest": "^18.1.0", "nps": "^5.0.4", "nps-utils": "^1.2.0", "pushstate-server": "^2.2.1", + "react-addons-test-utils": "^15.4.2", "react-scripts": "^0.9.4" }, "license": "MIT", @@ -31,27 +33,5 @@ "test": "nps test", "postinstall": "nps postinstall" }, - "author": "Thinkster (https://github.com/gothinkster)", - "jest": { - "testEnvironment": "jest-environment-jsdom", - "transform": { - "^.+\\.js$": "/node_modules/babel-jest", - "^.+\\.css$": "/config/jest/cssTransform.js", - "^(?!.*\\.(js|css|json)$)": "/config/jest/fileTransform.js" - }, - "transformIgnorePatterns": [ - "[/\\\\]node_modules[/\\\\].+\\.jsx$" - ], - "collectCoverageFrom": [ - "src/**" - ], - "coverageThreshold": { - "global": { - "statements": 0, - "branches": 0, - "functions": 0, - "lines": 0 - } - } - } + "author": "Thinkster (https://github.com/gothinkster)" } diff --git a/client/src/index.js b/client/src/index.js index 2f68a055..9370ba7f 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -9,7 +9,7 @@ import { hashHistory, } from 'react-router' -import store from './store' +import createStore from './store' import App from './screens/app' import Article from './screens/article' import Editor from './screens/editor' @@ -21,7 +21,7 @@ import Register from './screens/register' import Settings from './screens/settings' ReactDOM.render( - + next => action => { if (!skipTracking && currentState.viewChangeCounter !== currentView) { return } - console.log('RESULT', res) - action.payload = res + action.payload = res || {} store.dispatch({type: 'ASYNC_END', promise: action.payload}) store.dispatch(action) }, @@ -23,7 +22,6 @@ const promiseMiddleware = store => next => action => { if (!skipTracking && currentState.viewChangeCounter !== currentView) { return } - console.log('ERROR', error) action.error = true action.payload = error.response.body if (!action.skipTracking) { diff --git a/client/src/reducers/auth.js b/client/src/reducers/auth.js index 2b0c2e08..3ff9d3b9 100644 --- a/client/src/reducers/auth.js +++ b/client/src/reducers/auth.js @@ -1,4 +1,4 @@ -export default (state = {}, action) => { +export default (state = {inProgress: false, errors: null}, action) => { switch (action.type) { case 'LOGIN': case 'REGISTER': @@ -16,8 +16,6 @@ export default (state = {}, action) => { } else { return state } - case 'UPDATE_FIELD_AUTH': - return {...state, [action.key]: action.value} default: return state } diff --git a/client/src/screens/__tests__/login.js b/client/src/screens/__tests__/login.js new file mode 100644 index 00000000..a476d93d --- /dev/null +++ b/client/src/screens/__tests__/login.js @@ -0,0 +1,51 @@ +import React from 'react' +import {mount} from 'enzyme' +import {Component as Login} from '../login' + +test('renders login form by default', () => { + const wrapper = render() + expect(isSubmitButtonDisabled(wrapper)).toBe(false) +}) + +test('disabled button when inProgress', () => { + const wrapper = render({inProgress: true}) + expect(isSubmitButtonDisabled(wrapper)).toBe(true) +}) + +test('shows list of errors when there are errors', () => { + const wrapper = render({ + errors: { + 'some-error': 'there was some error', + 'some-other-error': 'there was some other error', + }, + }) + expect(getErrors(wrapper)).toEqual([ + 'some-error there was some error', + 'some-other-error there was some other error', + ]) +}) + +function render(props = {}) { + const propsToUse = { + onSubmit() {}, + onUnload() {}, + inProgress: false, + errors: {}, + ...props, + } + return mount() +} + +function isSubmitButtonDisabled(wrapper) { + const button = getSubmitButton(wrapper).getNode() + return button.disabled +} + +function getSubmitButton(wrapper) { + return wrapper.find('button[type="submit"]') +} + +function getErrors(wrapper) { + return Array.from(wrapper.find('.error-messages li').getNodes()) + .map(n => n.textContent) +} diff --git a/client/src/screens/login.js b/client/src/screens/login.js index ab441207..8521a286 100644 --- a/client/src/screens/login.js +++ b/client/src/screens/login.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, {PropTypes} from 'react' import {Link} from 'react-router' import {connect} from 'react-redux' import agent from '../shared/agent' @@ -7,24 +7,23 @@ import ListErrors from '../shared/components/list-errors' const mapStateToProps = state => ({...state.auth}) const mapDispatchToProps = dispatch => ({ - onChangeEmail: value => - dispatch({type: 'UPDATE_FIELD_AUTH', key: 'email', value}), - onChangePassword: value => - dispatch({type: 'UPDATE_FIELD_AUTH', key: 'password', value}), onSubmit: (email, password) => dispatch({type: 'LOGIN', payload: agent.Auth.login(email, password)}), onUnload: () => dispatch({type: 'LOGIN_PAGE_UNLOADED'}), }) class Login extends React.Component { - constructor() { - super() - this.changeEmail = ev => this.props.onChangeEmail(ev.target.value) - this.changePassword = ev => this.props.onChangePassword(ev.target.value) - this.submitForm = (email, password) => ev => { - ev.preventDefault() - this.props.onSubmit(email, password) - } + static propTypes = { + onSubmit: PropTypes.func.isRequired, + onUnload: PropTypes.func.isRequired, + inProgress: PropTypes.bool.isRequired, + + // forwarding these props + errors: PropTypes.any, + } + submitForm = ev => { + ev.preventDefault() + this.props.onSubmit(this._email.value, this._password.value) } componentWillUnmount() { @@ -32,8 +31,6 @@ class Login extends React.Component { } render() { - const email = this.props.email - const password = this.props.password return (
@@ -49,7 +46,7 @@ class Login extends React.Component { -
+
@@ -57,8 +54,7 @@ class Login extends React.Component { className="form-control form-control-lg" type="email" placeholder="Email" - value={email} - onChange={this.changeEmail} + ref={node => this._email = node} />
@@ -67,8 +63,7 @@ class Login extends React.Component { className="form-control form-control-lg" type="password" placeholder="Password" - value={password} - onChange={this.changePassword} + ref={node => this._password = node} />
@@ -91,4 +86,5 @@ class Login extends React.Component { } } +export {Login as Component} export default connect(mapStateToProps, mapDispatchToProps)(Login) diff --git a/client/src/screens/register.js b/client/src/screens/register.js index 602dab5c..ff3bb95c 100644 --- a/client/src/screens/register.js +++ b/client/src/screens/register.js @@ -7,12 +7,6 @@ import ListErrors from '../shared/components/list-errors' const mapStateToProps = state => ({...state.auth}) const mapDispatchToProps = dispatch => ({ - onChangeEmail: value => - dispatch({type: 'UPDATE_FIELD_AUTH', key: 'email', value}), - onChangePassword: value => - dispatch({type: 'UPDATE_FIELD_AUTH', key: 'password', value}), - onChangeUsername: value => - dispatch({type: 'UPDATE_FIELD_AUTH', key: 'username', value}), onSubmit: (username, email, password) => { const payload = agent.Auth.register(username, email, password) dispatch({type: 'REGISTER', payload}) @@ -21,15 +15,13 @@ const mapDispatchToProps = dispatch => ({ }) class Register extends React.Component { - constructor() { - super() - this.changeEmail = ev => this.props.onChangeEmail(ev.target.value) - this.changePassword = ev => this.props.onChangePassword(ev.target.value) - this.changeUsername = ev => this.props.onChangeUsername(ev.target.value) - this.submitForm = (username, email, password) => ev => { - ev.preventDefault() - this.props.onSubmit(username, email, password) - } + submitForm = ev => { + ev.preventDefault() + this.props.onSubmit( + this._username.value, + this._email.value, + this._password.value, + ) } componentWillUnmount() { @@ -37,10 +29,6 @@ class Register extends React.Component { } render() { - const email = this.props.email - const password = this.props.password - const username = this.props.username - return (
@@ -56,7 +44,7 @@ class Register extends React.Component { - +
@@ -64,8 +52,7 @@ class Register extends React.Component { className="form-control form-control-lg" type="text" placeholder="Username" - value={this.props.username} - onChange={this.changeUsername} + ref={node => this._username = node} data-e2e="username" />
@@ -75,8 +62,7 @@ class Register extends React.Component { className="form-control form-control-lg" type="email" placeholder="Email" - value={this.props.email} - onChange={this.changeEmail} + ref={node => this._email = node} data-e2e="email" />
@@ -86,8 +72,7 @@ class Register extends React.Component { className="form-control form-control-lg" type="password" placeholder="Password" - value={this.props.password} - onChange={this.changePassword} + ref={node => this._password = node} data-e2e="password" /> diff --git a/client/src/store.js b/client/src/store.js index 955e0141..991497fe 100644 --- a/client/src/store.js +++ b/client/src/store.js @@ -4,7 +4,9 @@ import {promiseMiddleware, localStorageMiddleware} from './middleware' import reducer from './reducer' const getMiddleware = () => { - if (process.env.NODE_ENV === 'production') { + const isProd = process.env.NODE_ENV === 'production' + const isTest = process.env.NODE_ENV === 'test' + if (isProd || isTest) { return applyMiddleware(promiseMiddleware, localStorageMiddleware) } else { // Enable additional logging in non-production environments. @@ -16,6 +18,8 @@ const getMiddleware = () => { } } -const store = createStore(reducer, getMiddleware()) +export default createStoreWithState -export default store +function createStoreWithState(state = {}) { + return createStore(reducer, state, getMiddleware()) +} diff --git a/client/tests/integration/__mocks__/axios.js b/client/tests/integration/__mocks__/axios.js new file mode 100644 index 00000000..c7c27028 --- /dev/null +++ b/client/tests/integration/__mocks__/axios.js @@ -0,0 +1,23 @@ +const defaultResponse = {data: {}} + +const __mock = { + reset() { + Object.assign(__mock.instance, { + get: jest.fn(() => Promise.resolve(defaultResponse)), + put: jest.fn(() => Promise.resolve(defaultResponse)), + post: jest.fn(() => Promise.resolve(defaultResponse)), + delete: jest.fn(() => Promise.resolve(defaultResponse)), + defaults: {headers: {common: {}}}, + }) + }, + instance: {}, +} + +__mock.reset() + +module.exports = { + __mock, + create() { + return __mock.instance + }, +} diff --git a/client/tests/integration/app.js b/client/tests/integration/app.js deleted file mode 100644 index e69de29b..00000000 diff --git a/client/tests/integration/helpers/render-with-redux.js b/client/tests/integration/helpers/render-with-redux.js new file mode 100644 index 00000000..e99a8a3c --- /dev/null +++ b/client/tests/integration/helpers/render-with-redux.js @@ -0,0 +1,16 @@ +import React from 'react' +import {Provider} from 'react-redux' +import {mount} from 'enzyme' +import createStore from '../../../src/store' + +export default renderWithState + +function renderWithState(state, children) { + const store = createStore(state) + const wrapper = mount( + + {children} + , + ) + return {store, wrapper} +} diff --git a/client/tests/integration/login.test.js b/client/tests/integration/login.test.js new file mode 100644 index 00000000..f94dcb36 --- /dev/null +++ b/client/tests/integration/login.test.js @@ -0,0 +1,40 @@ +import React from 'react' +import axiosMock from 'axios' +import Login from '../../src/screens/login' +import renderWithState from './helpers/render-with-redux' + +const flushAllPromises = () => new Promise(resolve => setImmediate(resolve)) + +test('logs in when the form is submitted', async () => { + const token = 'Luke, I am your father' + const user = {password: 'my-password', email: 'me@example.com'} + axiosMock.__mock.instance.post.mockImplementation(() => { + return Promise.resolve({data: {user: {token}}}) + }) + + const {wrapper} = renderWithState({}, ) + wrapper.find('input[type="email"]').node.value = user.email + wrapper.find('input[type="password"]').node.value = user.password + wrapper.find('form').simulate('submit') + await flushAllPromises() + expect(axiosMock.__mock.instance.post).toHaveBeenCalledTimes(1) + expect(axiosMock.__mock.instance.post).toHaveBeenCalledWith('/users/login', { + user, + }) + expect(window.localStorage.setItem).toHaveBeenCalledTimes(1) + expect(window.localStorage.setItem).toHaveBeenCalledWith('jwt', token) +}) + +/* + + + + + + + + + + + + */ diff --git a/client/tests/jest.config.integration.json b/client/tests/jest.config.integration.json new file mode 100644 index 00000000..dd85c8f1 --- /dev/null +++ b/client/tests/jest.config.integration.json @@ -0,0 +1,32 @@ +{ + "setupFiles": [ + "./tests/setup-integration.js" + ], + "testRegex": "tests/integration/.*.js$", + "testPathIgnorePatterns": [ + "/node_modules/", + "__mocks__", + "helpers" + ], + "testEnvironment": "jest-environment-jsdom", + "transform": { + "^.+\\.js$": "/node_modules/babel-jest", + "^.+\\.css$": "/config/jest/cssTransform.js", + "^(?!.*\\.(js|css|json)$)": "/config/jest/fileTransform.js" + }, + "transformIgnorePatterns": [ + "[/\\\\]node_modules[/\\\\].+\\.js$" + ], + "collectCoverageFrom": [ + "src/**/*.js" + ], + "coverageThreshold": { + "global": { + "statements": 10, + "branches": 10, + "functions": 10, + "lines": 10 + } + }, + "coverageDirectory": "coverage/integration" +} diff --git a/client/tests/jest.config.unit.json b/client/tests/jest.config.unit.json new file mode 100644 index 00000000..85958174 --- /dev/null +++ b/client/tests/jest.config.unit.json @@ -0,0 +1,29 @@ +{ + "testRegex": "src/.*__tests__/.*.js$", + "testPathIgnorePatterns": [ + "/node_modules/", + "__mocks__", + "helpers" + ], + "testEnvironment": "jest-environment-jsdom", + "transform": { + "^.+\\.js$": "/node_modules/babel-jest", + "^.+\\.css$": "/config/jest/cssTransform.js", + "^(?!.*\\.(js|css|json)$)": "/config/jest/fileTransform.js" + }, + "transformIgnorePatterns": [ + "[/\\\\]node_modules[/\\\\].+\\.js$" + ], + "collectCoverageFrom": [ + "src/**" + ], + "coverageThreshold": { + "global": { + "statements": 2, + "branches": 0.5, + "functions": 1, + "lines": 2 + } + }, + "coverageDirectory": "coverage/unit" +} \ No newline at end of file diff --git a/client/tests/setup-integration.js b/client/tests/setup-integration.js new file mode 100644 index 00000000..3fc00e62 --- /dev/null +++ b/client/tests/setup-integration.js @@ -0,0 +1,4 @@ +window.localStorage = { + setItem: jest.fn(), + getItem: jest.fn(), +} diff --git a/client/yarn.lock b/client/yarn.lock index 2c771635..20c14106 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -898,20 +898,13 @@ babel-register@^6.22.0, babel-register@^6.23.0: mkdirp "^0.5.1" source-map-support "^0.4.2" -babel-runtime@6.22.0: +babel-runtime@6.22.0, babel-runtime@^6.18.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.22.0.tgz#1cf8b4ac67c77a4ddb0db2ae1f74de52ac4ca611" dependencies: core-js "^2.4.0" regenerator-runtime "^0.10.0" -babel-runtime@^6.18.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.10.0" - babel-template@^6.16.0, babel-template@^6.22.0, babel-template@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.23.0.tgz#04d4f270adbb3aa704a8143ae26faa529238e638" @@ -1160,6 +1153,27 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +cheerio@^0.22.0: + version "0.22.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash.assignin "^4.0.9" + lodash.bind "^4.1.4" + lodash.defaults "^4.0.1" + lodash.filter "^4.4.0" + lodash.flatten "^4.2.0" + lodash.foreach "^4.3.0" + lodash.map "^4.4.0" + lodash.merge "^4.4.0" + lodash.pick "^4.2.1" + lodash.reduce "^4.4.0" + lodash.reject "^4.4.0" + lodash.some "^4.4.0" + chokidar@^1.0.0: version "1.6.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2" @@ -1544,7 +1558,7 @@ css-loader@0.26.1: postcss-modules-values "^1.1.0" source-list-map "^0.1.4" -css-select@^1.1.0: +css-select@^1.1.0, css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" dependencies: @@ -1695,6 +1709,13 @@ default-require-extensions@^1.0.0: dependencies: strip-bom "^2.0.0" +define-properties@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + dependencies: + foreach "^2.0.5" + object-keys "^1.0.8" + defined@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" @@ -1771,7 +1792,7 @@ dom-converter@~0.1: dependencies: utila "~0.3" -dom-serializer@0: +dom-serializer@0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" dependencies: @@ -1782,7 +1803,7 @@ domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" -domelementtype@1: +domelementtype@1, domelementtype@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" @@ -1796,13 +1817,19 @@ domhandler@2.1: dependencies: domelementtype "1" +domhandler@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" + dependencies: + domelementtype "1" + domutils@1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" dependencies: domelementtype "1" -domutils@1.5.1: +domutils@1.5.1, domutils@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" dependencies: @@ -1861,10 +1888,24 @@ enhanced-resolve@~0.9.0: memory-fs "^0.2.0" tapable "^0.1.8" -entities@~1.1.1: +entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" +enzyme@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-2.7.1.tgz#76370e1d99e91f73091bb8c4314b7c128cc2d621" + dependencies: + cheerio "^0.22.0" + function.prototype.name "^1.0.0" + is-subset "^0.1.1" + lodash "^4.17.2" + object-is "^1.0.1" + object.assign "^4.0.4" + object.entries "^1.0.3" + object.values "^1.0.3" + uuid "^2.0.3" + "errno@>=0.1.1 <0.2.0-0", errno@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" @@ -1877,6 +1918,23 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" +es-abstract@^1.6.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.0" + is-callable "^1.1.3" + is-regex "^1.0.3" + +es-to-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" + dependencies: + is-callable "^1.1.1" + is-date-object "^1.0.1" + is-symbol "^1.0.1" + es5-ext@^0.10.7, es5-ext@^0.10.8, es5-ext@~0.10.11, es5-ext@~0.10.2, es5-ext@~0.10.7: version "0.10.12" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047" @@ -2383,6 +2441,10 @@ for-own@^0.1.4: dependencies: for-in "^1.0.1" +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -2449,10 +2511,18 @@ fstream@^1.0.0, fstream@^1.0.2, fstream@~1.0.10: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.0.2: +function-bind@^1.0.2, function-bind@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" +function.prototype.name@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.0.0.tgz#5f523ca64e491a5f95aba80cc1e391080a14482e" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.0" + is-callable "^1.1.2" + gauge@~2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.3.tgz#1c23855f962f17b3ad3d0dc7443f304542edfe09" @@ -2708,6 +2778,17 @@ html-webpack-plugin@2.24.0: pretty-error "^2.0.2" toposort "^1.0.0" +htmlparser2@^3.9.1: + version "3.9.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" + dependencies: + domelementtype "^1.3.0" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^2.0.2" + htmlparser2@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" @@ -2883,12 +2964,20 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" +is-callable@^1.1.1, is-callable@^1.1.2, is-callable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" + is-ci@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" dependencies: ci-info "^1.0.0" +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + is-dotfile@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.2.tgz#2c132383f39199f8edc268ca01b9b007d205cc4d" @@ -2990,6 +3079,12 @@ is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" +is-regex@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + dependencies: + has "^1.0.1" + is-resolvable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" @@ -3000,12 +3095,20 @@ is-stream@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + is-svg@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" dependencies: html-comment-regex "^1.1.0" +is-symbol@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -3516,6 +3619,14 @@ lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" +lodash.assignin@^4.0.9: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" + +lodash.bind@^4.1.4: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" + lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -3531,6 +3642,22 @@ lodash.cond@^4.3.0: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" +lodash.defaults@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + +lodash.filter@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" + +lodash.flatten@^4.2.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + +lodash.foreach@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -3547,14 +3674,38 @@ lodash.keys@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" +lodash.map@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" + lodash.memoize@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" +lodash.merge@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" + +lodash.pick@^4.2.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + lodash.pickby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" +lodash.reduce@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" + +lodash.reject@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" + +lodash.some@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" + lodash.uniq@^4.3.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -3947,6 +4098,31 @@ object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" +object-is@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" + +object-keys@^1.0.10, object-keys@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + +object.assign@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.0" + object-keys "^1.0.10" + +object.entries@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.6.1" + function-bind "^1.1.0" + has "^1.0.1" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -3954,6 +4130,15 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" +object.values@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.6.1" + function-bind "^1.1.0" + has "^1.0.1" + on-finished@~2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.2.1.tgz#5c85c1cc36299f78029653f667f27b6b99ebc029" @@ -4587,6 +4772,13 @@ rc@~1.1.6: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-addons-test-utils@^15.4.2: + version "15.4.2" + resolved "https://registry.yarnpkg.com/react-addons-test-utils/-/react-addons-test-utils-15.4.2.tgz#93bcaa718fcae7360d42e8fb1c09756cc36302a2" + dependencies: + fbjs "^0.8.4" + object-assign "^4.1.0" + react-dev-utils@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-0.5.2.tgz#50d0b962d3a94b6c2e8f2011ed6468e4124bc410" @@ -5670,7 +5862,7 @@ utils-merge@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" -uuid@^2.0.1, uuid@^2.0.2: +uuid@^2.0.1, uuid@^2.0.2, uuid@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"