diff options
79 files changed, 1618 insertions, 821 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml index 1c60cbdb3..7c6da9d57 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -5,12 +5,14 @@ env: browser: true node: true es6: true + jest: true parser: babel-eslint plugins: - react - jsx-a11y +- import parserOptions: sourceType: module @@ -21,8 +23,14 @@ parserOptions: modules: true spread: true -rules: +settings: + import/extensions: + - .js + import/ignore: + - node_modules + - \\.(css|scss|json)$ +rules: brace-style: warn comma-dangle: - error @@ -125,3 +133,17 @@ rules: jsx-a11y/role-supports-aria-props: off jsx-a11y/scope: warn jsx-a11y/tabindex-no-positive: warn + + import/extensions: + - error + - always + - js: never + import/newline-after-import: error + import/no-extraneous-dependencies: + - error + - devDependencies: + - "config/webpack/**" + - "app/javascript/mastodon/test_setup.js" + - "app/javascript/**/__tests__/**" + import/no-unresolved: error + import/no-webpack-loader-syntax: error diff --git a/.travis.yml b/.travis.yml index 52ff15c01..71b3a6069 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,5 +53,5 @@ before_script: script: - travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec - - npm test + - yarn test - bundle exec i18n-tasks unused diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 207c7b324..069026715 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -39,6 +39,7 @@ class Settings::PreferencesController < ApplicationController :setting_boost_modal, :setting_delete_modal, :setting_auto_play_gif, + :setting_reduce_motion, :setting_system_font_ui, :setting_noindex, :setting_theme, diff --git a/app/javascript/glitch/components/account/header.js b/app/javascript/glitch/components/account/header.js index 6359c1775..f4a413aa3 100644 --- a/app/javascript/glitch/components/account/header.js +++ b/app/javascript/glitch/components/account/header.js @@ -48,7 +48,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; // Mastodon imports // -import emojify from 'mastodon/features/emoji/emoji'; +import emojify from '../../../mastodon/features/emoji/emoji'; import IconButton from '../../../mastodon/components/icon_button'; import Avatar from '../../../mastodon/components/avatar'; diff --git a/app/javascript/glitch/components/local_settings/container.js b/app/javascript/glitch/components/local_settings/container.js index 6c202a4e7..4569db99f 100644 --- a/app/javascript/glitch/components/local_settings/container.js +++ b/app/javascript/glitch/components/local_settings/container.js @@ -2,10 +2,10 @@ import { connect } from 'react-redux'; // Mastodon imports // -import { closeModal } from 'mastodon/actions/modal'; +import { closeModal } from '../../../mastodon/actions/modal'; // Our imports // -import { changeLocalSetting } from 'glitch/actions/local_settings'; +import { changeLocalSetting } from '../../../glitch/actions/local_settings'; import LocalSettings from '.'; const mapStateToProps = state => ({ diff --git a/app/javascript/glitch/components/local_settings/index.js b/app/javascript/glitch/components/local_settings/index.js index 7f7b93de4..ef711229a 100644 --- a/app/javascript/glitch/components/local_settings/index.js +++ b/app/javascript/glitch/components/local_settings/index.js @@ -8,7 +8,7 @@ import LocalSettingsPage from './page'; import LocalSettingsNavigation from './navigation'; // Stylesheet imports -import './style'; +import './style.scss'; export default class LocalSettings extends React.PureComponent { diff --git a/app/javascript/glitch/components/local_settings/navigation/index.js b/app/javascript/glitch/components/local_settings/navigation/index.js index 1f72cc824..fa35e83c7 100644 --- a/app/javascript/glitch/components/local_settings/navigation/index.js +++ b/app/javascript/glitch/components/local_settings/navigation/index.js @@ -7,7 +7,7 @@ import { injectIntl, defineMessages } from 'react-intl'; import LocalSettingsNavigationItem from './item'; // Stylesheet imports -import './style'; +import './style.scss'; // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * diff --git a/app/javascript/glitch/components/local_settings/navigation/item/index.js b/app/javascript/glitch/components/local_settings/navigation/item/index.js index 1676aa404..a352d5fb2 100644 --- a/app/javascript/glitch/components/local_settings/navigation/item/index.js +++ b/app/javascript/glitch/components/local_settings/navigation/item/index.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; // Stylesheet imports -import './style'; +import './style.scss'; // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * diff --git a/app/javascript/glitch/components/local_settings/page/index.js b/app/javascript/glitch/components/local_settings/page/index.js index 338d86333..366c113c0 100644 --- a/app/javascript/glitch/components/local_settings/page/index.js +++ b/app/javascript/glitch/components/local_settings/page/index.js @@ -8,7 +8,7 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; import LocalSettingsPageItem from './item'; // Stylesheet imports -import './style'; +import './style.scss'; // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * diff --git a/app/javascript/glitch/components/local_settings/page/item/index.js b/app/javascript/glitch/components/local_settings/page/item/index.js index 326c7eeb0..37e28c084 100644 --- a/app/javascript/glitch/components/local_settings/page/item/index.js +++ b/app/javascript/glitch/components/local_settings/page/item/index.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; // Stylesheet imports -import './style'; +import './style.scss'; // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * diff --git a/app/javascript/mastodon/base_polyfills.js b/app/javascript/mastodon/base_polyfills.js index 266a0020c..7856b26f9 100644 --- a/app/javascript/mastodon/base_polyfills.js +++ b/app/javascript/mastodon/base_polyfills.js @@ -1,5 +1,5 @@ import 'intl'; -import 'intl/locale-data/jsonp/en.js'; +import 'intl/locale-data/jsonp/en'; import 'es6-symbol/implement'; import includes from 'array-includes'; import assign from 'object-assign'; diff --git a/app/javascript/mastodon/components/__tests__/__snapshots__/avatar-test.js.snap b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar-test.js.snap new file mode 100644 index 000000000..4005c860f --- /dev/null +++ b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar-test.js.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<Avatar /> Autoplay renders a animated avatar 1`] = ` +<div + className="account__avatar" + data-avatar-of="@alice" + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "backgroundImage": "url(/animated/alice.gif)", + "backgroundSize": "100px 100px", + "height": "100px", + "width": "100px", + } + } +/> +`; + +exports[`<Avatar /> Still renders a still avatar 1`] = ` +<div + className="account__avatar" + data-avatar-of="@alice" + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "backgroundImage": "url(/static/alice.jpg)", + "backgroundSize": "100px 100px", + "height": "100px", + "width": "100px", + } + } +/> +`; diff --git a/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.js.snap b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.js.snap new file mode 100644 index 000000000..d9e5e5252 --- /dev/null +++ b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.js.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<AvatarOverlay renders a overlay avatar 1`] = ` +<div + className="account__avatar-overlay" +> + <div + className="account__avatar-overlay-base" + data-avatar-of="@alice" + style={ + Object { + "backgroundImage": "url(/static/alice.jpg)", + } + } + /> + <div + className="account__avatar-overlay-overlay" + data-avatar-of="@eve@blackhat.lair" + style={ + Object { + "backgroundImage": "url(/static/eve.jpg)", + } + } + /> +</div> +`; diff --git a/app/javascript/mastodon/components/__tests__/__snapshots__/button-test.js.snap b/app/javascript/mastodon/components/__tests__/__snapshots__/button-test.js.snap new file mode 100644 index 000000000..c3f018d90 --- /dev/null +++ b/app/javascript/mastodon/components/__tests__/__snapshots__/button-test.js.snap @@ -0,0 +1,114 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<Button /> adds class "button-secondary" if props.secondary given 1`] = ` +<button + className="button button-secondary" + disabled={undefined} + onClick={[Function]} + style={ + Object { + "height": "36px", + "lineHeight": "36px", + "padding": "0 16px", + } + } +/> +`; + +exports[`<Button /> renders a button element 1`] = ` +<button + className="button" + disabled={undefined} + onClick={[Function]} + style={ + Object { + "height": "36px", + "lineHeight": "36px", + "padding": "0 16px", + } + } +/> +`; + +exports[`<Button /> renders a disabled attribute if props.disabled given 1`] = ` +<button + className="button" + disabled={true} + onClick={[Function]} + style={ + Object { + "height": "36px", + "lineHeight": "36px", + "padding": "0 16px", + } + } +/> +`; + +exports[`<Button /> renders class="button--block" if props.block given 1`] = ` +<button + className="button button--block" + disabled={undefined} + onClick={[Function]} + style={ + Object { + "height": "36px", + "lineHeight": "36px", + "padding": "0 16px", + } + } +/> +`; + +exports[`<Button /> renders the children 1`] = ` +<button + className="button" + disabled={undefined} + onClick={[Function]} + style={ + Object { + "height": "36px", + "lineHeight": "36px", + "padding": "0 16px", + } + } +> + <p> + children + </p> +</button> +`; + +exports[`<Button /> renders the given text 1`] = ` +<button + className="button" + disabled={undefined} + onClick={[Function]} + style={ + Object { + "height": "36px", + "lineHeight": "36px", + "padding": "0 16px", + } + } +> + foo +</button> +`; + +exports[`<Button /> renders the props.text instead of children 1`] = ` +<button + className="button" + disabled={undefined} + onClick={[Function]} + style={ + Object { + "height": "36px", + "lineHeight": "36px", + "padding": "0 16px", + } + } +> + foo +</button> +`; diff --git a/app/javascript/mastodon/components/__tests__/__snapshots__/display_name-test.js.snap b/app/javascript/mastodon/components/__tests__/__snapshots__/display_name-test.js.snap new file mode 100644 index 000000000..533359ffe --- /dev/null +++ b/app/javascript/mastodon/components/__tests__/__snapshots__/display_name-test.js.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<DisplayName /> renders display name + account name 1`] = ` +<span + className="display-name" +> + <strong + className="display-name__html" + dangerouslySetInnerHTML={ + Object { + "__html": "<p>Foo</p>", + } + } + /> + + <span + className="display-name__account" + > + @ + bar@baz + </span> +</span> +`; diff --git a/app/javascript/mastodon/components/__tests__/avatar-test.js b/app/javascript/mastodon/components/__tests__/avatar-test.js new file mode 100644 index 000000000..dd3f7b7d2 --- /dev/null +++ b/app/javascript/mastodon/components/__tests__/avatar-test.js @@ -0,0 +1,36 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { fromJS } from 'immutable'; +import Avatar from '../avatar'; + +describe('<Avatar />', () => { + const account = fromJS({ + username: 'alice', + acct: 'alice', + display_name: 'Alice', + avatar: '/animated/alice.gif', + avatar_static: '/static/alice.jpg', + }); + + const size = 100; + + describe('Autoplay', () => { + it('renders a animated avatar', () => { + const component = renderer.create(<Avatar account={account} animate size={size} />); + const tree = component.toJSON(); + + expect(tree).toMatchSnapshot(); + }); + }); + + describe('Still', () => { + it('renders a still avatar', () => { + const component = renderer.create(<Avatar account={account} size={size} />); + const tree = component.toJSON(); + + expect(tree).toMatchSnapshot(); + }); + }); + + // TODO add autoplay test if possible +}); diff --git a/app/javascript/mastodon/components/__tests__/avatar_overlay-test.js b/app/javascript/mastodon/components/__tests__/avatar_overlay-test.js new file mode 100644 index 000000000..44addea83 --- /dev/null +++ b/app/javascript/mastodon/components/__tests__/avatar_overlay-test.js @@ -0,0 +1,29 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { fromJS } from 'immutable'; +import AvatarOverlay from '../avatar_overlay'; + +describe('<AvatarOverlay', () => { + const account = fromJS({ + username: 'alice', + acct: 'alice', + display_name: 'Alice', + avatar: '/animated/alice.gif', + avatar_static: '/static/alice.jpg', + }); + + const friend = fromJS({ + username: 'eve', + acct: 'eve@blackhat.lair', + display_name: 'Evelyn', + avatar: '/animated/eve.gif', + avatar_static: '/static/eve.jpg', + }); + + it('renders a overlay avatar', () => { + const component = renderer.create(<AvatarOverlay account={account} friend={friend} />); + const tree = component.toJSON(); + + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/app/javascript/mastodon/components/__tests__/button-test.js b/app/javascript/mastodon/components/__tests__/button-test.js new file mode 100644 index 000000000..160cd3cbc --- /dev/null +++ b/app/javascript/mastodon/components/__tests__/button-test.js @@ -0,0 +1,75 @@ +import { shallow } from 'enzyme'; +import React from 'react'; +import renderer from 'react-test-renderer'; +import Button from '../button'; + +describe('<Button />', () => { + it('renders a button element', () => { + const component = renderer.create(<Button />); + const tree = component.toJSON(); + + expect(tree).toMatchSnapshot(); + }); + + it('renders the given text', () => { + const text = 'foo'; + const component = renderer.create(<Button text={text} />); + const tree = component.toJSON(); + + expect(tree).toMatchSnapshot(); + }); + + it('handles click events using the given handler', () => { + const handler = jest.fn(); + const button = shallow(<Button onClick={handler} />); + button.find('button').simulate('click'); + + expect(handler.mock.calls.length).toEqual(1); + }); + + it('does not handle click events if props.disabled given', () => { + const handler = jest.fn(); + const button = shallow(<Button onClick={handler} disabled />); + button.find('button').simulate('click'); + + expect(handler.mock.calls.length).toEqual(0); + }); + + it('renders a disabled attribute if props.disabled given', () => { + const component = renderer.create(<Button disabled />); + const tree = component.toJSON(); + + expect(tree).toMatchSnapshot(); + }); + + it('renders the children', () => { + const children = <p>children</p>; + const component = renderer.create(<Button>{children}</Button>); + const tree = component.toJSON(); + + expect(tree).toMatchSnapshot(); + }); + + it('renders the props.text instead of children', () => { + const text = 'foo'; + const children = <p>children</p>; + const component = renderer.create(<Button text={text}>{children}</Button>); + const tree = component.toJSON(); + + expect(tree).toMatchSnapshot(); + }); + + it('renders class="button--block" if props.block given', () => { + const component = renderer.create(<Button block />); + const tree = component.toJSON(); + + expect(tree).toMatchSnapshot(); + }); + + it('adds class "button-secondary" if props.secondary given', () => { + const component = renderer.create(<Button secondary />); + const tree = component.toJSON(); + + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/app/javascript/mastodon/components/__tests__/display_name-test.js b/app/javascript/mastodon/components/__tests__/display_name-test.js new file mode 100644 index 000000000..0d040c4cd --- /dev/null +++ b/app/javascript/mastodon/components/__tests__/display_name-test.js @@ -0,0 +1,18 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { fromJS } from 'immutable'; +import DisplayName from '../display_name'; + +describe('<DisplayName />', () => { + it('renders display name + account name', () => { + const account = fromJS({ + username: 'bar', + acct: 'bar@baz', + display_name_html: '<p>Foo</p>', + }); + const component = renderer.create(<DisplayName account={account} />); + const tree = component.toJSON(); + + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/app/javascript/mastodon/components/collapsable.js b/app/javascript/mastodon/components/collapsable.js index ad1453589..42ea37ec2 100644 --- a/app/javascript/mastodon/components/collapsable.js +++ b/app/javascript/mastodon/components/collapsable.js @@ -1,5 +1,5 @@ import React from 'react'; -import Motion from 'react-motion/lib/Motion'; +import Motion from '../features/ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import PropTypes from 'prop-types'; diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js index 73ad46bb7..3a3ebf487 100644 --- a/app/javascript/mastodon/components/dropdown_menu.js +++ b/app/javascript/mastodon/components/dropdown_menu.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import IconButton from './icon_button'; import Overlay from 'react-overlays/lib/Overlay'; -import Motion from 'react-motion/lib/Motion'; +import Motion from '../features/ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import detectPassiveEvents from 'detect-passive-events'; diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js index 6fb191c6b..76b0da12f 100644 --- a/app/javascript/mastodon/components/icon_button.js +++ b/app/javascript/mastodon/components/icon_button.js @@ -1,7 +1,8 @@ import React from 'react'; -import Motion from 'react-motion/lib/Motion'; +import Motion from '../features/ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; export default class IconButton extends React.PureComponent { @@ -56,27 +57,26 @@ export default class IconButton extends React.PureComponent { style.textAlign = 'left'; } - const classes = ['icon-button']; - - if (this.props.active) { - classes.push('active'); - } - - if (this.props.disabled) { - classes.push('disabled'); - } - - if (this.props.inverted) { - classes.push('inverted'); - } - - if (this.props.overlay) { - classes.push('overlayed'); - } - - if (this.props.className) { - classes.push(this.props.className); - } + const { + active, + animate, + className, + disabled, + expanded, + icon, + inverted, + overlay, + pressed, + tabIndex, + title, + } = this.props; + + const classes = classNames(className, 'icon-button', { + active, + disabled, + inverted, + overlayed: overlay, + }); const flipDeg = this.props.flip ? -180 : -360; const rotateDeg = this.props.active ? flipDeg : 0; @@ -90,23 +90,23 @@ export default class IconButton extends React.PureComponent { damping: 7, }; const motionStyle = { - rotate: this.props.animate ? spring(rotateDeg, springOpts) : 0, + rotate: animate ? spring(rotateDeg, springOpts) : 0, }; return ( <Motion defaultStyle={motionDefaultStyle} style={motionStyle}> {({ rotate }) => <button - aria-label={this.props.title} - aria-pressed={this.props.pressed} - aria-expanded={this.props.expanded} - title={this.props.title} - className={classes.join(' ')} + aria-label={title} + aria-pressed={pressed} + aria-expanded={expanded} + title={title} + className={classes} onClick={this.handleClick} style={style} - tabIndex={this.props.tabIndex} + tabIndex={tabIndex} > - <i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' /> + <i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${icon}`} aria-hidden='true' /> {this.props.label} </button> } diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index cf9c8fb53..af152cc32 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -16,6 +16,7 @@ const messages = defineMessages({ block: { id: 'account.block', defaultMessage: 'Block @{name}' }, reply: { id: 'status.reply', defaultMessage: 'Reply' }, share: { id: 'status.share', defaultMessage: 'Share' }, + more: { id: 'status.more', defaultMessage: 'More' }, replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' }, reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, @@ -182,7 +183,7 @@ export default class StatusActionBar extends ImmutablePureComponent { {shareButton} <div className='status__action-bar-dropdown'> - <DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' /> + <DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel={intl.formatMessage(messages.more)} /> </div> </div> ); diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 6beffca1c..a7138e62d 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -10,6 +10,7 @@ import { hydrateStore } from '../actions/store'; import { connectUserStream } from '../actions/streaming'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; + const { localeData, messages } = getLocale(); addLocaleData(localeData); diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 5402d6753..57678d162 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import IconButton from '../../../components/icon_button'; -import Motion from 'react-motion/lib/Motion'; +import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import { connect } from 'react-redux'; import ImmutablePureComponent from 'react-immutable-pure-component'; diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js index e38ed38c1..c1e85aee3 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { injectIntl, defineMessages } from 'react-intl'; import IconButton from '../../../components/icon_button'; import Overlay from 'react-overlays/lib/Overlay'; -import Motion from 'react-motion/lib/Motion'; +import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import detectPassiveEvents from 'detect-passive-events'; import classNames from 'classnames'; diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js index f57d54618..398fc44ce 100644 --- a/app/javascript/mastodon/features/compose/components/search.js +++ b/app/javascript/mastodon/features/compose/components/search.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import Overlay from 'react-overlays/lib/Overlay'; -import Motion from 'react-motion/lib/Motion'; +import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; const messages = defineMessages({ diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js index cd9e08360..5d8d66cf7 100644 --- a/app/javascript/mastodon/features/compose/components/upload.js +++ b/app/javascript/mastodon/features/compose/components/upload.js @@ -2,7 +2,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import IconButton from '../../../components/icon_button'; -import Motion from 'react-motion/lib/Motion'; +import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { defineMessages, injectIntl } from 'react-intl'; diff --git a/app/javascript/mastodon/features/compose/components/upload_progress.js b/app/javascript/mastodon/features/compose/components/upload_progress.js index 3e49098c7..d5e6f19cd 100644 --- a/app/javascript/mastodon/features/compose/components/upload_progress.js +++ b/app/javascript/mastodon/features/compose/components/upload_progress.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Motion from 'react-motion/lib/Motion'; +import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import { FormattedMessage } from 'react-intl'; diff --git a/app/javascript/mastodon/features/compose/components/warning.js b/app/javascript/mastodon/features/compose/components/warning.js index a0814e984..803b7f86a 100644 --- a/app/javascript/mastodon/features/compose/components/warning.js +++ b/app/javascript/mastodon/features/compose/components/warning.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Motion from 'react-motion/lib/Motion'; +import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; export default class Warning extends React.PureComponent { diff --git a/app/javascript/mastodon/features/compose/containers/autosuggest_status_container.js b/app/javascript/mastodon/features/compose/containers/autosuggest_status_container.js deleted file mode 100644 index a9e3a9edf..000000000 --- a/app/javascript/mastodon/features/compose/containers/autosuggest_status_container.js +++ /dev/null @@ -1,15 +0,0 @@ -import { connect } from 'react-redux'; -import AutosuggestStatus from '../components/autosuggest_status'; -import { makeGetStatus } from '../../../selectors'; - -const makeMapStateToProps = () => { - const getStatus = makeGetStatus(); - - const mapStateToProps = (state, { id }) => ({ - status: getStatus(state, id), - }); - - return mapStateToProps; -}; - -export default connect(makeMapStateToProps)(AutosuggestStatus); diff --git a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js index 8624849f3..e4bd5a743 100644 --- a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js +++ b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import IconButton from '../../../components/icon_button'; import { changeComposeSensitivity } from '../../../actions/compose'; -import Motion from 'react-motion/lib/Motion'; +import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import { injectIntl, defineMessages } from 'react-intl'; diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index 9068648bd..41a97d550 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -10,7 +10,7 @@ import { changeLocalSetting } from '../../../glitch/actions/local_settings'; import { Link } from 'react-router-dom'; import { injectIntl, defineMessages } from 'react-intl'; import SearchContainer from './containers/search_container'; -import Motion from 'react-motion/lib/Motion'; +import Motion from '../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import SearchResultsContainer from './containers/search_results_container'; import { changeComposing } from '../../actions/compose'; diff --git a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js new file mode 100644 index 000000000..636402172 --- /dev/null +++ b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js @@ -0,0 +1,61 @@ +import emojify from '../emoji'; + +describe('emoji', () => { + describe('.emojify', () => { + it('ignores unknown shortcodes', () => { + expect(emojify(':foobarbazfake:')).toEqual(':foobarbazfake:'); + }); + + it('ignores shortcodes inside of tags', () => { + expect(emojify('<p data-foo=":smile:"></p>')).toEqual('<p data-foo=":smile:"></p>'); + }); + + it('works with unclosed tags', () => { + expect(emojify('hello>')).toEqual('hello>'); + expect(emojify('<hello')).toEqual('<hello'); + }); + + it('works with unclosed shortcodes', () => { + expect(emojify('smile:')).toEqual('smile:'); + expect(emojify(':smile')).toEqual(':smile'); + }); + + it('does unicode', () => { + expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).toEqual( + '<img draggable="false" class="emojione" alt="👩👩👦👦" title=":woman-woman-boy-boy:" src="/emoji/1f469-200d-1f469-200d-1f466-200d-1f466.svg" />'); + expect(emojify('👨👩👧👧')).toEqual( + '<img draggable="false" class="emojione" alt="👨👩👧👧" title=":man-woman-girl-girl:" src="/emoji/1f468-200d-1f469-200d-1f467-200d-1f467.svg" />'); + expect(emojify('👩👩👦')).toEqual('<img draggable="false" class="emojione" alt="👩👩👦" title=":woman-woman-boy:" src="/emoji/1f469-200d-1f469-200d-1f466.svg" />'); + expect(emojify('\u2757')).toEqual( + '<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />'); + }); + + it('does multiple unicode', () => { + expect(emojify('\u2757 #\uFE0F\u20E3')).toEqual( + '<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" />'); + expect(emojify('\u2757#\uFE0F\u20E3')).toEqual( + '<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" />'); + expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).toEqual( + '<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" /> <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />'); + expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).toEqual( + 'foo <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" /> bar'); + }); + + it('ignores unicode inside of tags', () => { + expect(emojify('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>')).toEqual('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>'); + }); + + it('does multiple emoji properly (issue 5188)', () => { + expect(emojify('👌🌈💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg" /><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg" /><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg" />'); + expect(emojify('👌 🌈 💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg" /> <img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg" /> <img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg" />'); + }); + + it('does an emoji that has no shortcode', () => { + expect(emojify('🕉️')).toEqual('<img draggable="false" class="emojione" alt="🕉️" title="" src="/emoji/1f549.svg" />'); + }); + + it('does an emoji whose filename is irregular', () => { + expect(emojify('↙️')).toEqual('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg" />'); + }); + }); +}); diff --git a/app/javascript/mastodon/features/emoji/__tests__/emoji_index-test.js b/app/javascript/mastodon/features/emoji/__tests__/emoji_index-test.js new file mode 100644 index 000000000..53efa5743 --- /dev/null +++ b/app/javascript/mastodon/features/emoji/__tests__/emoji_index-test.js @@ -0,0 +1,130 @@ +import { pick } from 'lodash'; +import { emojiIndex } from 'emoji-mart'; +import { search } from '../emoji_mart_search_light'; + +const trimEmojis = emoji => pick(emoji, ['id', 'unified', 'native', 'custom']); + +describe('emoji_index', () => { + it('should give same result for emoji_index_light and emoji-mart', () => { + const expected = [ + { + id: 'pineapple', + unified: '1f34d', + native: '🍍', + }, + ]; + expect(search('pineapple').map(trimEmojis)).toEqual(expected); + expect(emojiIndex.search('pineapple').map(trimEmojis)).toEqual(expected); + }); + + it('orders search results correctly', () => { + const expected = [ + { + id: 'apple', + unified: '1f34e', + native: '🍎', + }, + { + id: 'pineapple', + unified: '1f34d', + native: '🍍', + }, + { + id: 'green_apple', + unified: '1f34f', + native: '🍏', + }, + { + id: 'iphone', + unified: '1f4f1', + native: '📱', + }, + ]; + expect(search('apple').map(trimEmojis)).toEqual(expected); + expect(emojiIndex.search('apple').map(trimEmojis)).toEqual(expected); + }); + + it('handles custom emoji', () => { + const custom = [ + { + id: 'mastodon', + name: 'mastodon', + short_names: ['mastodon'], + text: '', + emoticons: [], + keywords: ['mastodon'], + imageUrl: 'http://example.com', + custom: true, + }, + ]; + search('', { custom }); + emojiIndex.search('', { custom }); + const expected = [ + { + id: 'mastodon', + custom: true, + }, + ]; + expect(search('masto').map(trimEmojis)).toEqual(expected); + expect(emojiIndex.search('masto').map(trimEmojis)).toEqual(expected); + }); + + it('should filter only emojis we care about, exclude pineapple', () => { + const emojisToShowFilter = unified => unified !== '1F34D'; + expect(search('apple', { emojisToShowFilter }).map((obj) => obj.id)) + .not.toContain('pineapple'); + expect(emojiIndex.search('apple', { emojisToShowFilter }).map((obj) => obj.id)) + .not.toContain('pineapple'); + }); + + it('can include/exclude categories', () => { + expect(search('flag', { include: ['people'] })).toEqual([]); + expect(emojiIndex.search('flag', { include: ['people'] })).toEqual([]); + }); + + it('does an emoji whose unified name is irregular', () => { + const expected = [ + { + 'id': 'water_polo', + 'unified': '1f93d', + 'native': '🤽', + }, + { + 'id': 'man-playing-water-polo', + 'unified': '1f93d-200d-2642-fe0f', + 'native': '🤽♂️', + }, + { + 'id': 'woman-playing-water-polo', + 'unified': '1f93d-200d-2640-fe0f', + 'native': '🤽♀️', + }, + ]; + expect(search('polo').map(trimEmojis)).toEqual(expected); + expect(emojiIndex.search('polo').map(trimEmojis)).toEqual(expected); + }); + + it('can search for thinking_face', () => { + const expected = [ + { + id: 'thinking_face', + unified: '1f914', + native: '🤔', + }, + ]; + expect(search('thinking_fac').map(trimEmojis)).toEqual(expected); + expect(emojiIndex.search('thinking_fac').map(trimEmojis)).toEqual(expected); + }); + + it('can search for woman-facepalming', () => { + const expected = [ + { + id: 'woman-facepalming', + unified: '1f926-200d-2640-fe0f', + native: '🤦♀️', + }, + ]; + expect(search('woman-facep').map(trimEmojis)).toEqual(expected); + expect(emojiIndex.search('woman-facep').map(trimEmojis)).toEqual(expected); + }); +}); diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.js b/app/javascript/mastodon/features/emoji/emoji_compressed.js index 3bd89cf3b..c0cba952a 100644 --- a/app/javascript/mastodon/features/emoji/emoji_compressed.js +++ b/app/javascript/mastodon/features/emoji/emoji_compressed.js @@ -9,7 +9,8 @@ const { unicodeToFilename } = require('./unicode_to_filename'); const { unicodeToUnifiedName } = require('./unicode_to_unified_name'); const emojiMap = require('./emoji_map.json'); const { emojiIndex } = require('emoji-mart'); -const emojiMartData = require('emoji-mart/dist/data').default; +const { default: emojiMartData } = require('emoji-mart/dist/data'); + const excluded = ['®', '©', '™']; const skins = ['🏻', '🏼', '🏽', '🏾', '🏿']; const shortcodeMap = {}; diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 816f83e45..d8547db36 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -48,6 +48,8 @@ export default class DetailedStatus extends ImmutablePureComponent { let media = ''; let mediaIcon = null; let applicationLink = ''; + let reblogLink = ''; + let reblogIcon = 'retweet'; if (status.get('media_attachments').size > 0) { if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { @@ -85,6 +87,23 @@ export default class DetailedStatus extends ImmutablePureComponent { applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>; } + if (status.get('visibility') === 'direct') { + reblogIcon = 'envelope'; + } else if (status.get('visibility') === 'private') { + reblogIcon = 'lock'; + } + + if (status.get('visibility') === 'private') { + reblogLink = <i className={`fa fa-${reblogIcon}`} />; + } else { + reblogLink = (<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'> + <i className={`fa fa-${reblogIcon}`} /> + <span className='detailed-status__reblogs'> + <FormattedNumber value={status.get('reblogs_count')} /> + </span> + </Link>); + } + return ( <div className='detailed-status'> <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'> @@ -101,12 +120,7 @@ export default class DetailedStatus extends ImmutablePureComponent { <div className='detailed-status__meta'> <a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'> <FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' /> - </a>{applicationLink} · <Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'> - <i className='fa fa-retweet' /> - <span className='detailed-status__reblogs'> - <FormattedNumber value={status.get('reblogs_count')} /> - </span> - </Link> · <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'> + </a>{applicationLink} · {reblogLink} · <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'> <i className='fa fa-star' /> <span className='detailed-status__favorites'> <FormattedNumber value={status.get('favourites_count')} /> diff --git a/spec/javascript/components/features/ui/components/column.test.js b/app/javascript/mastodon/features/ui/components/__tests__/column-test.js index 4491d6e19..1e5e1d8dc 100644 --- a/spec/javascript/components/features/ui/components/column.test.js +++ b/app/javascript/mastodon/features/ui/components/__tests__/column-test.js @@ -1,14 +1,18 @@ -import { expect } from 'chai'; -import { mount } from 'enzyme'; -import sinon from 'sinon'; import React from 'react'; -import Column from '../../../../../../app/javascript/mastodon/features/ui/components/column'; -import ColumnHeader from '../../../../../../app/javascript/mastodon/features/ui/components/column_header'; +import { mount } from 'enzyme'; +import Column from '../column'; +import ColumnHeader from '../column_header'; describe('<Column />', () => { describe('<ColumnHeader /> click handler', () => { + const originalRaf = global.requestAnimationFrame; + beforeEach(() => { - global.requestAnimationFrame = sinon.spy(); + global.requestAnimationFrame = jest.fn(); + }); + + afterAll(() => { + global.requestAnimationFrame = originalRaf; }); it('runs the scroll animation if the column contains scrollable content', () => { @@ -18,13 +22,13 @@ describe('<Column />', () => { </Column> ); wrapper.find(ColumnHeader).simulate('click'); - expect(global.requestAnimationFrame.called).to.equal(true); + expect(global.requestAnimationFrame.mock.calls.length).toEqual(1); }); it('does not try to scroll if there is no scrollable content', () => { const wrapper = mount(<Column heading='notifications' />); wrapper.find(ColumnHeader).simulate('click'); - expect(global.requestAnimationFrame.called).to.equal(false); + expect(global.requestAnimationFrame.mock.calls.length).toEqual(0); }); }); }); diff --git a/app/javascript/mastodon/features/ui/components/upload_area.js b/app/javascript/mastodon/features/ui/components/upload_area.js index dda28feeb..c19065be6 100644 --- a/app/javascript/mastodon/features/ui/components/upload_area.js +++ b/app/javascript/mastodon/features/ui/components/upload_area.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Motion from 'react-motion/lib/Motion'; +import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import { FormattedMessage } from 'react-intl'; diff --git a/app/javascript/mastodon/features/ui/util/optional_motion.js b/app/javascript/mastodon/features/ui/util/optional_motion.js new file mode 100644 index 000000000..4276eeaa4 --- /dev/null +++ b/app/javascript/mastodon/features/ui/util/optional_motion.js @@ -0,0 +1,34 @@ +// Like react-motion's Motion, but checks to see if the user prefers +// reduced motion and uses a cross-fade in those cases. + +import Motion from 'react-motion/lib/Motion'; +import { connect } from 'react-redux'; + +const stylesToKeep = ['opacity', 'backgroundOpacity']; + +const extractValue = (value) => { + // This is either an object with a "val" property or it's a number + return (typeof value === 'object' && value && 'val' in value) ? value.val : value; +}; + +const mapStateToProps = (state, ownProps) => { + const reduceMotion = state.getIn(['meta', 'reduce_motion']); + + if (reduceMotion) { + const { style, defaultStyle } = ownProps; + + Object.keys(style).forEach(key => { + if (stylesToKeep.includes(key)) { + return; + } + // If it's setting an x or height or scale or some other value, we need + // to preserve the end-state value without actually animating it + style[key] = defaultStyle[key] = extractValue(style[key]); + }); + + return { style, defaultStyle }; + } + return {}; +}; + +export default connect(mapStateToProps)(Motion); diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index fe2433591..1130e6c09 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -184,6 +184,7 @@ "status.load_more": "Carrega més", "status.media_hidden": "Multimèdia amagat", "status.mention": "Esmentar @{name}", + "status.more": "Més", "status.mute_conversation": "Silenciar conversació", "status.open": "Ampliar aquest estat", "status.pin": "Fixat en el perfil", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 12efe0e0c..7e8d30c64 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -179,6 +179,7 @@ "status.load_more": "Load more", "status.media_hidden": "Media hidden", "status.mention": "Mention @{name}", + "status.more": "More", "status.mute_conversation": "Mute conversation", "status.open": "Expand this status", "status.pin": "Pin on profile", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index f6bfbb04d..03dd9ce02 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -179,6 +179,7 @@ "status.load_more": "Cargar más", "status.media_hidden": "Contenido multimedia oculto", "status.mention": "Mencionar", + "status.more": "Más", "status.mute_conversation": "Silenciar conversación", "status.open": "Expandir estado", "status.pin": "Fijar", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 145b683f3..f192f3cfc 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -179,6 +179,7 @@ "status.load_more": "Charger plus", "status.media_hidden": "Média caché", "status.mention": "Mentionner", + "status.more": "Plus", "status.mute_conversation": "Masquer la conversation", "status.open": "Déplier ce statut", "status.pin": "Épingler sur le profil", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index c1768cf8f..637acfab6 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -18,7 +18,7 @@ "account.unblock_domain": "{domain} 숨김 해제", "account.unfollow": "팔로우 해제", "account.unmute": "뮤트 해제", - "account.view_full_profile": "View full profile", + "account.view_full_profile": "전체 프로필 보기", "boost_modal.combo": "다음부터 {combo}를 누르면 이 과정을 건너뛸 수 있습니다.", "bundle_column_error.body": "Something went wrong while loading this component.", "bundle_column_error.retry": "Try again", @@ -33,7 +33,7 @@ "column.home": "홈", "column.mutes": "뮤트 중인 사용자", "column.notifications": "알림", - "column.pins": "고정된 Toot", + "column.pins": "고정된 툿", "column.public": "연합 타임라인", "column_back_button.label": "돌아가기", "column_header.hide_settings": "Hide settings", @@ -47,7 +47,7 @@ "compose_form.lock_disclaimer": "이 계정은 {locked}로 설정 되어 있지 않습니다. 누구나 이 계정을 팔로우 할 수 있으며, 팔로워 공개의 포스팅을 볼 수 있습니다.", "compose_form.lock_disclaimer.lock": "비공개", "compose_form.placeholder": "지금 무엇을 하고 있나요?", - "compose_form.publish": "Toot", + "compose_form.publish": "툿", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive": "이 미디어를 민감한 미디어로 취급", "compose_form.spoiler": "텍스트 숨기기", @@ -63,8 +63,8 @@ "confirmations.mute.message": "정말로 {name}를 뮤트하시겠습니까?", "confirmations.unfollow.confirm": "Unfollow", "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", - "embed.instructions": "Embed this status on your website by copying the code below.", - "embed.preview": "Here is what it will look like:", + "embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 퍼가세요.", + "embed.preview": "다음과 같이 표시됩니다:", "emoji_button.activity": "활동", "emoji_button.custom": "Custom", "emoji_button.flags": "국기", @@ -82,7 +82,6 @@ "empty_column.community": "로컬 타임라인에 아무 것도 없습니다. 아무거나 적어 보세요!", "empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.", "empty_column.home": "아직 아무도 팔로우 하고 있지 않습니다. {public}를 보러 가거나, 검색하여 다른 사용자를 찾아 보세요.", - "empty_column.home.inactivity": "홈 피드에 아무 것도 없습니다. 한동안 활동하지 않은 경우 곧 원래대로 돌아올 것입니다.", "empty_column.home.public_timeline": "연합 타임라인", "empty_column.notifications": "아직 알림이 없습니다. 다른 사람과 대화를 시작해 보세요!", "empty_column.public": "여기엔 아직 아무 것도 없습니다! 공개적으로 무언가 포스팅하거나, 다른 인스턴스 유저를 팔로우 해서 가득 채워보세요!", @@ -113,7 +112,7 @@ "navigation_bar.info": "이 인스턴스에 대해서", "navigation_bar.logout": "로그아웃", "navigation_bar.mutes": "뮤트 중인 사용자", - "navigation_bar.pins": "고정된 Toot", + "navigation_bar.pins": "고정된 툿", "navigation_bar.preferences": "사용자 설정", "navigation_bar.public_timeline": "연합 타임라인", "notification.favourite": "{name}님이 즐겨찾기 했습니다", @@ -159,29 +158,34 @@ "privacy.public.long": "공개 타임라인에 표시", "privacy.public.short": "공개", "privacy.unlisted.long": "공개 타임라인에 표시하지 않음", - "privacy.unlisted.short": "Unlisted", + "privacy.unlisted.short": "타임라인에 비표시", + "relative_time.days": "{number}일 전", + "relative_time.hours": "{number}시간 전", + "relative_time.just_now": "방금", + "relative_time.minutes": "{number}분 전", + "relative_time.seconds": "{number}초 전", "reply_indicator.cancel": "취소", "report.placeholder": "코멘트", "report.submit": "신고하기", "report.target": "문제가 된 사용자", "search.placeholder": "검색", - "search_popout.search_format": "Advanced search format", - "search_popout.tips.hashtag": "hashtag", - "search_popout.tips.status": "status", - "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", - "search_popout.tips.user": "user", + "search_popout.search_format": "고급 검색 방법", + "search_popout.tips.hashtag": "해시태그", + "search_popout.tips.status": "툿", + "search_popout.tips.text": "단순한 텍스트 검색은 관계된 프로필 이름, 유저 이름 그리고 해시태그를 표시합니다", + "search_popout.tips.user": "유저", "search_results.total": "{count, number}건의 결과", "standalone.public_title": "A look inside...", "status.cannot_reblog": "이 포스트는 부스트 할 수 없습니다", "status.delete": "삭제", - "status.embed": "Embed", + "status.embed": "공유하기", "status.favourite": "즐겨찾기", "status.load_more": "더 보기", "status.media_hidden": "미디어 숨겨짐", "status.mention": "답장", "status.mute_conversation": "이 대화를 뮤트", "status.open": "상세 정보 표시", - "status.pin": "Pin on profile", + "status.pin": "고정", "status.reblog": "부스트", "status.reblogged_by": "{name}님이 부스트 했습니다", "status.reply": "답장", @@ -193,7 +197,7 @@ "status.show_less": "숨기기", "status.show_more": "더 보기", "status.unmute_conversation": "이 대화의 뮤트 해제하기", - "status.unpin": "Unpin from profile", + "status.unpin": "고정 해제", "tabs_bar.compose": "포스트", "tabs_bar.federated_timeline": "연합", "tabs_bar.home": "홈", @@ -212,5 +216,9 @@ "video.mute": "Mute sound", "video.pause": "Pause", "video.play": "Play", - "video.unmute": "Unmute sound" + "video.unmute": "Unmute sound", + "video_player.expand": "Expand video", + "video_player.toggle_sound": "Toggle sound", + "video_player.toggle_visible": "Toggle visibility", + "video_player.video_error": "Video could not be played" } diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 4715f60ef..773f2d1e4 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -179,6 +179,7 @@ "status.load_more": "Cargar mai", "status.media_hidden": "Mèdia rescondut", "status.mention": "Mencionar", + "status.more": "Mai", "status.mute_conversation": "Rescondre la conversacion", "status.open": "Desplegar aqueste estatut", "status.pin": "Penjar al perfil", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index c8228c0cb..77da77e10 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -179,6 +179,7 @@ "status.load_more": "Załaduj więcej", "status.media_hidden": "Zawartość multimedialna ukryta", "status.mention": "Wspomnij o @{name}", + "status.more": "Więcej", "status.mute_conversation": "Wycisz konwersację", "status.open": "Rozszerz ten wpis", "status.pin": "Przypnij do profilu", diff --git a/app/javascript/mastodon/main.js b/app/javascript/mastodon/main.js index c85cd5800..93d2eaf10 100644 --- a/app/javascript/mastodon/main.js +++ b/app/javascript/mastodon/main.js @@ -1,6 +1,5 @@ -import * as OfflinePluginRuntime from 'offline-plugin/runtime'; import * as WebPushSubscription from './web_push_subscription'; -import Mastodon from 'mastodon/containers/mastodon'; +import Mastodon from './containers/mastodon'; import React from 'react'; import ReactDOM from 'react-dom'; import ready from './ready'; @@ -25,7 +24,7 @@ function main() { ReactDOM.render(<Mastodon {...props} />, mountNode); if (process.env.NODE_ENV === 'production') { // avoid offline in dev mode because it's harder to debug - OfflinePluginRuntime.install(); + require('offline-plugin/runtime').install(); WebPushSubscription.register(); } perf.stop('main()'); diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index b17d74ef3..c3f117647 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -31,10 +31,10 @@ const initialTimeline = ImmutableMap({ }); const normalizeTimeline = (state, timeline, statuses, next) => { - const ids = ImmutableList(statuses.map(status => status.get('id'))); + const oldIds = state.getIn([timeline, 'items'], ImmutableList()); + const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId)); const wasLoaded = state.getIn([timeline, 'loaded']); const hadNext = state.getIn([timeline, 'next']); - const oldIds = state.getIn([timeline, 'items'], ImmutableList()); return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { mMap.set('loaded', true); @@ -45,8 +45,8 @@ const normalizeTimeline = (state, timeline, statuses, next) => { }; const appendNormalizedTimeline = (state, timeline, statuses, next) => { - const ids = ImmutableList(statuses.map(status => status.get('id'))); const oldIds = state.getIn([timeline, 'items'], ImmutableList()); + const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId)); return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { mMap.set('isLoading', false); diff --git a/app/javascript/mastodon/test_setup.js b/app/javascript/mastodon/test_setup.js new file mode 100644 index 000000000..80148379b --- /dev/null +++ b/app/javascript/mastodon/test_setup.js @@ -0,0 +1,5 @@ +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +const adapter = new Adapter(); +configure({ adapter }); diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index aa94006c6..d275c3bb0 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -2,7 +2,8 @@ import loadPolyfills from '../mastodon/load_polyfills'; // import default stylesheet with variables require('font-awesome/css/font-awesome.css'); -import 'styles/application'; + +import '../styles/application.scss'; require.context('../images/', true); diff --git a/app/javascript/packs/common.js b/app/javascript/packs/common.js index de0c68fa5..5ac6504d4 100644 --- a/app/javascript/packs/common.js +++ b/app/javascript/packs/common.js @@ -1,6 +1,9 @@ import { start } from 'rails-ujs'; +import 'font-awesome/css/font-awesome.css'; // import common styling require('../styles/common.scss'); +require.context('../images/', true); + start(); diff --git a/app/javascript/themes/spin/pack.js b/app/javascript/themes/spin/pack.js index dab0e93a4..b11ac4802 100644 --- a/app/javascript/themes/spin/pack.js +++ b/app/javascript/themes/spin/pack.js @@ -1,2 +1,2 @@ -import 'packs/application'; -import 'themes/spin/style'; +import '../../packs/application'; +import './style.scss'; diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 5a3af7206..9a64d58bd 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -100,11 +100,24 @@ class FeedManager end def populate_feed(account) - prepopulate_limit = FeedManager::MAX_ITEMS / 4 - statuses = Status.as_home_timeline(account).order(account_id: :desc).limit(prepopulate_limit) - statuses.reverse_each do |status| - next if filter_from_home?(status, account) - add_to_feed(:home, account, status) + added = 0 + limit = FeedManager::MAX_ITEMS / 2 + max_id = nil + + loop do + statuses = Status.as_home_timeline(account) + .paginate_by_max_id(limit, max_id) + + break if statuses.empty? + + statuses.each do |status| + next if filter_from_home?(status, account) + added += 1 if add_to_feed(:home, account, status) + end + + break unless added.zero? + + max_id = statuses.last.id end end @@ -167,13 +180,19 @@ class FeedManager # either action is appropriate. def add_to_feed(timeline_type, account, status) timeline_key = key(timeline_type, account.id) - reblog_key = key(timeline_type, account.id, 'reblogs') + reblog_key = key(timeline_type, account.id, 'reblogs') if status.reblog? + reblog_set_key = key(timeline_type, account.id, "reblogs:#{status.reblog_of_id}") + # If the original status or a reblog of it is within # REBLOG_FALLOFF statuses from the top, do not re-insert it into # the feed rank = redis.zrevrank(timeline_key, status.reblog_of_id) + + redis.sadd(reblog_set_key, status.reblog_of_id) unless rank.nil? + redis.sadd(reblog_set_key, status.id) + return false if !rank.nil? && rank < FeedManager::REBLOG_FALLOFF reblog_rank = redis.zrevrank(reblog_key, status.reblog_of_id) @@ -194,7 +213,7 @@ class FeedManager # do so if appropriate. def remove_from_feed(timeline_type, account, status) timeline_key = key(timeline_type, account.id) - reblog_key = key(timeline_type, account.id, 'reblogs') + reblog_key = key(timeline_type, account.id, 'reblogs') if status.reblog? # 1. If the reblogging status is not in the feed, stop. @@ -204,12 +223,21 @@ class FeedManager # 2. Remove the reblogged status from the `:reblogs` zset. redis.zrem(reblog_key, status.reblog_of_id) - # 3. Add the reblogged status to the feed using the reblogging - # status' ID as its score, and the reblogged status' ID as its - # value. - redis.zadd(timeline_key, status.id, status.reblog_of_id) + # 3. Remove reblog from set of this status's reblogs, and + # re-insert another reblog or original into the feed if + # one remains in the set + reblog_set_key = key(timeline_type, account.id, "reblogs:#{status.reblog_of_id}") + + redis.srem(reblog_set_key, status.id) + other_reblog = redis.srandmember(reblog_set_key) + + redis.zadd(timeline_key, other_reblog, other_reblog) if other_reblog # 4. Remove the reblogging status from the feed (as normal) + # (outside conditional) + else + # If the original is getting deleted, no use for reblog references + redis.del(key(timeline_type, account.id, "reblogs:#{status.id}")) end redis.zrem(timeline_key, status.id) diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb index 3b7a856ee..d86959c0b 100644 --- a/app/lib/user_settings_decorator.rb +++ b/app/lib/user_settings_decorator.rb @@ -23,6 +23,7 @@ class UserSettingsDecorator user.settings['boost_modal'] = boost_modal_preference if change?('setting_boost_modal') user.settings['delete_modal'] = delete_modal_preference if change?('setting_delete_modal') user.settings['auto_play_gif'] = auto_play_gif_preference if change?('setting_auto_play_gif') + user.settings['reduce_motion'] = reduce_motion_preference if change?('setting_reduce_motion') user.settings['system_font_ui'] = system_font_ui_preference if change?('setting_system_font_ui') user.settings['noindex'] = noindex_preference if change?('setting_noindex') user.settings['theme'] = theme_preference if change?('setting_theme') @@ -64,6 +65,10 @@ class UserSettingsDecorator boolean_cast_setting 'setting_auto_play_gif' end + def reduce_motion_preference + boolean_cast_setting 'setting_reduce_motion' + end + def noindex_preference boolean_cast_setting 'setting_noindex' end diff --git a/app/models/user.rb b/app/models/user.rb index 3bf069a31..325e27f44 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -102,6 +102,10 @@ class User < ApplicationRecord settings.auto_play_gif end + def setting_reduce_motion + settings.reduce_motion + end + def setting_system_font_ui settings.system_font_ui end diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 0992771fc..1f5ee789a 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -25,6 +25,7 @@ class InitialStateSerializer < ActiveModel::Serializer store[:boost_modal] = object.current_account.user.setting_boost_modal store[:delete_modal] = object.current_account.user.setting_delete_modal store[:auto_play_gif] = object.current_account.user.setting_auto_play_gif + store[:reduce_motion] = object.current_account.user.setting_reduce_motion end store diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml index 7475e3fd2..69e26a7be 100644 --- a/app/views/settings/preferences/show.html.haml +++ b/app/views/settings/preferences/show.html.haml @@ -35,6 +35,7 @@ .fields-group = f.input :setting_auto_play_gif, as: :boolean, wrapper: :with_label + = f.input :setting_reduce_motion, as: :boolean, wrapper: :with_label = f.input :setting_system_font_ui, as: :boolean, wrapper: :with_label .actions diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index a4eab16df..b488bd9ba 100644 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@ -19,15 +19,14 @@ %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more') .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }< = Formatter.instance.format(status, custom_emojify: true) - - if !status.media_attachments.empty? - if status.media_attachments.first.video? - video = status.media_attachments.first - %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 670, height: 380) }}>< + %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 670, height: 380) }}< - else - %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}>< + %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}< - elsif status.preview_cards.first - %div{ data: { component: 'Card', props: Oj.dump('maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json) }}>< + %div{ data: { component: 'Card', props: Oj.dump('maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json) }}< .detailed-status__meta %data.dt-published{ value: status.created_at.to_time.iso8601 } @@ -40,9 +39,16 @@ - else = link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener' · - %span< - = fa_icon('retweet') - %span= status.reblogs_count + - if status.direct_visibility? + %span< + = fa_icon('envelope') + - elsif status.private_visibility? + %span< + = fa_icon('lock') + - else + %span< + = fa_icon('retweet') + %span= status.reblogs_count · %span< = fa_icon('star') diff --git a/app/workers/scheduler/feed_cleanup_scheduler.rb b/app/workers/scheduler/feed_cleanup_scheduler.rb index dbebaa2c3..222f5ed84 100644 --- a/app/workers/scheduler/feed_cleanup_scheduler.rb +++ b/app/workers/scheduler/feed_cleanup_scheduler.rb @@ -6,8 +6,9 @@ class Scheduler::FeedCleanupScheduler def perform redis.pipelined do - inactive_users.pluck(:account_id).each do |account_id| + inactive_users.each do |account_id| redis.del(FeedManager.instance.key(:home, account_id)) + redis.del(FeedManager.instance.key(:home, account_id, 'reblogs')) end end end @@ -15,7 +16,7 @@ class Scheduler::FeedCleanupScheduler private def inactive_users - User.confirmed.inactive + @inactive_users ||= User.confirmed.inactive.pluck(:account_id) end def redis diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 2d72d247f..b30f797c7 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -120,9 +120,9 @@ oc: destroyed_msg: Nòta de moderacion ben suprimida ! custom_emojis: - copied_msg: Còpia locale de l’emoji ben creada + copied_msg: Còpia locala de l’emoji ben creada copy: Copiar - copy_failed_msg: Fracàs de la còpia locale de l’emoji + copy_failed_msg: Fracàs de la còpia locala de l’emoji created_msg: Emoji ben creat ! delete: Suprimir destroyed_msg: Emojo ben suprimit ! diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 86c80290c..aafae48ce 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -44,6 +44,7 @@ en: setting_default_sensitive: Always mark media as sensitive setting_delete_modal: Show confirmation dialog before deleting a toot setting_noindex: Opt-out of search engine indexing + setting_reduce_motion: Reduce motion in animations setting_system_font_ui: Use system's default font setting_theme: Site theme setting_unfollow_modal: Show confirmation dialog before unfollowing someone diff --git a/config/locales/simple_form.oc.yml b/config/locales/simple_form.oc.yml index d45f98e66..c1d5e2008 100644 --- a/config/locales/simple_form.oc.yml +++ b/config/locales/simple_form.oc.yml @@ -38,6 +38,7 @@ oc: otp_attempt: Còdi Two-factor password: Senhal setting_auto_play_gif: Lectura automatica dels GIFS animats + setting_reduce_motion: Reduire la velocitat de las animacions setting_boost_modal: Afichar una fenèstra de confirmacion abans de partejar un estatut setting_default_privacy: Confidencialitat de las publicacions setting_default_sensitive: Totjorn marcar los mèdias coma sensibles diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index e5d408973..68f84d109 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -48,6 +48,7 @@ pl: setting_default_sensitive: Zawsze oznaczaj zawartość multimedialną jako wrażliwą setting_delete_modal: Pytaj o potwierdzenie przed usunięciem wpisu setting_noindex: Nie indeksuj mojego profilu w wyszukiwarkach internetowych + setting_reduce_motion: Ogranicz ruch w animacjach setting_system_font_ui: Używaj domyślnej czcionki systemu setting_theme: Motyw strony setting_unfollow_modal: Pytaj o potwierdzenie przed cofnięciem śledzenia diff --git a/config/settings.yml b/config/settings.yml index 3cd3307f4..c03d0b766 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -22,6 +22,7 @@ defaults: &defaults boost_modal: false delete_modal: true auto_play_gif: false + reduce_motion: false system_font_ui: false noindex: false theme: 'default' diff --git a/db/migrate/20170920024819_status_ids_to_timestamp_ids.rb b/db/migrate/20170920024819_status_ids_to_timestamp_ids.rb index 5d15817bd..c10aa2c4f 100644 --- a/db/migrate/20170920024819_status_ids_to_timestamp_ids.rb +++ b/db/migrate/20170920024819_status_ids_to_timestamp_ids.rb @@ -26,7 +26,7 @@ class StatusIdsToTimestampIds < ActiveRecord::Migration[5.1] SELECT setval('statuses_id_seq', (SELECT MAX(id) FROM statuses)); ALTER TABLE statuses ALTER COLUMN id - SET DEFAULT nextval('statuses_id_seq');" + SET DEFAULT nextval('statuses_id_seq'); SQL end end diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 000000000..dd9dadf87 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,17 @@ +module.exports = { + projects: [ + '<rootDir>/app/javascript/mastodon', + ], + testPathIgnorePatterns: [ + '<rootDir>/node_modules/', + '<rootDir>/vendor/', + '<rootDir>/config/', + '<rootDir>/log/', + '<rootDir>/public/', + '<rootDir>/tmp/', + ], + setupFiles: [ + 'raf/polyfill', + ], + setupTestFrameworkScriptFile: '<rootDir>/app/javascript/mastodon/test_setup.js', +}; diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 0f2fc5ac6..e7b3085e5 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -21,7 +21,7 @@ module Mastodon end def flags - 'rc2' + 'rc3' end def to_a diff --git a/package.json b/package.json index 5dc2a9144..0863412eb 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,9 @@ "build:production": "cross-env RAILS_ENV=production ./bin/webpack", "manage:translations": "node ./config/webpack/translationRunner.js", "start": "node ./streaming/index.js", - "test": "npm run test:lint && npm run test:mocha", + "test": "npm run test:lint && npm run test:jest", "test:lint": "eslint -c .eslintrc.yml --ext=js app/javascript/ config/webpack/ spec/javascript/ streaming/", - "test:mocha": "cross-env NODE_ENV=test mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/**/*.test.js", + "test:jest": "cross-env NODE_ENV=test jest", "postinstall": "npm rebuild node-sass" }, "repository": { @@ -58,6 +58,7 @@ "immutable": "^3.8.1", "intersection-observer": "^0.4.0", "intl": "^1.2.5", + "intl-messageformat": "^2.1.0", "intl-relativeformat": "^2.0.0", "is-nan": "^1.2.1", "js-yaml": "^3.9.0", @@ -119,22 +120,37 @@ }, "devDependencies": { "babel-eslint": "^7.2.3", - "chai": "^4.1.0", - "chai-enzyme": "^0.8.0", "enzyme": "^3.0.0", "enzyme-adapter-react-16": "^1.0.0", "eslint": "^3.19.0", + "eslint-plugin-import": "^2.7.0", "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.10.3", - "jsdom": "^11.1.0", - "mocha": "^3.4.1", + "jest": "^21.2.1", + "raf": "^3.4.0", "react-intl-translations-manager": "^5.0.0", "react-test-renderer": "^16.0.0", - "sinon": "^2.3.7", "webpack-dev-server": "^2.6.1", "yargs": "^8.0.2" }, "optionalDependencies": { "fsevents": "*" + }, + "jest": { + "projects": [ + "<rootDir>/app/javascript/mastodon" + ], + "testPathIgnorePatterns": [ + "<rootDir>/node_modules/", + "<rootDir>/vendor/", + "<rootDir>/config/", + "<rootDir>/log/", + "<rootDir>/public/", + "<rootDir>/tmp/" + ], + "setupFiles": [ + "raf/polyfill" + ], + "setupTestFrameworkScriptFile": "<rootDir>/app/javascript/mastodon/test_setup.js" } } diff --git a/spec/javascript/.eslintrc.yml b/spec/javascript/.eslintrc.yml deleted file mode 100644 index 6db2a46c5..000000000 --- a/spec/javascript/.eslintrc.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -env: - mocha: true diff --git a/spec/javascript/components/avatar.test.js b/spec/javascript/components/avatar.test.js deleted file mode 100644 index 34949f2b5..000000000 --- a/spec/javascript/components/avatar.test.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import Avatar from '../../../app/javascript/mastodon/components/avatar'; - -import { expect } from 'chai'; -import { render } from 'enzyme'; -import { fromJS } from 'immutable'; - -describe('<Avatar />', () => { - const account = fromJS({ - username: 'alice', - acct: 'alice', - display_name: 'Alice', - avatar: '/animated/alice.gif', - avatar_static: '/static/alice.jpg', - }); - - const size = 100; - const animated = render(<Avatar account={account} animate size={size} />); - const still = render(<Avatar account={account} size={size} />); - - // Autoplay - xit('renders a div element with the given src as background', () => { - expect(animated.find('div')).to.have.style('background-image', `url(${account.get('avatar')})`); - }); - - xit('renders a div element of the given size', () => { - ['width', 'height'].map((attr) => { - expect(animated.find('div')).to.have.style(attr, `${size}px`); - }); - }); - - // Still - xit('renders a div element with the given static src as background if not autoplay', () => { - expect(still.find('div')).to.have.style('background-image', `url(${account.get('avatar_static')})`); - }); - - xit('renders a div element of the given size if not autoplay', () => { - ['width', 'height'].map((attr) => { - expect(still.find('div')).to.have.style(attr, `${size}px`); - }); - }); - - // TODO add autoplay test if possible -}); diff --git a/spec/javascript/components/avatar_overlay.test.js b/spec/javascript/components/avatar_overlay.test.js deleted file mode 100644 index fe1d3a012..000000000 --- a/spec/javascript/components/avatar_overlay.test.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import AvatarOverlay from '../../../app/javascript/mastodon/components/avatar_overlay'; - -import { expect } from 'chai'; -import { render } from 'enzyme'; -import { fromJS } from 'immutable'; - -describe('<Avatar />', () => { - const account = fromJS({ - username: 'alice', - acct: 'alice', - display_name: 'Alice', - avatar: '/animated/alice.gif', - avatar_static: '/static/alice.jpg', - }); - - const friend = fromJS({ - username: 'eve', - acct: 'eve@blackhat.lair', - display_name: 'Evelyn', - avatar: '/animated/eve.gif', - avatar_static: '/static/eve.jpg', - }); - - const overlay = render(<AvatarOverlay account={account} friend={friend} />); - - xit('renders account static src as base of overlay avatar', () => { - expect(overlay.find('.account__avatar-overlay-base')) - .to.have.style('background-image', `url(${account.get('avatar_static')})`); - }); - - xit('renders friend static src as overlay of overlay avatar', () => { - expect(overlay.find('.account__avatar-overlay-overlay')) - .to.have.style('background-image', `url(${friend.get('avatar_static')})`); - }); -}); diff --git a/spec/javascript/components/button.test.js b/spec/javascript/components/button.test.js deleted file mode 100644 index d2cd0b4e7..000000000 --- a/spec/javascript/components/button.test.js +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react'; -import Button from '../../../app/javascript/mastodon/components/button'; - -import { expect } from 'chai'; -import { shallow } from 'enzyme'; -import sinon from 'sinon'; - -describe('<Button />', () => { - xit('renders a button element', () => { - const wrapper = shallow(<Button />); - expect(wrapper).to.match('button'); - }); - - xit('renders the given text', () => { - const text = 'foo'; - const wrapper = shallow(<Button text={text} />); - expect(wrapper.find('button')).to.have.text(text); - }); - - it('handles click events using the given handler', () => { - const handler = sinon.spy(); - const wrapper = shallow(<Button onClick={handler} />); - wrapper.find('button').simulate('click'); - expect(handler.calledOnce).to.equal(true); - }); - - it('does not handle click events if props.disabled given', () => { - const handler = sinon.spy(); - const wrapper = shallow(<Button onClick={handler} disabled />); - wrapper.find('button').simulate('click'); - expect(handler.called).to.equal(false); - }); - - xit('renders a disabled attribute if props.disabled given', () => { - const wrapper = shallow(<Button disabled />); - expect(wrapper.find('button')).to.be.disabled(); - }); - - xit('renders the children', () => { - const children = <p>children</p>; - const wrapper = shallow(<Button>{children}</Button>); - expect(wrapper.find('button')).to.contain(children); - }); - - xit('renders the props.text instead of children', () => { - const text = 'foo'; - const children = <p>children</p>; - const wrapper = shallow(<Button text={text}>{children}</Button>); - expect(wrapper.find('button')).to.have.text(text); - expect(wrapper.find('button')).to.not.contain(children); - }); - - xit('renders style="display: block; width: 100%;" if props.block given', () => { - const wrapper = shallow(<Button block />); - expect(wrapper.find('button')).to.have.className('button--block'); - }); - - xit('renders style="display: inline-block; width: auto;" by default', () => { - const wrapper = shallow(<Button />); - expect(wrapper.find('button')).to.not.have.className('button--block'); - }); - - xit('adds class "button-secondary" if props.secondary given', () => { - const wrapper = shallow(<Button secondary />); - expect(wrapper.find('button')).to.have.className('button-secondary'); - }); - - xit('does not add class "button-secondary" by default', () => { - const wrapper = shallow(<Button />); - expect(wrapper.find('button')).to.not.have.className('button-secondary'); - }); -}); diff --git a/spec/javascript/components/display_name.test.js b/spec/javascript/components/display_name.test.js deleted file mode 100644 index 97a111894..000000000 --- a/spec/javascript/components/display_name.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import DisplayName from '../../../app/javascript/mastodon/components/display_name'; - -import { expect } from 'chai'; -import { render } from 'enzyme'; -import { fromJS } from 'immutable'; - -describe('<DisplayName />', () => { - xit('renders display name + account name', () => { - const account = fromJS({ - username: 'bar', - acct: 'bar@baz', - display_name_html: '<p>Foo</p>', - }); - const wrapper = render(<DisplayName account={account} />); - expect(wrapper).to.have.text('Foo @bar@baz'); - }); -}); diff --git a/spec/javascript/components/emoji_index.test.js b/spec/javascript/components/emoji_index.test.js deleted file mode 100644 index cdb50cb8c..000000000 --- a/spec/javascript/components/emoji_index.test.js +++ /dev/null @@ -1,111 +0,0 @@ -import { expect } from 'chai'; -import { search } from '../../../app/javascript/mastodon/features/emoji/emoji_mart_search_light'; -import { emojiIndex } from 'emoji-mart'; -import { pick } from 'lodash'; - -const trimEmojis = emoji => pick(emoji, ['id', 'unified', 'native', 'custom']); - -// hack to fix https://github.com/chaijs/type-detect/issues/98 -// see: https://github.com/chaijs/type-detect/issues/98#issuecomment-325010785 -import jsdom from 'jsdom'; -global.window = new jsdom.JSDOM().window; -global.document = window.document; -global.HTMLElement = window.HTMLElement; - -describe('emoji_index', () => { - - it('should give same result for emoji_index_light and emoji-mart', () => { - let expected = [{ - id: 'pineapple', - unified: '1f34d', - native: '🍍', - }]; - expect(search('pineapple').map(trimEmojis)).to.deep.equal(expected); - expect(emojiIndex.search('pineapple').map(trimEmojis)).to.deep.equal(expected); - }); - - it('orders search results correctly', () => { - let expected = [{ - id: 'apple', - unified: '1f34e', - native: '🍎', - }, { - id: 'pineapple', - unified: '1f34d', - native: '🍍', - }, { - id: 'green_apple', - unified: '1f34f', - native: '🍏', - }, { - id: 'iphone', - unified: '1f4f1', - native: '📱', - }]; - expect(search('apple').map(trimEmojis)).to.deep.equal(expected); - expect(emojiIndex.search('apple').map(trimEmojis)).to.deep.equal(expected); - }); - - it('handles custom emoji', () => { - let custom = [{ - id: 'mastodon', - name: 'mastodon', - short_names: ['mastodon'], - text: '', - emoticons: [], - keywords: ['mastodon'], - imageUrl: 'http://example.com', - custom: true, - }]; - search('', { custom }); - emojiIndex.search('', { custom }); - let expected = [ { id: 'mastodon', custom: true } ]; - expect(search('masto').map(trimEmojis)).to.deep.equal(expected); - expect(emojiIndex.search('masto').map(trimEmojis)).to.deep.equal(expected); - }); - - it('should filter only emojis we care about, exclude pineapple', () => { - let emojisToShowFilter = (unified) => unified !== '1F34D'; - expect(search('apple', { emojisToShowFilter }).map((obj) => obj.id)) - .not.to.contain('pineapple'); - expect(emojiIndex.search('apple', { emojisToShowFilter }).map((obj) => obj.id)) - .not.to.contain('pineapple'); - }); - - it('can include/exclude categories', () => { - expect(search('flag', { include: ['people'] })) - .to.deep.equal([]); - expect(emojiIndex.search('flag', { include: ['people'] })) - .to.deep.equal([]); - }); - - it('does an emoji whose unified name is irregular', () => { - let expected = [{ - 'id': 'water_polo', - 'unified': '1f93d', - 'native': '🤽', - }, { - 'id': 'man-playing-water-polo', - 'unified': '1f93d-200d-2642-fe0f', - 'native': '🤽♂️', - }, { - 'id': 'woman-playing-water-polo', - 'unified': '1f93d-200d-2640-fe0f', - 'native': '🤽♀️', - }]; - expect(search('polo').map(trimEmojis)).to.deep.equal(expected); - expect(emojiIndex.search('polo').map(trimEmojis)).to.deep.equal(expected); - }); - - it('can search for thinking_face', () => { - let expected = [ { id: 'thinking_face', unified: '1f914', native: '🤔' } ]; - expect(search('thinking_fac').map(trimEmojis)).to.deep.equal(expected); - expect(emojiIndex.search('thinking_fac').map(trimEmojis)).to.deep.equal(expected); - }); - - it('can search for woman-facepalming', () => { - let expected = [ { id: 'woman-facepalming', unified: '1f926-200d-2640-fe0f', native: '🤦♀️' } ]; - expect(search('woman-facep').map(trimEmojis)).to.deep.equal(expected); - expect(emojiIndex.search('woman-facep').map(trimEmojis)).deep.equal(expected); - }); -}); diff --git a/spec/javascript/components/emojify.test.js b/spec/javascript/components/emojify.test.js deleted file mode 100644 index 3105c8e3f..000000000 --- a/spec/javascript/components/emojify.test.js +++ /dev/null @@ -1,61 +0,0 @@ -import { expect } from 'chai'; -import emojify from '../../../app/javascript/mastodon/features/emoji/emoji'; - -describe('emojify', () => { - it('ignores unknown shortcodes', () => { - expect(emojify(':foobarbazfake:')).to.equal(':foobarbazfake:'); - }); - - it('ignores shortcodes inside of tags', () => { - expect(emojify('<p data-foo=":smile:"></p>')).to.equal('<p data-foo=":smile:"></p>'); - }); - - it('works with unclosed tags', () => { - expect(emojify('hello>')).to.equal('hello>'); - expect(emojify('<hello')).to.equal('<hello'); - }); - - it('works with unclosed shortcodes', () => { - expect(emojify('smile:')).to.equal('smile:'); - expect(emojify(':smile')).to.equal(':smile'); - }); - - it('does unicode', () => { - expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).to.equal( - '<img draggable="false" class="emojione" alt="👩👩👦👦" title=":woman-woman-boy-boy:" src="/emoji/1f469-200d-1f469-200d-1f466-200d-1f466.svg" />'); - expect(emojify('👨👩👧👧')).to.equal( - '<img draggable="false" class="emojione" alt="👨👩👧👧" title=":man-woman-girl-girl:" src="/emoji/1f468-200d-1f469-200d-1f467-200d-1f467.svg" />'); - expect(emojify('👩👩👦')).to.equal('<img draggable="false" class="emojione" alt="👩👩👦" title=":woman-woman-boy:" src="/emoji/1f469-200d-1f469-200d-1f466.svg" />'); - expect(emojify('\u2757')).to.equal( - '<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />'); - }); - - it('does multiple unicode', () => { - expect(emojify('\u2757 #\uFE0F\u20E3')).to.equal( - '<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" />'); - expect(emojify('\u2757#\uFE0F\u20E3')).to.equal( - '<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" />'); - expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).to.equal( - '<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" /> <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" />'); - expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).to.equal( - 'foo <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg" /> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg" /> bar'); - }); - - it('ignores unicode inside of tags', () => { - expect(emojify('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>')).to.equal('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>'); - }); - - it('does multiple emoji properly (issue 5188)', () => { - expect(emojify('👌🌈💕')).to.equal('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg" /><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg" /><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg" />'); - expect(emojify('👌 🌈 💕')).to.equal('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg" /> <img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg" /> <img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg" />'); - }); - - it('does an emoji that has no shortcode', () => { - expect(emojify('🕉️')).to.equal('<img draggable="false" class="emojione" alt="🕉️" title="" src="/emoji/1f549.svg" />'); - }); - - it('does an emoji whose filename is irregular', () => { - expect(emojify('↙️')).to.equal('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg" />'); - }); - -}); diff --git a/spec/javascript/setup.js b/spec/javascript/setup.js deleted file mode 100644 index ab8a36b95..000000000 --- a/spec/javascript/setup.js +++ /dev/null @@ -1,15 +0,0 @@ -import { JSDOM } from 'jsdom'; -import Enzyme from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; - -Enzyme.configure({ adapter: new Adapter() }); - -const { window } = new JSDOM('', { - userAgent: 'node.js', -}); - -Object.keys(window).forEach(property => { - if (typeof global[property] === 'undefined') { - global[property] = window[property]; - } -}); diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb index 923894ccb..454c3afec 100644 --- a/spec/lib/feed_manager_spec.rb +++ b/spec/lib/feed_manager_spec.rb @@ -231,33 +231,66 @@ RSpec.describe FeedManager do end describe '#unpush' do - it 'leaves a reblogged status when deleting the reblog' do - account = Fabricate(:account) + let(:receiver) { Fabricate(:account) } + + it 'leaves a reblogged status if original was on feed' do reblogged = Fabricate(:status) - status = Fabricate(:status, reblog: reblogged) + status = Fabricate(:status, reblog: reblogged) - FeedManager.instance.push('type', account, status) + FeedManager.instance.push('type', receiver, reblogged) + FeedManager::REBLOG_FALLOFF.times { FeedManager.instance.push('type', receiver, Fabricate(:status)) } + FeedManager.instance.push('type', receiver, status) + + # The reblogging status should show up under normal conditions. + expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to include(status.id.to_s) + + FeedManager.instance.unpush('type', receiver, status) + + # Restore original status + expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to_not include(status.id.to_s) + expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to include(reblogged.id.to_s) + end + + it 'removes a reblogged status if it was only reblogged once' do + reblogged = Fabricate(:status) + status = Fabricate(:status, reblog: reblogged) + + FeedManager.instance.push('type', receiver, status) # The reblogging status should show up under normal conditions. - expect(Redis.current.zrange("feed:type:#{account.id}", 0, -1)).to eq [status.id.to_s] + expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to eq [status.id.to_s] - FeedManager.instance.unpush('type', account, status) + FeedManager.instance.unpush('type', receiver, status) - # Because we couldn't tell if the status showed up any other way, - # we had to stick the reblogged status in by itself. - expect(Redis.current.zrange("feed:type:#{account.id}", 0, -1)).to eq [reblogged.id.to_s] + expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to be_empty + end + + it 'leaves a reblogged status if another reblog was in feed' do + reblogged = Fabricate(:status) + status = Fabricate(:status, reblog: reblogged) + another_status = Fabricate(:status, reblog: reblogged) + + FeedManager.instance.push('type', receiver, status) + FeedManager.instance.push('type', receiver, another_status) + + # The reblogging status should show up under normal conditions. + expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to eq [status.id.to_s] + + FeedManager.instance.unpush('type', receiver, status) + + expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to eq [another_status.id.to_s] end it 'sends push updates' do - account = Fabricate(:account) - status = Fabricate(:status) - FeedManager.instance.push('type', account, status) + status = Fabricate(:status) + + FeedManager.instance.push('type', receiver, status) allow(Redis.current).to receive_messages(publish: nil) - FeedManager.instance.unpush('type', account, status) + FeedManager.instance.unpush('type', receiver, status) deletion = Oj.dump(event: :delete, payload: status.id.to_s) - expect(Redis.current).to have_received(:publish).with("timeline:#{account.id}", deletion) + expect(Redis.current).to have_received(:publish).with("timeline:#{receiver.id}", deletion) end end end diff --git a/yarn.lock b/yarn.lock index a1c29badd..aceff463b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,8 +3,8 @@ "@types/node@^6.0.46": - version "6.0.80" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.80.tgz#914a75799605b4609bd9a2918c865ba3c4141367" + version "6.0.89" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.89.tgz#154be0e6a823760cd6083aa8c48f952e2e63e0b0" abab@^1.0.3: version "1.0.3" @@ -114,6 +114,10 @@ ansi-escapes@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" +ansi-escapes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" + ansi-html@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" @@ -136,6 +140,12 @@ ansi-styles@^3.1.0: dependencies: color-convert "^1.0.0" +ansi-styles@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" + dependencies: + color-convert "^1.9.0" + any-promise@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-0.1.0.tgz#830b680aa7e56f33451d4b049f3bd8044498ee27" @@ -151,6 +161,12 @@ ap@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/ap/-/ap-0.2.0.tgz#ae0942600b29912f0d2b14ec60c45e8f330b6110" +append-transform@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" + dependencies: + default-require-extensions "^1.0.0" + aproba@^1.0.3: version "1.1.2" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1" @@ -228,7 +244,7 @@ array.prototype.find@^2.0.1: define-properties "^1.1.2" es-abstract "^1.7.0" -arrify@^1.0.0: +arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -262,14 +278,14 @@ assert@^1.1.1, assert@^1.3.0: dependencies: util "0.10.3" -assertion-error@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" - ast-types-flow@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -282,11 +298,11 @@ async@0.2.x: version "0.2.10" resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" -async@^1.5.2: +async@^1.4.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.1.2, async@^2.1.5: +async@^2.1.2, async@^2.1.4, async@^2.1.5: version "2.5.0" resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" dependencies: @@ -357,7 +373,7 @@ babel-code-frame@^6.26.0: esutils "^2.0.2" js-tokens "^3.0.2" -babel-core@^6.25.0, babel-core@^6.26.0: +babel-core@^6.0.0, babel-core@^6.25.0, babel-core@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" dependencies: @@ -390,7 +406,7 @@ babel-eslint@^7.2.3: babel-types "^6.23.0" babylon "^6.17.0" -babel-generator@^6.26.0: +babel-generator@^6.18.0, babel-generator@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" dependencies: @@ -512,6 +528,13 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" +babel-jest@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-21.2.0.tgz#2ce059519a9374a2c46f2455b6fbef5ad75d863e" + dependencies: + babel-plugin-istanbul "^4.0.0" + babel-preset-jest "^21.2.0" + babel-loader@^7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.2.tgz#f6cbe122710f1aa2af4d881c6d5b54358ca24126" @@ -536,6 +559,18 @@ babel-plugin-check-es2015-constants@^6.22.0: dependencies: babel-runtime "^6.22.0" +babel-plugin-istanbul@^4.0.0: + version "4.1.5" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e" + dependencies: + find-up "^2.1.0" + istanbul-lib-instrument "^1.7.5" + test-exclude "^4.1.1" + +babel-plugin-jest-hoist@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-21.2.0.tgz#2cef637259bd4b628a6cace039de5fcd14dbb006" + babel-plugin-lodash@^3.2.11: version "3.2.11" resolved "https://registry.yarnpkg.com/babel-plugin-lodash/-/babel-plugin-lodash-3.2.11.tgz#21c8fdec9fe1835efaa737873e3902bdd66d5701" @@ -595,7 +630,7 @@ babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" -babel-plugin-syntax-object-rest-spread@^6.8.0: +babel-plugin-syntax-object-rest-spread@^6.13.0, babel-plugin-syntax-object-rest-spread@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" @@ -927,6 +962,13 @@ babel-preset-flow@^6.23.0: dependencies: babel-plugin-transform-flow-strip-types "^6.22.0" +babel-preset-jest@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-21.2.0.tgz#ff9d2bce08abd98e8a36d9a8a5189b9173b85638" + dependencies: + babel-plugin-jest-hoist "^21.2.0" + babel-plugin-syntax-object-rest-spread "^6.13.0" + babel-preset-react@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380" @@ -964,6 +1006,16 @@ babel-runtime@^6.26.0: core-js "^2.4.0" regenerator-runtime "^0.11.0" +babel-template@^6.16.0, babel-template@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + dependencies: + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" + babel-template@^6.24.1, babel-template@^6.3.0: version "6.25.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071" @@ -974,14 +1026,18 @@ babel-template@^6.24.1, babel-template@^6.3.0: babylon "^6.17.2" lodash "^4.2.0" -babel-template@^6.26.0: +babel-traverse@^6.18.0, babel-traverse@^6.26.0: version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" babel-runtime "^6.26.0" - babel-traverse "^6.26.0" babel-types "^6.26.0" babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" lodash "^4.17.4" babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.25.0: @@ -998,19 +1054,14 @@ babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.25.0: invariant "^2.2.0" lodash "^4.2.0" -babel-traverse@^6.26.0: +babel-types@^6.18.0, babel-types@^6.26.0: version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" dependencies: - babel-code-frame "^6.26.0" - babel-messages "^6.23.0" babel-runtime "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - debug "^2.6.8" - globals "^9.18.0" - invariant "^2.2.2" + esutils "^2.0.2" lodash "^4.17.4" + to-fast-properties "^1.0.3" babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.25.0: version "6.25.0" @@ -1021,15 +1072,6 @@ babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.25 lodash "^4.2.0" to-fast-properties "^1.0.1" -babel-types@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" - babylon@^6.17.0, babylon@^6.17.2: version "6.17.4" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" @@ -1143,9 +1185,11 @@ brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" -browser-stdout@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" +browser-resolve@^1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" + dependencies: + resolve "1.1.7" browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.0.6" @@ -1219,6 +1263,12 @@ browserslist@^2.4.0: caniuse-lite "^1.0.30000718" electron-to-chromium "^1.3.18" +bser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" + dependencies: + node-int64 "^0.4.0" + buffer-indexof@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982" @@ -1239,7 +1289,7 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -builtin-modules@^1.0.0: +builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -1265,6 +1315,10 @@ callsites@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + camelcase-css@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-1.0.1.tgz#157c4238265f5cf94a1dffde86446552cbf3f705" @@ -1324,24 +1378,6 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chai-enzyme@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/chai-enzyme/-/chai-enzyme-0.8.0.tgz#609c552a1dcdb091f435e1e281cc4f2149a33be1" - dependencies: - html "^1.0.0" - react-element-to-jsx-string "^5.0.0" - -chai@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" - dependencies: - assertion-error "^1.0.1" - check-error "^1.0.1" - deep-eql "^3.0.0" - get-func-name "^2.0.0" - pathval "^1.0.0" - type-detect "^4.0.0" - chain-function@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" @@ -1372,10 +1408,6 @@ chalk@^2.1.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" -check-error@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" - cheerio@^1.0.0-rc.2: version "1.0.0-rc.2" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" @@ -1402,6 +1434,10 @@ chokidar@^1.6.0, chokidar@^1.7.0: optionalDependencies: fsevents "^1.0.0" +ci-info@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.1.tgz#47b44df118c48d2597b56d342e7e25791060171a" + cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -1476,11 +1512,7 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" -collapse-white-space@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.3.tgz#4b906f670e5a963a87b76b0e1689643341b6023c" - -color-convert@^1.0.0, color-convert@^1.3.0: +color-convert@^1.0.0, color-convert@^1.3.0, color-convert@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" dependencies: @@ -1526,12 +1558,6 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - dependencies: - graceful-readlink ">= 1.0.0" - commander@^2.8.1, commander@^2.9.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" @@ -1575,7 +1601,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.4.7, concat-stream@^1.5.2: +concat-stream@^1.5.2: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" dependencies: @@ -1601,6 +1627,10 @@ constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + content-disposition@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" @@ -1621,7 +1651,7 @@ convert-source-map@^0.3.3: version "0.3.5" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190" -convert-source-map@^1.1.1, convert-source-map@^1.5.0: +convert-source-map@^1.1.1, convert-source-map@^1.4.0, convert-source-map@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" @@ -1934,7 +1964,7 @@ debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.4.5, debug@^2.6.6, debug@^2.6. dependencies: ms "2.0.0" -debug@2.6.9: +debug@2.6.9, debug@^2.6.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -1952,12 +1982,6 @@ decimal.js@7.2.3: version "7.2.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-7.2.3.tgz#6434c3b8a8c375780062fc633d0d2bbdb264cc78" -deep-eql@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" - dependencies: - type-detect "^4.0.0" - deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -1970,6 +1994,12 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" +default-require-extensions@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" + dependencies: + strip-bom "^2.0.0" + defaults@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" @@ -2051,13 +2081,9 @@ detect-passive-events@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/detect-passive-events/-/detect-passive-events-1.0.4.tgz#6ed477e6e5bceb79079735dcd357789d37f9a91a" -diff@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" - -diff@^3.1.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.0.tgz#056695150d7aa93237ca7e378ac3b1682b7963b9" +diff@^3.2.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" diffie-hellman@^5.0.0: version "5.0.2" @@ -2088,7 +2114,7 @@ dns-txt@^2.0.2: dependencies: buffer-indexof "^1.0.0" -doctrine@^1.2.2: +doctrine@1.5.0, doctrine@^1.2.2: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" dependencies: @@ -2163,10 +2189,6 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" -editions@^1.1.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.3.tgz#0907101bdda20fac3cbe334c27cbd0688dc99a5b" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -2231,8 +2253,8 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" enzyme-adapter-react-16@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.0.tgz#e7edd5536743818dcbef336d40d7da59b3a7db8e" + version "1.0.1" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.1.tgz#066cb1735e65d8d95841a023f94dab3ce6109e17" dependencies: enzyme-adapter-utils "^1.0.0" lodash "^4.17.4" @@ -2241,16 +2263,16 @@ enzyme-adapter-react-16@^1.0.0: prop-types "^15.5.10" enzyme-adapter-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.0.0.tgz#e94eee63da9a798d498adb1162a2102ed04fc638" + version "1.0.1" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.0.1.tgz#fcd81223339a55a312f7552641e045c404084009" dependencies: lodash "^4.17.4" object.assign "^4.0.4" prop-types "^15.5.10" enzyme@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.0.0.tgz#94ce364254dc654c4e619b25eecc644bf6481de7" + version "3.1.0" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.1.0.tgz#d8ca84085790fbcec6ed40badd14478faee4c25a" dependencies: cheerio "^1.0.0-rc.2" function.prototype.name "^1.0.3" @@ -2261,9 +2283,9 @@ enzyme@^3.0.0: object.entries "^1.0.4" object.values "^1.0.4" raf "^3.3.2" - rst-selector-parser "^2.2.1" + rst-selector-parser "^2.2.2" -errno@^0.1.3: +errno@^0.1.3, errno@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" dependencies: @@ -2275,7 +2297,17 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.6.1, es-abstract@^1.7.0: +es-abstract@^1.6.1: + version "1.9.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.9.0.tgz#690829a07cae36b222e7fd9b75c0d0573eb25227" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.1" + has "^1.0.1" + is-callable "^1.1.3" + is-regex "^1.0.4" + +es-abstract@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c" dependencies: @@ -2348,7 +2380,7 @@ escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2372,6 +2404,35 @@ escope@^3.6.0: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-import-resolver-node@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz#4422574cde66a9a7b099938ee4d508a199e0e3cc" + dependencies: + debug "^2.6.8" + resolve "^1.2.0" + +eslint-module-utils@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449" + dependencies: + debug "^2.6.8" + pkg-dir "^1.0.0" + +eslint-plugin-import@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz#21de33380b9efb55f5ef6d2e210ec0e07e7fa69f" + dependencies: + builtin-modules "^1.1.1" + contains-path "^0.1.0" + debug "^2.6.8" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.1" + eslint-module-utils "^2.1.1" + has "^1.0.1" + lodash.cond "^4.3.0" + minimatch "^3.0.3" + read-pkg-up "^2.0.0" + eslint-plugin-jsx-a11y@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-4.0.0.tgz#779bb0fe7b08da564a422624911de10061e048ee" @@ -2508,6 +2569,12 @@ evp_bytestokey@^1.0.0: dependencies: create-hash "^1.1.1" +exec-sh@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38" + dependencies: + merge "^1.1.3" + execa@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/execa/-/execa-0.5.1.tgz#de3fb85cb8d6e91c85bcbceb164581785cb57b36" @@ -2536,6 +2603,17 @@ expand-range@^1.8.1: dependencies: fill-range "^2.1.0" +expect@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-21.2.1.tgz#003ac2ac7005c3c29e73b38a272d4afadd6d1d7b" + dependencies: + ansi-styles "^3.2.0" + jest-diff "^21.2.1" + jest-get-type "^21.2.0" + jest-matcher-utils "^21.2.1" + jest-message-util "^21.2.1" + jest-regex-util "^21.2.0" + express@^4.13.3: version "4.15.3" resolved "https://registry.yarnpkg.com/express/-/express-4.15.3.tgz#bab65d0f03aa80c358408972fc700f916944b662" @@ -2651,6 +2729,12 @@ faye-websocket@~0.11.0: dependencies: websocket-driver ">=0.5.1" +fb-watchman@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" + dependencies: + bser "^2.0.0" + fbjs@^0.8.14, fbjs@^0.8.16: version "0.8.16" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" @@ -2699,6 +2783,13 @@ filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" +fileset@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" + dependencies: + glob "^7.0.3" + minimatch "^3.0.3" + filesize@^3.5.9: version "3.5.10" resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.10.tgz#fc8fa23ddb4ef9e5e0ab6e1e64f679a24a56761f" @@ -2817,12 +2908,6 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" -formatio@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb" - dependencies: - samsam "1.x" - forwarded@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" @@ -2857,7 +2942,7 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" -fsevents@*, fsevents@^1.0.0: +fsevents@*, fsevents@^1.0.0, fsevents@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" dependencies: @@ -2881,9 +2966,9 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "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-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" function.prototype.name@^1.0.3: version "1.0.3" @@ -2930,10 +3015,6 @@ get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" -get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" @@ -2964,17 +3045,6 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" @@ -3025,17 +3095,13 @@ gonzales-pe@^4.0.3: dependencies: minimist "1.1.x" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - -growl@1.9.2: - version "1.9.2" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" gzip-size@^3.0.0: version "3.0.0" @@ -3047,6 +3113,16 @@ handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" +handlebars@^4.0.3: + version "4.0.10" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + har-schema@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" @@ -3104,10 +3180,6 @@ hawk@~3.1.3: hoek "2.x.x" sntp "1.x.x" -he@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" - history@^4.7.2: version "4.7.2" resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" @@ -3168,12 +3240,6 @@ html-entities@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" -html@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/html/-/html-1.0.0.tgz#a544fa9ea5492bfb3a2cca8210a10be7b5af1f61" - dependencies: - concat-stream "^1.4.7" - htmlparser2@^3.9.1: version "3.9.2" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" @@ -3430,6 +3496,12 @@ is-callable@^1.1.1, 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.10: + 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" @@ -3553,16 +3625,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: +is-regex@^1.0.3, is-regex@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" dependencies: has "^1.0.1" -is-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" - is-resolvable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" @@ -3636,10 +3704,298 @@ isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" +istanbul-api@^1.1.1: + version "1.1.14" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.14.tgz#25bc5701f7c680c0ffff913de46e3619a3a6e680" + dependencies: + async "^2.1.4" + fileset "^2.0.2" + istanbul-lib-coverage "^1.1.1" + istanbul-lib-hook "^1.0.7" + istanbul-lib-instrument "^1.8.0" + istanbul-lib-report "^1.1.1" + istanbul-lib-source-maps "^1.2.1" + istanbul-reports "^1.1.2" + js-yaml "^3.7.0" + mkdirp "^0.5.1" + once "^1.4.0" + +istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" + +istanbul-lib-hook@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.7.tgz#dd6607f03076578fe7d6f2a630cf143b49bacddc" + dependencies: + append-transform "^0.4.0" + +istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.8.0.tgz#66f6c9421cc9ec4704f76f2db084ba9078a2b532" + dependencies: + babel-generator "^6.18.0" + babel-template "^6.16.0" + babel-traverse "^6.18.0" + babel-types "^6.18.0" + babylon "^6.18.0" + istanbul-lib-coverage "^1.1.1" + semver "^5.3.0" + +istanbul-lib-report@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#f0e55f56655ffa34222080b7a0cd4760e1405fc9" + dependencies: + istanbul-lib-coverage "^1.1.1" + mkdirp "^0.5.1" + path-parse "^1.0.5" + supports-color "^3.1.2" + +istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.1.tgz#a6fe1acba8ce08eebc638e572e294d267008aa0c" + dependencies: + debug "^2.6.3" + istanbul-lib-coverage "^1.1.1" + mkdirp "^0.5.1" + rimraf "^2.6.1" + source-map "^0.5.3" + +istanbul-reports@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.2.tgz#0fb2e3f6aa9922bd3ce45d05d8ab4d5e8e07bd4f" + dependencies: + handlebars "^4.0.3" + javascript-natural-sort@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" +jest-changed-files@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-21.2.0.tgz#5dbeecad42f5d88b482334902ce1cba6d9798d29" + dependencies: + throat "^4.0.0" + +jest-cli@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-21.2.1.tgz#9c528b6629d651911138d228bdb033c157ec8c00" + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.1" + glob "^7.1.2" + graceful-fs "^4.1.11" + is-ci "^1.0.10" + istanbul-api "^1.1.1" + istanbul-lib-coverage "^1.0.1" + istanbul-lib-instrument "^1.4.2" + istanbul-lib-source-maps "^1.1.0" + jest-changed-files "^21.2.0" + jest-config "^21.2.1" + jest-environment-jsdom "^21.2.1" + jest-haste-map "^21.2.0" + jest-message-util "^21.2.1" + jest-regex-util "^21.2.0" + jest-resolve-dependencies "^21.2.0" + jest-runner "^21.2.1" + jest-runtime "^21.2.1" + jest-snapshot "^21.2.1" + jest-util "^21.2.1" + micromatch "^2.3.11" + node-notifier "^5.0.2" + pify "^3.0.0" + slash "^1.0.0" + string-length "^2.0.0" + strip-ansi "^4.0.0" + which "^1.2.12" + worker-farm "^1.3.1" + yargs "^9.0.0" + +jest-config@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-21.2.1.tgz#c7586c79ead0bcc1f38c401e55f964f13bf2a480" + dependencies: + chalk "^2.0.1" + glob "^7.1.1" + jest-environment-jsdom "^21.2.1" + jest-environment-node "^21.2.1" + jest-get-type "^21.2.0" + jest-jasmine2 "^21.2.1" + jest-regex-util "^21.2.0" + jest-resolve "^21.2.0" + jest-util "^21.2.1" + jest-validate "^21.2.1" + pretty-format "^21.2.1" + +jest-diff@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-21.2.1.tgz#46cccb6cab2d02ce98bc314011764bb95b065b4f" + dependencies: + chalk "^2.0.1" + diff "^3.2.0" + jest-get-type "^21.2.0" + pretty-format "^21.2.1" + +jest-docblock@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" + +jest-environment-jsdom@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-21.2.1.tgz#38d9980c8259b2a608ec232deee6289a60d9d5b4" + dependencies: + jest-mock "^21.2.0" + jest-util "^21.2.1" + jsdom "^9.12.0" + +jest-environment-node@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-21.2.1.tgz#98c67df5663c7fbe20f6e792ac2272c740d3b8c8" + dependencies: + jest-mock "^21.2.0" + jest-util "^21.2.1" + +jest-get-type@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.2.0.tgz#f6376ab9db4b60d81e39f30749c6c466f40d4a23" + +jest-haste-map@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-21.2.0.tgz#1363f0a8bb4338f24f001806571eff7a4b2ff3d8" + dependencies: + fb-watchman "^2.0.0" + graceful-fs "^4.1.11" + jest-docblock "^21.2.0" + micromatch "^2.3.11" + sane "^2.0.0" + worker-farm "^1.3.1" + +jest-jasmine2@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-21.2.1.tgz#9cc6fc108accfa97efebce10c4308548a4ea7592" + dependencies: + chalk "^2.0.1" + expect "^21.2.1" + graceful-fs "^4.1.11" + jest-diff "^21.2.1" + jest-matcher-utils "^21.2.1" + jest-message-util "^21.2.1" + jest-snapshot "^21.2.1" + p-cancelable "^0.3.0" + +jest-matcher-utils@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-21.2.1.tgz#72c826eaba41a093ac2b4565f865eb8475de0f64" + dependencies: + chalk "^2.0.1" + jest-get-type "^21.2.0" + pretty-format "^21.2.1" + +jest-message-util@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-21.2.1.tgz#bfe5d4692c84c827d1dcf41823795558f0a1acbe" + dependencies: + chalk "^2.0.1" + micromatch "^2.3.11" + slash "^1.0.0" + +jest-mock@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-21.2.0.tgz#7eb0770e7317968165f61ea2a7281131534b3c0f" + +jest-regex-util@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-21.2.0.tgz#1b1e33e63143babc3e0f2e6c9b5ba1eb34b2d530" + +jest-resolve-dependencies@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-21.2.0.tgz#9e231e371e1a736a1ad4e4b9a843bc72bfe03d09" + dependencies: + jest-regex-util "^21.2.0" + +jest-resolve@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-21.2.0.tgz#068913ad2ba6a20218e5fd32471f3874005de3a6" + dependencies: + browser-resolve "^1.11.2" + chalk "^2.0.1" + is-builtin-module "^1.0.0" + +jest-runner@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-21.2.1.tgz#194732e3e518bfb3d7cbfc0fd5871246c7e1a467" + dependencies: + jest-config "^21.2.1" + jest-docblock "^21.2.0" + jest-haste-map "^21.2.0" + jest-jasmine2 "^21.2.1" + jest-message-util "^21.2.1" + jest-runtime "^21.2.1" + jest-util "^21.2.1" + pify "^3.0.0" + throat "^4.0.0" + worker-farm "^1.3.1" + +jest-runtime@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-21.2.1.tgz#99dce15309c670442eee2ebe1ff53a3cbdbbb73e" + dependencies: + babel-core "^6.0.0" + babel-jest "^21.2.0" + babel-plugin-istanbul "^4.0.0" + chalk "^2.0.1" + convert-source-map "^1.4.0" + graceful-fs "^4.1.11" + jest-config "^21.2.1" + jest-haste-map "^21.2.0" + jest-regex-util "^21.2.0" + jest-resolve "^21.2.0" + jest-util "^21.2.1" + json-stable-stringify "^1.0.1" + micromatch "^2.3.11" + slash "^1.0.0" + strip-bom "3.0.0" + write-file-atomic "^2.1.0" + yargs "^9.0.0" + +jest-snapshot@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-21.2.1.tgz#29e49f16202416e47343e757e5eff948c07fd7b0" + dependencies: + chalk "^2.0.1" + jest-diff "^21.2.1" + jest-matcher-utils "^21.2.1" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^21.2.1" + +jest-util@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-21.2.1.tgz#a274b2f726b0897494d694a6c3d6a61ab819bb78" + dependencies: + callsites "^2.0.0" + chalk "^2.0.1" + graceful-fs "^4.1.11" + jest-message-util "^21.2.1" + jest-mock "^21.2.0" + jest-validate "^21.2.1" + mkdirp "^0.5.1" + +jest-validate@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-21.2.1.tgz#cc0cbca653cd54937ba4f2a111796774530dd3c7" + dependencies: + chalk "^2.0.1" + jest-get-type "^21.2.0" + leven "^2.1.0" + pretty-format "^21.2.1" + +jest@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-21.2.1.tgz#c964e0b47383768a1438e3ccf3c3d470327604e1" + dependencies: + jest-cli "^21.2.1" + js-base64@^2.1.8, js-base64@^2.1.9: version "2.1.9" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" @@ -3659,7 +4015,7 @@ js-yaml@^3.4.3, js-yaml@^3.5.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.9.0: +js-yaml@^3.7.0, js-yaml@^3.9.0: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: @@ -3677,9 +4033,9 @@ jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" -jsdom@^11.1.0: - version "11.2.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.2.0.tgz#4f6b8736af3357c3af7227a3b54a5bda1c513fd6" +jsdom@^9.12.0: + version "9.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" dependencies: abab "^1.0.3" acorn "^4.0.4" @@ -3690,17 +4046,15 @@ jsdom@^11.1.0: cssstyle ">= 0.2.37 < 0.3.0" escodegen "^1.6.1" html-encoding-sniffer "^1.0.1" - nwmatcher "^1.4.1" - parse5 "^3.0.2" - pn "^1.0.0" + nwmatcher ">= 1.3.9 < 2.0.0" + parse5 "^1.5.1" request "^2.79.0" - request-promise-native "^1.0.3" sax "^1.2.1" symbol-tree "^3.2.1" tough-cookie "^2.3.2" webidl-conversions "^4.0.0" whatwg-encoding "^1.0.1" - whatwg-url "^6.1.0" + whatwg-url "^4.3.0" xml-name-validator "^2.0.1" jsesc@^1.3.0: @@ -3733,7 +4087,7 @@ json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" -json3@3.3.2, json3@^3.3.2: +json3@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" @@ -3810,6 +4164,10 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" +leven@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -3879,10 +4237,6 @@ lodash._basecopy@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" -lodash._basecreate@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" - lodash._bindcallback@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" @@ -3923,13 +4277,9 @@ lodash.clonedeep@^4.3.2: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" -lodash.create@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" - dependencies: - lodash._baseassign "^3.0.0" - lodash._basecreate "^3.0.0" - lodash._isiterateecall "^3.0.0" +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@^3.1.2: version "3.1.2" @@ -3974,10 +4324,6 @@ lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - lodash.tail@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" @@ -3994,10 +4340,6 @@ loglevel@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.4.1.tgz#95b383f91a3c2756fd4ab093667e4309161f2bcd" -lolex@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6" - longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" @@ -4032,6 +4374,12 @@ make-dir@^1.0.0: dependencies: pify "^2.3.0" +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + dependencies: + tmpl "1.0.x" + map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" @@ -4096,6 +4444,10 @@ merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" +merge@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -4187,10 +4539,14 @@ minimist@1.1.x: version "1.1.3" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8" -minimist@^1.1.3, minimist@^1.2.0: +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + mixin-object@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" @@ -4198,29 +4554,12 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" -mocha@^3.4.1: - version "3.5.3" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.5.3.tgz#1e0480fe36d2da5858d1eb6acc38418b26eaa20d" - dependencies: - browser-stdout "1.3.0" - commander "2.9.0" - debug "2.6.8" - diff "3.2.0" - escape-string-regexp "1.0.5" - glob "7.1.1" - growl "1.9.2" - he "1.1.1" - json3 "3.3.2" - lodash.create "3.1.1" - mkdirp "0.5.1" - supports-color "3.1.2" - mousetrap@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.1.tgz#2a085f5c751294c75e7e81f6ec2545b29cbf42d9" @@ -4248,10 +4587,6 @@ nan@^2.0.0, nan@^2.3.0, nan@^2.3.2: version "2.6.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" -native-promise-only@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11" - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -4297,6 +4632,10 @@ node-gyp@^3.3.1: tar "^2.0.0" which "1" +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + node-libs-browser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.0.0.tgz#a3a59ec97024985b46e958379646f96c4b616646" @@ -4325,6 +4664,15 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" +node-notifier@^5.0.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff" + dependencies: + growly "^1.3.0" + semver "^5.3.0" + shellwords "^0.1.0" + which "^1.2.12" + node-pre-gyp@^0.6.36, node-pre-gyp@^0.6.4: version "0.6.36" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786" @@ -4448,9 +4796,9 @@ number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" -nwmatcher@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.1.tgz#7ae9b07b0ea804db7e25f05cb5fe4097d4e4949f" +"nwmatcher@>= 1.3.9 < 2.0.0": + version "1.4.3" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c" oauth-sign@~0.8.1: version "0.8.2" @@ -4537,7 +4885,7 @@ on-headers@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" -once@^1.3.0, once@^1.3.3: +once@^1.3.0, once@^1.3.3, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: @@ -4557,6 +4905,13 @@ opn@^5.1.0: dependencies: is-wsl "^1.1.0" +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + optionator@^0.8.1, optionator@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" @@ -4607,6 +4962,10 @@ osenv@0, osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-cancelable@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -4672,7 +5031,11 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" -parse5@^3.0.1, parse5@^3.0.2: +parse5@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + +parse5@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.2.tgz#05eff57f0ef4577fb144a79f8b9a967a6cc44510" dependencies: @@ -4744,10 +5107,6 @@ path-type@^2.0.0: dependencies: pify "^2.0.0" -pathval@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" - pbkdf2@^3.0.3: version "3.0.12" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.12.tgz#be36785c5067ea48d806ff923288c5f750b6b8a2" @@ -4824,6 +5183,12 @@ pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" @@ -4834,10 +5199,6 @@ pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" -pn@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pn/-/pn-1.0.0.tgz#1cf5a30b0d806cd18f88fc41a6b5d4ad615b3ba9" - portfinder@^1.0.9: version "1.0.13" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" @@ -5352,6 +5713,13 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" +pretty-format@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-21.2.1.tgz#ae5407f3cf21066cd011aa1ba5fce7b6a2eddb36" + dependencies: + ansi-regex "^3.0.0" + ansi-styles "^3.2.0" + private@^0.1.6, private@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" @@ -5484,12 +5852,18 @@ quote@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/quote/-/quote-0.4.0.tgz#10839217f6c1362b89194044d29b233fd7f32f01" -raf@^3.1.0, raf@^3.3.2: +raf@^3.1.0: version "3.3.2" resolved "https://registry.yarnpkg.com/raf/-/raf-3.3.2.tgz#0c13be0b5b49b46f76d6669248d527cf2b02fe27" dependencies: performance-now "^2.1.0" +raf@^3.3.2, raf@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" + dependencies: + performance-now "^2.1.0" + railroad-diagrams@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" @@ -5549,17 +5923,6 @@ react-dom@^16.0.0: object-assign "^4.1.1" prop-types "^15.6.0" -react-element-to-jsx-string@^5.0.0: - version "5.0.7" - resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-5.0.7.tgz#c663a4800a9c712115c0d8519cb0215a46a1f0f2" - dependencies: - collapse-white-space "^1.0.0" - is-plain-object "^2.0.1" - lodash "^4.17.4" - sortobject "^1.0.0" - stringify-object "2.4.0" - traverse "^0.6.6" - react-event-listener@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/react-event-listener/-/react-event-listener-0.5.0.tgz#d82105135573e187e3d900d18150a5882304b8d1" @@ -5945,20 +6308,6 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request-promise-core@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" - dependencies: - lodash "^4.13.1" - -request-promise-native@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.4.tgz#86988ec8eee408e45579fce83bfd05b3adf9a155" - dependencies: - request-promise-core "1.1.1" - stealthy-require "^1.1.0" - tough-cookie ">=2.3.0" - request@2, request@^2.79.0, request@^2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" @@ -6043,12 +6392,22 @@ resolve-url@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" dependencies: path-parse "^1.0.5" +resolve@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" + dependencies: + path-parse "^1.0.5" + restore-cursor@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" @@ -6100,7 +6459,7 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^2.0.0" inherits "^2.0.1" -rst-selector-parser@^2.2.1: +rst-selector-parser@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.2.tgz#9927b619bd5af8dc23a76c64caef04edf90d2c65" dependencies: @@ -6125,9 +6484,19 @@ safe-buffer@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" -samsam@1.x, samsam@^1.1.3: - version "1.2.1" - resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.2.1.tgz#edd39093a3184370cb859243b2bdf255e7d8ea67" +sane@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-2.2.0.tgz#d6d2e2fcab00e3d283c93b912b7c3a20846f1d56" + dependencies: + anymatch "^1.3.0" + exec-sh "^0.2.0" + fb-watchman "^2.0.0" + minimatch "^3.0.2" + minimist "^1.1.1" + walker "~1.0.5" + watch "~0.18.0" + optionalDependencies: + fsevents "^1.1.1" sass-graph@^2.1.1: version "2.2.4" @@ -6313,23 +6682,14 @@ shelljs@^0.7.5: interpret "^1.0.0" rechoir "^0.6.2" -signal-exit@^3.0.0: +shellwords@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + +signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" -sinon@^2.3.7: - version "2.4.1" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.4.1.tgz#021fd64b54cb77d9d2fb0d43cdedfae7629c3a36" - dependencies: - diff "^3.1.0" - formatio "1.2.0" - lolex "^1.6.0" - native-promise-only "^0.8.1" - path-to-regexp "^1.7.0" - samsam "^1.1.3" - text-encoding "0.6.4" - type-detect "^4.0.0" - slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" @@ -6368,12 +6728,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -sortobject@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/sortobject/-/sortobject-1.1.1.tgz#4f695d4d44ed0a4c06482c34c2582a2dcdc2ab34" - dependencies: - editions "^1.1.1" - source-list-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" @@ -6407,7 +6761,7 @@ source-map@^0.1.38: dependencies: amdefine ">=0.0.4" -source-map@^0.4.2: +source-map@^0.4.2, source-map@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: @@ -6498,10 +6852,6 @@ stdout-stream@^1.4.0: dependencies: readable-stream "^2.0.1" -stealthy-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" - stream-browserify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" @@ -6523,6 +6873,13 @@ strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" +string-length@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" + dependencies: + astral-regex "^1.0.0" + strip-ansi "^4.0.0" + string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -6548,13 +6905,6 @@ string_decoder@~1.0.3: dependencies: safe-buffer "~5.1.0" -stringify-object@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-2.4.0.tgz#c62d11023eb21fe2d9b087be039a26df3b22a09d" - dependencies: - is-plain-obj "^1.0.0" - is-regexp "^1.0.0" - stringstream@~0.0.4: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" @@ -6575,16 +6925,16 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" +strip-bom@3.0.0, strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" dependencies: is-utf8 "^0.2.0" -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -6616,17 +6966,11 @@ sugarss@^1.0.0: dependencies: postcss "^6.0.0" -supports-color@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" - dependencies: - has-flag "^1.0.0" - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.2.3: +supports-color@^3.1.2, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: @@ -6710,14 +7054,24 @@ tcomb@^2.5.0, tcomb@^2.5.1: version "2.7.0" resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0" -text-encoding@0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" +test-exclude@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" + dependencies: + arrify "^1.0.1" + micromatch "^2.3.11" + object-assign "^4.1.0" + read-pkg-up "^1.0.1" + require-main-filename "^1.0.1" text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" +throat@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" + throng@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/throng/-/throng-4.0.0.tgz#983c6ba1993b58eae859998aa687ffe88df84c17" @@ -6746,6 +7100,10 @@ tiny-queue@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tiny-queue/-/tiny-queue-0.2.1.tgz#25a67f2c6e253b2ca941977b5ef7442ef97a6046" +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" @@ -6754,7 +7112,7 @@ to-fast-properties@^1.0.1, to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" -tough-cookie@>=2.3.0, tough-cookie@^2.3.2, tough-cookie@~2.3.0: +tough-cookie@^2.3.2, tough-cookie@~2.3.0: version "2.3.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" dependencies: @@ -6764,10 +7122,6 @@ tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" -traverse@^0.6.6: - version "0.6.6" - resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" - trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -6800,10 +7154,6 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.3.tgz#0e3f2670b44099b0b46c284d136a7ef49c74c2ea" - type-is@~1.6.15: version "1.6.15" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" @@ -6823,7 +7173,7 @@ ua-parser-js@^0.7.9: version "0.7.13" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.13.tgz#cd9dd2f86493b3f44dbeeef3780fda74c5ee14be" -uglify-js@^2.8.29: +uglify-js@^2.6, uglify-js@^2.8.29: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" dependencies: @@ -6974,12 +7324,25 @@ vm-browserify@0.0.4: dependencies: indexof "0.0.1" +walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + dependencies: + makeerror "1.0.x" + warning@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" dependencies: loose-envify "^1.0.0" +watch@~0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" + dependencies: + exec-sh "^0.2.0" + minimist "^1.2.0" + watchpack@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" @@ -6994,7 +7357,11 @@ wbuf@^1.1.0, wbuf@^1.7.2: dependencies: minimalistic-assert "^1.0.0" -webidl-conversions@^4.0.0, webidl-conversions@^4.0.1: +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + +webidl-conversions@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.1.tgz#8015a17ab83e7e1b311638486ace81da6ce206a0" @@ -7132,13 +7499,12 @@ whatwg-fetch@>=0.10.0: version "2.0.3" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" -whatwg-url@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.1.0.tgz#5fc8279b93d75483b9ced8b26239854847a18578" +whatwg-url@^4.3.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0" dependencies: - lodash.sortby "^4.7.0" tr46 "~0.0.3" - webidl-conversions "^4.0.1" + webidl-conversions "^3.0.0" whet.extend@~0.9.9: version "0.9.9" @@ -7158,6 +7524,12 @@ which@1, which@^1.2.9: dependencies: isexe "^2.0.0" +which@^1.2.12: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" @@ -7172,10 +7544,21 @@ wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" +worker-farm@^1.3.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.0.tgz#adfdf0cd40581465ed0a1f648f9735722afd5c8d" + dependencies: + errno "^0.1.4" + xtend "^4.0.1" + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -7187,6 +7570,14 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" +write-file-atomic@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + write@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" @@ -7204,7 +7595,7 @@ xml-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" -xtend@^4.0.0: +xtend@^4.0.0, xtend@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -7288,6 +7679,24 @@ yargs@^8.0.2: y18n "^3.2.1" yargs-parser "^7.0.0" +yargs@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c" + dependencies: + camelcase "^4.1.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + read-pkg-up "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^7.0.0" + yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" |