Przeglądaj źródła

Improve unit testing framework and add new tests

Updated enzyme library to support React's Portal elements and updated
our testing framework to allow testing of Portal components (components
that need the DOM to render, like modals and dropdowns).

Added unit tests to newer components that didn't have test suites.
Sergiu Miclea 7 lat temu
rodzic
commit
d0c46ab377
32 zmienionych plików z 972 dodań i 136 usunięć
  1. 4 5
      package.json
  2. 6 2
      src/components/atoms/SmallLoading/SmallLoading.jsx
  3. 34 0
      src/components/atoms/SmallLoading/test.jsx
  4. 10 6
      src/components/molecules/ActionDropdown/ActionDropdown.jsx
  5. 92 0
      src/components/molecules/ActionDropdown/test.jsx
  6. 1 1
      src/components/molecules/LoginOptions/test.jsx
  7. 7 6
      src/components/molecules/MainDetailsTable/MainDetailsTable.jsx
  8. 108 0
      src/components/molecules/MainDetailsTable/test.jsx
  9. 1 1
      src/components/molecules/MainListFilter/test.jsx
  10. 1 1
      src/components/molecules/MainListItem/test.jsx
  11. 3 1
      src/components/molecules/NavigationMini/NavigationMini.jsx
  12. 35 0
      src/components/molecules/NavigationMini/test.jsx
  13. 2 2
      src/components/molecules/NewItemDropdown/test.jsx
  14. 5 2
      src/components/molecules/Panel/Panel.jsx
  15. 61 0
      src/components/molecules/Panel/test.jsx
  16. 2 2
      src/components/molecules/PropertiesTable/test.jsx
  17. 2 2
      src/components/molecules/Table/test.jsx
  18. 1 1
      src/components/molecules/Timeline/test.jsx
  19. 2 3
      src/components/organisms/DetailsContentHeader/test.jsx
  20. 1 1
      src/components/organisms/DropdownFilterGroup/test.jsx
  21. 15 8
      src/components/organisms/UserDetailsContent/UserDetailsContent.jsx
  22. 100 0
      src/components/organisms/UserDetailsContent/test.jsx
  23. 1 1
      src/components/organisms/WizardEndpointList/test.jsx
  24. 1 1
      src/components/organisms/WizardInstances/test.jsx
  25. 1 1
      src/components/organisms/WizardNetworks/test.jsx
  26. 2 2
      src/components/organisms/WizardOptions/test.jsx
  27. 9 4
      src/components/organisms/WizardStorage/WizardStorage.jsx
  28. 120 0
      src/components/organisms/WizardStorage/test.jsx
  29. 1 1
      src/types/MainItem.js
  30. 0 1
      src/types/Project.js
  31. 22 5
      src/utils/TestWrapper.js
  32. 322 76
      yarn.lock

+ 4 - 5
package.json

@@ -1,6 +1,6 @@
 {
   "name": "coriolis-web",
-  "version": "1.4.4",
+  "version": "1.4.5",
   "license": "AGPL-3.0",
   "scripts": {
     "start": "npm run env:dev && node server.js --dev",
@@ -8,14 +8,13 @@
     "env:prod": "cross-env NODE_ENV=production",
     "cypress": "cypress open",
     "test": "jest",
-    "testc": "jest --runInBand -t 'WizardOptions Component'",
+    "testc": "jest --runInBand -t 'WizardStorage Component'",
     "storybook": "start-storybook -p 9001 -c private/storybook",
     "lint": "eslint src private webpack.config.js --ext js,jsx",
     "build:clean": "rimraf \"dist/!(.git*|Procfile)**\"",
     "build:copy": "copyfiles -u 1 public/* public/**/* dist",
     "prebuild": "npm run build:clean && npm run build:copy",
     "build": "npm run env:prod -- webpack",
-    "postinstall": "npm run flow-typed",
     "flow": "flow",
     "flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true"
   },
@@ -39,8 +38,8 @@
     "babel-eslint": "^8.0.1",
     "babel-jest": "^21.2.0",
     "cypress": "3.0.3",
-    "enzyme": "^3.1.0",
-    "enzyme-adapter-react-16": "^1.0.4",
+    "enzyme": "^3.9.0",
+    "enzyme-adapter-react-16": "^1.11.2",
     "eslint": "^4.8.0",
     "eslint-config-airbnb": "^15.1.0",
     "eslint-plugin-cypress": "^2.0.1",

+ 6 - 2
src/components/atoms/SmallLoading/SmallLoading.jsx

@@ -49,7 +49,9 @@ const CircleProgressBar = styled.circle`
   transition: stroke-dashoffset ${StyleProps.animations.swift};
 `
 
-type Props = {
+export const TEST_ID = 'smallLoading'
+
+export type Props = {
   loadingProgress: number,
 }
 
@@ -97,7 +99,9 @@ class SmallLoading extends React.Component<Props> {
     }
 
     return (
-      <ProgressText>{this.props.loadingProgress ? this.props.loadingProgress.toFixed(0) : 0}%</ProgressText>
+      <ProgressText
+        data-test-id={`${TEST_ID}-progressText`}
+      >{this.props.loadingProgress ? this.props.loadingProgress.toFixed(0) : 0}%</ProgressText>
     )
   }
 

+ 34 - 0
src/components/atoms/SmallLoading/test.jsx

@@ -0,0 +1,34 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import React from 'react'
+import { shallow } from 'enzyme'
+
+import TW from '../../../utils/TestWrapper'
+import Component, { TEST_ID } from '.'
+import type { Props } from '.'
+
+const defaultProps: Props = {
+  loadingProgress: 33,
+}
+const wrap = (props: Props) => new TW(shallow(<Component {...props} />), TEST_ID)
+
+describe('SmallLoading Component', () => {
+  it('renders', () => {
+    let wrapper = wrap(defaultProps)
+    expect(wrapper.findText('progressText')).toBe('33%')
+  })
+})

+ 10 - 6
src/components/molecules/ActionDropdown/ActionDropdown.jsx

@@ -51,6 +51,11 @@ const ListItem = styled.div`
     border-bottom-right-radius: ${StyleProps.borderRadius};
   }
 `
+const ListStyle = css`
+  box-shadow: 0 0 8px 0px rgba(111, 114, 118, 0.51);
+  border: none;
+`
+export const TEST_ID = 'actionDropdown'
 export type Action = {
   label: string,
   color?: string,
@@ -58,14 +63,10 @@ export type Action = {
   disabled?: boolean,
   hidden?: boolean,
 }
-const ListStyle = css`
-  box-shadow: 0 0 8px 0px rgba(111, 114, 118, 0.51);
-  border: none;
-`
-type Props = {
+export type Props = {
   label: string,
   actions: Action[],
-  style: any,
+  style?: any,
   'data-test-id'?: string,
 }
 
@@ -157,6 +158,7 @@ class ActionDropdown extends React.Component<Props, State> {
             onClick={() => { this.handleItemClick(action) }}
             color={action.color}
             disabled={action.disabled}
+            data-test-id={`${TEST_ID}-listItem-${action.label}`}
           >
             {action.label}
           </ListItem>
@@ -177,6 +179,7 @@ class ActionDropdown extends React.Component<Props, State> {
         width={`${StyleProps.inputSizes.regular.width}px`}
         padding={0}
         customStyle={ListStyle}
+        data-test-id={`${TEST_ID}-list`}
       >
         <Tip innerRef={ref => { this.tipRef = ref }} borderColor={'rgba(111, 114, 118, 0.2)'} />
         {this.renderListItems()}
@@ -193,6 +196,7 @@ class ActionDropdown extends React.Component<Props, State> {
           value={this.props.label}
           customRef={ref => { this.buttonRef = ref }}
           onClick={() => { this.handleButtonClick() }}
+          data-test-id={`${TEST_ID}-dropdownButton`}
         />
         {this.renderList()}
       </Wrapper>

+ 92 - 0
src/components/molecules/ActionDropdown/test.jsx

@@ -0,0 +1,92 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import React from 'react'
+import sinon from 'sinon'
+import { shallow, mount } from 'enzyme'
+
+import TW from '../../../utils/TestWrapper'
+import ActionDropdown, { TEST_ID } from '.'
+import type { Props, Action } from '.'
+
+const defaultActions: Action[] = [
+  { label: 'Action1', action: () => { } },
+  { label: 'Action2', action: () => { }, color: 'red' },
+  { label: 'Action3', action: () => { }, disabled: true },
+  { label: 'Action4', action: () => { }, hidden: true },
+  { label: 'Action5', action: () => { } },
+]
+const defaultProps: Props = {
+  label: 'Actions',
+  actions: defaultActions,
+}
+
+const wrap = (props: Props) => new TW(shallow(<ActionDropdown {...props} />), TEST_ID)
+const domWrap = (props: Props) => new TW(mount(<ActionDropdown {...props} />), TEST_ID)
+
+describe('ActionDropdown Component', () => {
+  it('renders the dropdown button with the correct label', () => {
+    let wrapper = wrap(defaultProps)
+    expect(wrapper.find('dropdownButton').prop('value')).toBe(defaultProps.label)
+  })
+
+  it('opens list on click', () => {
+    let wrapper = domWrap(defaultProps)
+    expect(wrapper.findDiv('list').length).toBe(0)
+    wrapper.findDiv('dropdownButton').simulate('click')
+    expect(wrapper.findDiv('list').length).toBe(1)
+  })
+
+  it('renders only visible actions labels', () => {
+    let wrapper = domWrap(defaultProps)
+    wrapper.findDiv('dropdownButton').simulate('click')
+    defaultActions.forEach(a => {
+      if (a.hidden) {
+        expect(wrapper.findDiv(`listItem-${a.label}`).length).toBe(0)
+      } else {
+        expect(wrapper.findDiv(`listItem-${a.label}`).length).toBe(1)
+        expect(wrapper.findDiv(`listItem-${a.label}`).text()).toBe(a.label)
+      }
+    })
+  })
+
+  it('renders correct props for all actions', () => {
+    let wrapper = domWrap(defaultProps)
+    wrapper.findDiv('dropdownButton').simulate('click')
+    defaultActions.filter(a => !a.hidden).forEach(a => {
+      expect(wrapper.findDiv(`listItem-${a.label}`).prop('color')).toBe(a.color)
+      expect(wrapper.findDiv(`listItem-${a.label}`).prop('disabled')).toBe(a.disabled)
+    })
+  })
+
+  it('dispaches correct actions on action click', () => {
+    let props: Props = { ...defaultProps }
+    let enabledAction = props.actions[1]
+    let disabledAction = props.actions[2]
+    enabledAction.action = sinon.spy()
+    disabledAction.action = sinon.spy()
+
+    let wrapper = domWrap(props)
+    wrapper.findDiv('dropdownButton').simulate('click')
+
+    let enabledActionWrapper = wrapper.findDiv(`listItem-${enabledAction.label}`)
+    let disabledActionWrapper = wrapper.findDiv(`listItem-${disabledAction.label}`)
+    enabledActionWrapper.simulate('click')
+    disabledActionWrapper.simulate('click')
+    expect(enabledAction.action.called).toBe(true)
+    expect(disabledAction.action.called).toBe(false)
+  })
+})

+ 1 - 1
src/components/molecules/LoginOptions/test.jsx

@@ -47,7 +47,7 @@ let buttons = [
 describe('LoginOptions Component', () => {
   it('renders with all buttons', () => {
     let wrapper = wrap({ buttons })
-    expect(wrapper.find('button', true).length).toBe(4)
+    expect(wrapper.findPartialId('button').length).toBe(4)
     buttons.forEach(button => {
       expect(wrapper.findText(`button-${button.id}`)).toBe(`<styled.div />Sign in with ${button.name}`)
       expect(wrapper.find(`logo-${button.id}`).prop('id')).toBe(button.id)

+ 7 - 6
src/components/molecules/MainDetailsTable/MainDetailsTable.jsx

@@ -133,8 +133,9 @@ const ArrowIcon = styled.div`
   background: url('${arrowIcon}') center no-repeat;
   margin-left: 16px;
 `
+export const TEST_ID = 'mainDetailsTable'
 
-type Props = {
+export type Props = {
   item: ?MainItem,
   instancesDetails: Instance[],
   networks?: Network[],
@@ -193,11 +194,11 @@ class MainDetailsTable extends React.Component<Props, State> {
         <RowHeader>
           <RowHeaderColumn>
             <HeaderIcon icon={icon} />
-            <HeaderName source>{sourceName}</HeaderName>
+            <HeaderName source data-test-id={`${TEST_ID}-source-${icon}`}>{sourceName}</HeaderName>
             {destinationName ? <ArrowIcon /> : null}
           </RowHeaderColumn>
           <RowHeaderColumn>
-            <HeaderName>{destinationName}</HeaderName>
+            <HeaderName data-test-id={`${TEST_ID}-destination-${icon}`}>{destinationName}</HeaderName>
           </RowHeaderColumn>
         </RowHeader>
         <Collapse isOpened={isOpened} springConfig={{ stiffness: 100, damping: 20 }}>
@@ -347,8 +348,8 @@ class MainDetailsTable extends React.Component<Props, State> {
     return this.renderRow(
       instance.instance_name,
       'instance',
-      `${instance.instance_name}`,
-      `${destinationName}`,
+      instance.instance_name,
+      destinationName,
       sourceBody,
       destinationBody
     )
@@ -367,7 +368,7 @@ class MainDetailsTable extends React.Component<Props, State> {
         </Header>
         {this.props.instancesDetails.map(instance => (
           <InstanceInfo key={instance.name}>
-            <InstanceName>{instance.name}</InstanceName>
+            <InstanceName data-test-id={`${TEST_ID}-instanceName-${instance.name}`}>{instance.name}</InstanceName>
             <InstanceBody>
               {this.renderInstanceDetails(instance)}
               {this.renderNetworks(instance)}

+ 108 - 0
src/components/molecules/MainDetailsTable/test.jsx

@@ -0,0 +1,108 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import React from 'react'
+import { shallow } from 'enzyme'
+
+import type { MainItem } from '../../../types/MainItem'
+import type { Instance } from '../../../types/Instance'
+import TW from '../../../utils/TestWrapper'
+import Component, { TEST_ID } from '.'
+import type { Props } from '.'
+
+const defaultInstance: Instance = {
+  id: 'instance1id',
+  name: 'instance1name',
+  flavor_name: 'instance1flavorname',
+  instance_name: 'instance1instancename',
+  num_cpu: 2,
+  memory_mb: 2048,
+  os_type: 'windows',
+  devices: {
+    nics: [{
+      id: 'instance1nic1',
+      network_name: 'network1',
+      mac_address: 'instance1macaddress',
+      network_id: 'network1',
+    }],
+    disks: [{
+      id: 'instance1disk1',
+      name: 'instance1disk1name',
+      storage_backend_identifier: 'asdaf',
+    }],
+  },
+}
+
+const defaultItem: MainItem = {
+  id: 'id',
+  executions: [],
+  name: 'name',
+  notes: 'notes',
+  status: 'COMPLETED',
+  tasks: [],
+  created_at: new Date(2019, 2, 18, 13, 19, 10),
+  updated_at: new Date(2019, 2, 19, 14, 18, 55),
+  origin_endpoint_id: 'origin',
+  destination_endpoint_id: 'destination',
+  instances: ['instance1'],
+  type: 'replica',
+  info: {
+    instance1: {
+      export_info: { devices: { nics: [{ network_name: 'network1' }, { network_name: 'network2' }] } },
+    },
+  },
+  destination_environment: { option1: 'value1' },
+  transfer_result: {
+    instance1: defaultInstance,
+  },
+  storage_mappings: {
+    backend_mappings: [{
+      destination: 'asdaf',
+      source: 'asdaf1',
+    }],
+    default: 'asdaf',
+    disk_mappings: [{
+      destination: 'asdaf',
+      disk_id: 'instance1disk1',
+    }],
+  },
+  network_map: {
+    network1: 'network2',
+  },
+}
+
+const defaultProps: Props = {
+  item: defaultItem,
+  instancesDetails: [defaultInstance],
+}
+const wrap = (props: Props) => new TW(shallow(<Component {...props} />), TEST_ID)
+
+describe('MainDetailsTable Component', () => {
+  it('renders basic info', () => {
+    let wrapper = wrap(defaultProps)
+    defaultProps.instancesDetails.forEach(i => {
+      expect(wrapper.findText(`instanceName-${i.name}`)).toBe(i.name)
+    })
+    expect(wrapper.findText('source-instance')).toBe('instance1')
+    expect(wrapper.findText('destination-instance')).toBe('instance1')
+
+    expect(wrapper.findText('source-network')).toBe('network1')
+    expect(wrapper.findText('destination-network')).toBe('network2')
+
+    expect(wrapper.findText('source-storage')).toBe('instance1disk1')
+    expect(wrapper.findText('destination-storage')).toBe('instance1disk1name')
+  })
+})

+ 1 - 1
src/components/molecules/MainListFilter/test.jsx

@@ -41,7 +41,7 @@ let selectionInfo = { selected: 2, total: 7, label: 'items' }
 describe('MainListFilter Component', () => {
   it('renders given items', () => {
     let wrapper = wrap({ items, actions, selectionInfo })
-    expect(wrapper.find('filterItem', true).length).toBe(items.length)
+    expect(wrapper.findPartialId('filterItem').length).toBe(items.length)
     items.forEach(item => {
       expect(wrapper.findText(`filterItem-${item.value}`)).toBe(item.label)
     })

+ 1 - 1
src/components/molecules/MainListItem/test.jsx

@@ -36,7 +36,7 @@ let endpointType = id => id
 describe('MainListItem Component', () => {
   it('renders with given status', () => {
     let wrapper = wrap({ item, endpointType })
-    expect(wrapper.find('statusPill', true).at(0).prop('status')).toBe('COMPLETED')
+    expect(wrapper.findPartialId('statusPill').at(0).prop('status')).toBe('COMPLETED')
   })
 
   it('renders with given endpoints', () => {

+ 3 - 1
src/components/molecules/NavigationMini/NavigationMini.jsx

@@ -57,6 +57,8 @@ const NavigationStyled = styled(Navigation)`
   transition: left ${StyleProps.animations.swift};
 `
 
+export const TEST_ID = 'navigationMini'
+
 type State = {
   open: boolean,
 }
@@ -77,7 +79,7 @@ class NavigationMini extends React.Component<{}, State> {
           open={this.state.open}
           onClick={() => { this.handleMenuToggleClick() }}
           dangerouslySetInnerHTML={{ __html: menuImage() }}
-          data-test-id="sideMenu-toggle"
+          data-test-id={`${TEST_ID}-toggleButton`}
         />
         {this.state.open ? <Stub /> : null}
         <NavigationStyled

+ 35 - 0
src/components/molecules/NavigationMini/test.jsx

@@ -0,0 +1,35 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import React from 'react'
+import { shallow } from 'enzyme'
+
+import TW from '../../../utils/TestWrapper'
+import NavigationMini, { TEST_ID } from '.'
+
+const wrap = () => new TW(shallow(
+  <NavigationMini />
+), TEST_ID)
+
+describe('NavigationMini Component', () => {
+  it('toggles the navigation state', () => {
+    let wrapper = wrap()
+    let button = () => wrapper.find('toggleButton')
+    expect(button().prop('open')).toBe(false)
+    button().simulate('click')
+    expect(button().prop('open')).toBe(true)
+  })
+})

+ 2 - 2
src/components/molecules/NewItemDropdown/test.jsx

@@ -25,9 +25,9 @@ const wrap = props => new TW(shallow(<NewItemDropdown onChange={() => { }} {...p
 describe('NewItemDropdown Component', () => {
   it('opens list on click', () => {
     let wrapper = wrap()
-    expect(wrapper.find('listItem', true).length).toBe(0)
+    expect(wrapper.findPartialId('listItem').length).toBe(0)
     wrapper.find('button').simulate('click')
-    expect(wrapper.find('listItem', true).length).toBe(3)
+    expect(wrapper.findPartialId('listItem').length).toBe(3)
   })
 
   it('dispatches change on item click with correct args', () => {

+ 5 - 2
src/components/molecules/Panel/Panel.jsx

@@ -55,7 +55,7 @@ export type NavigationItem = {
   value: string,
 }
 
-type Props = {
+export type Props = {
   navigationItems: NavigationItem[],
   content: React.Node,
   selectedValue: string,
@@ -63,6 +63,8 @@ type Props = {
   style?: any,
 }
 
+export const TEST_ID = 'panel'
+
 @observer
 class Panel extends React.Component<Props> {
   handleItemClick(item: NavigationItem) {
@@ -79,9 +81,10 @@ class Panel extends React.Component<Props> {
             key={item.value}
             selected={this.props.selectedValue === item.value}
             onClick={() => { this.handleItemClick(item) }}
+            data-test-id={`${TEST_ID}-navItem-${item.value}`}
           >{item.label}</NavigationItemDiv>
         ))}</Navigation>
-        <Content>{this.props.content}</Content>
+        <Content data-test-id={`${TEST_ID}-content`}>{this.props.content}</Content>
       </Wrapper>
     )
   }

+ 61 - 0
src/components/molecules/Panel/test.jsx

@@ -0,0 +1,61 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import React from 'react'
+import { shallow } from 'enzyme'
+import sinon from 'sinon'
+
+import TW from '../../../utils/TestWrapper'
+import Panel, { TEST_ID } from '.'
+import type { Props, NavigationItem } from '.'
+
+const navigationItems: NavigationItem[] = [
+  { label: 'Navigation1', value: 'navigation1' },
+  { label: 'Navigation2', value: 'navigation2' },
+]
+
+const content = 'Content'
+
+const wrap = (props: Props) => new TW(shallow(
+  <Panel {...props} />
+), TEST_ID)
+
+describe('Panel Component', () => {
+  it('renders navigation items', () => {
+    let wrapper = wrap({ navigationItems, content, onChange: () => { }, selectedValue: 'navigation2' })
+    navigationItems.forEach(i => {
+      expect(wrapper.findText(`navItem-${i.value}`)).toBe(i.label)
+    })
+  })
+
+  it('selects the selected value', () => {
+    let wrapper = wrap({ navigationItems, content, onChange: () => { }, selectedValue: 'navigation2' })
+    expect(wrapper.find('navItem-navigation1').prop('selected')).toBeFalsy()
+    expect(wrapper.find('navItem-navigation2').prop('selected')).toBe(true)
+  })
+
+  it('dispatches onChange', () => {
+    let onChange = sinon.spy()
+    let wrapper = wrap({ navigationItems, content, onChange, selectedValue: 'navigation2' })
+    wrapper.find('navItem-navigation1').simulate('click')
+    expect(onChange.called).toBe(true)
+  })
+
+  it('renders content', () => {
+    let wrapper = wrap({ navigationItems, content, onChange: () => { }, selectedValue: 'navigation2' })
+    expect(wrapper.findText('content')).toBe(content)
+  })
+})

+ 2 - 2
src/components/molecules/PropertiesTable/test.jsx

@@ -37,8 +37,8 @@ const valueCallback = prop => {
 describe('PropertiesTable Component', () => {
   it('renders all properties', () => {
     const wrapper = wrap({ properties, valueCallback })
-    expect(wrapper.find('row-', true).length).toBe(properties.length)
-    expect(wrapper.find(`row-${properties[3].name}`).findText('header')).toBe('Prop 3a')
+    expect(wrapper.findPartialId('row-').length).toBe(properties.length)
+    expect(wrapper.findPartialId(`row-${properties[3].name}`).findText('header')).toBe('Prop 3a')
   })
 
   it('renders boolean properties', () => {

+ 2 - 2
src/components/molecules/Table/test.jsx

@@ -35,7 +35,7 @@ describe('TTable Component', () => {
 
   it('renders header', () => {
     let wrapper = wrap({ items, header: headerItems })
-    expect(wrapper.find('header-', true).length).toBe(headerItems.length)
+    expect(wrapper.findPartialId('header-').length).toBe(headerItems.length)
     headerItems.forEach((headerItem, i) => {
       expect(wrapper.findText(`header-${i}`)).toBe(headerItem)
     })
@@ -43,6 +43,6 @@ describe('TTable Component', () => {
 
   it('renders header with calculated widths', () => {
     let wrapper = wrap({ items, header: headerItems })
-    expect(wrapper.find('header-', true).at(3).prop('width')).toBe('20%')
+    expect(wrapper.findPartialId('header-').at(3).prop('width')).toBe('20%')
   })
 })

+ 1 - 1
src/components/molecules/Timeline/test.jsx

@@ -35,7 +35,7 @@ let items = [
 describe('Timeline Component', () => {
   it('renders with correct dates', () => {
     let wrapper = wrap({ items, selectedItem: items[2] })
-    expect(wrapper.find('label-', true).length).toBe(items.length)
+    expect(wrapper.findPartialId('label-').length).toBe(items.length)
     items.forEach(item => {
       expect(wrapper.findText(`label-${item.id}`)).toBe(moment(item.created_at).format('DD MMM YYYY'))
     })

+ 2 - 3
src/components/organisms/DetailsContentHeader/test.jsx

@@ -16,7 +16,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import React from 'react'
 import { shallow } from 'enzyme'
-import sinon from 'sinon'
 import TW from '../../../utils/TestWrapper'
 import DetailsContentHeader from '.'
 
@@ -69,11 +68,11 @@ describe('DetailsContentHeader Component', () => {
 
   it('renders correct STATUS pill', () => {
     let wrapper = wrap({ item })
-    expect(wrapper.find('statusPill-', true).prop('status')).toBe('COMPLETED')
+    expect(wrapper.findPartialId('statusPill-').prop('status')).toBe('COMPLETED')
     let newItem = { ...item, executions: [...item.executions] }
     newItem.executions.push({ status: 'RUNNING', created_at: new Date() })
     wrapper = wrap({ item: newItem })
-    expect(wrapper.find('statusPill-', true).prop('status')).toBe('RUNNING')
+    expect(wrapper.findPartialId('statusPill-').prop('status')).toBe('RUNNING')
   })
 
   it('renders item description', () => {

+ 1 - 1
src/components/organisms/DropdownFilterGroup/test.jsx

@@ -38,7 +38,7 @@ const wrap = props => new TW(shallow(
 describe('DropdownFilterGroup Component', () => {
   it('renders correct dropdowns', () => {
     let wrapper = wrap()
-    expect(wrapper.find('dropdown-', true).length).toBe(dropdowns.length)
+    expect(wrapper.findPartialId('dropdown-').length).toBe(dropdowns.length)
     dropdowns.forEach(dropdown => {
       expect(wrapper.find(`dropdown-${dropdown.key}`).prop('items')[0].value).toBe(dropdown.items[0].value)
       expect(wrapper.find(`dropdown-${dropdown.key}`).prop('selectedItem')).toBe(dropdown.selectedItem || undefined)

+ 15 - 8
src/components/organisms/UserDetailsContent/UserDetailsContent.jsx

@@ -80,7 +80,9 @@ const ButtonsColumn = styled.div`
   }
 `
 
-type Props = {
+export const TEST_ID = 'userDetailsContent'
+
+export type Props = {
   user: ?User,
   loading: boolean,
   projects: Project[],
@@ -89,7 +91,7 @@ type Props = {
   onUpdatePasswordClick: () => void,
   onDeleteClick: () => void,
 }
-const testName = 'udContent'
+
 @observer
 class UserDetailsContent extends React.Component<Props> {
   renderLoading() {
@@ -110,7 +112,11 @@ class UserDetailsContent extends React.Component<Props> {
     return (
       <Buttons>
         <ButtonsColumn>
-          <Button hollow onClick={this.props.onUpdatePasswordClick}>Change password</Button>
+          <Button
+            hollow
+            onClick={this.props.onUpdatePasswordClick}
+            data-test-id={`${TEST_ID}-updateButton`}
+          >Change password</Button>
         </ButtonsColumn>
         <ButtonsColumn>
           <Button
@@ -118,6 +124,7 @@ class UserDetailsContent extends React.Component<Props> {
             hollow
             onClick={() => { this.props.onDeleteClick() }}
             disabled={this.props.isLoggedUser}
+            data-test-id={`${TEST_ID}-deleteUserButton`}
           >Delete user</Button>
         </ButtonsColumn>
       </Buttons>
@@ -128,7 +135,7 @@ class UserDetailsContent extends React.Component<Props> {
     return projects.map((project, i) => (
       <span key={project.id}>
         {project.label ? (
-          <LinkStyled data-test-id={`${testName}-project-${project.id}`} to={`/project/${project.id}`}>
+          <LinkStyled data-test-id={`${TEST_ID}-project-${project.id}`} to={`/project/${project.id}`}>
             {project.label}
           </LinkStyled>
         ) : project.id}
@@ -169,11 +176,11 @@ class UserDetailsContent extends React.Component<Props> {
         </Field>
         <Field>
           <Label>Email</Label>
-          {this.renderValue(user.email || '-')}
+          {this.renderValue(user.email || '-', 'email')}
         </Field>
         <Field>
           <Label>Primary Project</Label>
-          {this.renderValue(primaryProjectName || '-')}
+          {this.renderValue(primaryProjectName || '-', 'primaryProject')}
         </Field>
         <Field>
           <Label>Project Membership</Label>
@@ -181,7 +188,7 @@ class UserDetailsContent extends React.Component<Props> {
         </Field>
         <Field>
           <Label>Enabled</Label>
-          <Value>{user.enabled ? 'Yes' : 'No'}</Value>
+          <Value data-test-id={`${TEST_ID}-enabled`}>{user.enabled ? 'Yes' : 'No'}</Value>
         </Field>
       </Info>
     )
@@ -190,7 +197,7 @@ class UserDetailsContent extends React.Component<Props> {
   renderValue(value: string, dataTestId?: string) {
     return value !== '-' ? (
       <CopyValue
-        data-test-id={`${testName}-${dataTestId || ''}`}
+        data-test-id={`${TEST_ID}-${dataTestId || ''}`}
         value={value}
         maxWidth="90%"
       />

+ 100 - 0
src/components/organisms/UserDetailsContent/test.jsx

@@ -0,0 +1,100 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import React from 'react'
+import { shallow } from 'enzyme'
+import sinon from 'sinon'
+
+import type { User } from '../../../types/User'
+import type { Project } from '../../../types/Project'
+import TW from '../../../utils/TestWrapper'
+import Component, { TEST_ID } from '.'
+import type { Props } from '.'
+
+const anotherProject: Project = {
+  id: 'project2-id',
+  name: 'project2-name',
+  enabled: false,
+  description: 'project2-description',
+}
+
+const defaultProject: Project = {
+  id: 'project-id',
+  name: 'project-name',
+  enabled: true,
+  description: 'project-description',
+}
+
+const defaultUser: User = {
+  project: defaultProject,
+  email: 'user@email.com',
+  name: 'name',
+  id: 'id',
+  description: 'description',
+  enabled: true,
+  project_id: 'project-id',
+  domain_id: 'default',
+  isAdmin: true,
+  password: 'password',
+}
+const defaultProps: Props = {
+  user: defaultUser,
+  loading: false,
+  projects: [defaultProject, anotherProject],
+  userProjects: [defaultProject, anotherProject],
+  isLoggedUser: true,
+  onDeleteClick: () => { },
+  onUpdatePasswordClick: () => { },
+}
+const wrap = (props: Props) => new TW(shallow(<Component {...props} />), TEST_ID)
+
+describe('UserDetailsContent Component', () => {
+  it('renders info', () => {
+    let wrapper = wrap(defaultProps)
+    expect(wrapper.find('name').prop('value')).toBe(defaultUser.name)
+    expect(wrapper.find('id').prop('value')).toBe(defaultUser.id)
+    expect(wrapper.find('email').prop('value')).toBe(defaultUser.email)
+    expect(wrapper.find('primaryProject').prop('value')).toBe(defaultProject.name)
+    expect(wrapper.findText('enabled')).toBe('Yes')
+  })
+
+  it('renders project membership', () => {
+    let wrapper = wrap(defaultProps)
+    expect(wrapper.find('project-project-id').prop('to')).toBe('/project/project-id')
+    expect(wrapper.find('project-project2-id').prop('to')).toBe('/project/project2-id')
+  })
+
+  it('dispatches delete an update clicks', () => {
+    let newProps: Props = { ...defaultProps }
+    newProps.onDeleteClick = sinon.spy()
+    newProps.onUpdatePasswordClick = sinon.spy()
+    let wrapper = wrap(newProps)
+    let deleteButton = wrapper.find('deleteUserButton')
+    deleteButton.simulate('click')
+    let updateButton = wrapper.find('updateButton')
+    updateButton.simulate('click')
+
+    expect(newProps.onDeleteClick.called).toBe(true)
+    expect(newProps.onUpdatePasswordClick.called).toBe(true)
+  })
+
+  it('has delete disabled if is the logged in user', () => {
+    let wrapper = wrap(defaultProps)
+    expect(wrapper.find('deleteUserButton').prop('disabled')).toBe(true)
+    wrapper = wrap({ ...defaultProps, isLoggedUser: false })
+    expect(wrapper.find('deleteUserButton').prop('disabled')).toBe(false)
+  })
+})

+ 1 - 1
src/components/organisms/WizardEndpointList/test.jsx

@@ -35,7 +35,7 @@ let endpoints = [
 describe('WizardEndpointList Component', () => {
   it('renders correct number of providers', () => {
     let wrapper = wrap({ endpoints, providers })
-    expect(wrapper.find('logo-', true).length).toBe(providers.length)
+    expect(wrapper.findPartialId('logo-').length).toBe(providers.length)
   })
 
   it('renders correct providers type', () => {

+ 1 - 1
src/components/organisms/WizardInstances/test.jsx

@@ -33,7 +33,7 @@ let instances = [
 describe('WizardInstances Component', () => {
   it('has correct number of instances', () => {
     let wrapper = wrap({ instances, currentPage: 1 })
-    expect(wrapper.find('item-', true).length).toBe(instances.length)
+    expect(wrapper.findPartialId('item-').length).toBe(instances.length)
   })
 
   it('has correct instances info', () => {

+ 1 - 1
src/components/organisms/WizardNetworks/test.jsx

@@ -54,7 +54,7 @@ let selectedNetworks = [
 describe('WizardNetworks Component', () => {
   it('renders correct number of instance details', () => {
     let wrapper = wrap({ networks, instancesDetails })
-    expect(wrapper.find('dropdown-', true).length).toBe(instancesDetails.length)
+    expect(wrapper.findPartialId('dropdown-').length).toBe(instancesDetails.length)
   })
 
   it('renders correct info for instance details', () => {

+ 2 - 2
src/components/organisms/WizardOptions/test.jsx

@@ -58,7 +58,7 @@ let fields = [
 describe('WizardOptions Component', () => {
   it('has description and required field in simple tab', () => {
     let wrapper = wrap({ fields, selectedInstances: [], wizardType: 'migration' })
-    expect(wrapper.find('field-', true).length).toBe(3)
+    expect(wrapper.findPartialId('field-').length).toBe(3)
     expect(wrapper.find('field-description').length).toBe(1)
     expect(wrapper.find('field-required_string_field').length).toBe(1)
   })
@@ -81,7 +81,7 @@ describe('WizardOptions Component', () => {
 
   it('renders correct number of fields in advanced tab', () => {
     let wrapper = wrap({ fields, selectedInstances: [], useAdvancedOptions: true, wizardType: 'migration' })
-    expect(wrapper.find('field-', true).length).toBe(fields.length + 2)
+    expect(wrapper.findPartialId('field-').length).toBe(fields.length + 2)
   })
 
   it('renders correct field info', () => {

+ 9 - 4
src/components/organisms/WizardStorage/WizardStorage.jsx

@@ -136,7 +136,9 @@ export const getDisks = (instancesDetails: Instance[], type: 'backend' | 'disk')
   return disks
 }
 
-type Props = {
+export const TEST_ID = 'wizardStorage'
+
+export type Props = {
   storageBackends: StorageBackend[],
   instancesDetails: Instance[],
   storageMap: ?StorageMap[],
@@ -147,7 +149,7 @@ type Props = {
 class WizardStorage extends React.Component<Props> {
   renderNoStorage() {
     return (
-      <NoStorageMessage>
+      <NoStorageMessage data-test-id={`${TEST_ID}-noStorage`}>
         <BigStorageImage />
         <NoStorageTitle>No storage backends were found</NoStorageTitle>
         <NoStorageSubtitle>We could not find any storage backends. Coriolis will skip this step.</NoStorageSubtitle>
@@ -187,8 +189,10 @@ class WizardStorage extends React.Component<Props> {
               <StorageItem key={disk[diskFieldName]}>
                 <StorageImage backend={type === 'backend'} />
                 <StorageTitle>
-                  <StorageName>{disk[diskFieldName]}</StorageName>
-                  <StorageSubtitle>{`Connected to ${connectedTo.join(', ')}`}</StorageSubtitle>
+                  <StorageName data-test-id={`${TEST_ID}-${type}-source`}>{disk[diskFieldName]}</StorageName>
+                  <StorageSubtitle
+                    data-test-id={`${TEST_ID}-${type}-connectedTo`}
+                  >{`Connected to ${connectedTo.join(', ')}`}</StorageSubtitle>
                 </StorageTitle>
                 <ArrowImage />
                 <Dropdown
@@ -201,6 +205,7 @@ class WizardStorage extends React.Component<Props> {
                   labelField="name"
                   valueField="id"
                   onChange={(item: StorageBackend) => { this.props.onChange(disk, item, type) }}
+                  data-test-id={`${TEST_ID}-${type}-destination`}
                 />
               </StorageItem>
             )

+ 120 - 0
src/components/organisms/WizardStorage/test.jsx

@@ -0,0 +1,120 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import React from 'react'
+import { shallow } from 'enzyme'
+import sinon from 'sinon'
+
+import type { Instance } from '../../../types/Instance'
+import TW from '../../../utils/TestWrapper'
+import Component, { TEST_ID } from '.'
+import type { Props } from '.'
+
+const defaultInstance: Instance = {
+  id: 'instance1id',
+  name: 'instance1name',
+  flavor_name: 'instance1flavorname',
+  instance_name: 'instance1instancename',
+  num_cpu: 2,
+  memory_mb: 2048,
+  os_type: 'windows',
+  devices: {
+    nics: [{
+      id: 'instance1nic1',
+      network_name: 'network1',
+      mac_address: 'instance1macaddress',
+      network_id: 'network1',
+    }],
+    disks: [{
+      id: 'disk1-id',
+      name: 'disk1-name',
+      storage_backend_identifier: 'sback2',
+    }],
+  },
+}
+
+const defaultProps: Props = {
+  storageBackends: [{
+    id: 'sback1',
+    name: 'sback1-name',
+  }, {
+    id: 'sback2',
+    name: 'sback2-name',
+  }],
+  instancesDetails: [defaultInstance],
+  storageMap: [{
+    type: 'disk',
+    source: { id: 'disk1-id' },
+    target: { id: 'sback2', name: 'sback2-name' },
+  }, {
+    type: 'backend',
+    source: { id: 'sback2', storage_backend_identifier: 'sback2' },
+    target: { id: 'sback1', name: 'sback1-name' },
+  }],
+  defaultStorage: 'sback1',
+  onChange: () => { },
+}
+const wrap = (props: Props) => new TW(shallow(<Component {...props} />), TEST_ID)
+
+describe('WizardStorage Component', () => {
+  it('renders backend mapping', () => {
+    let wrapper = wrap(defaultProps)
+
+    expect(wrapper.findText('backend-source')).toBe('sback2')
+    expect(wrapper.findText('backend-connectedTo')).toBe('Connected to instance1instancename')
+
+    expect(wrapper.find('backend-destination').prop('selectedItem').id).toBe('sback1')
+
+    expect(wrapper.find('backend-destination').prop('items')[0].id).toBe(null)
+    expect(wrapper.find('backend-destination').prop('items')[0].name).toBe('Default')
+    expect(wrapper.find('backend-destination').prop('items')[1].id).toBe('sback1')
+    expect(wrapper.find('backend-destination').prop('items')[2].id).toBe('sback2')
+  })
+
+  it('renders disk mapping', () => {
+    let wrapper = wrap(defaultProps)
+    expect(wrapper.findText('disk-source')).toBe('disk1-id')
+    expect(wrapper.findText('disk-connectedTo')).toBe('Connected to instance1instancename')
+
+    expect(wrapper.find('disk-destination').prop('selectedItem').id).toBe('sback2')
+
+    expect(wrapper.find('disk-destination').prop('items')[0].id).toBe(null)
+    expect(wrapper.find('disk-destination').prop('items')[0].name).toBe('Default')
+    expect(wrapper.find('disk-destination').prop('items')[1].id).toBe('sback1')
+    expect(wrapper.find('disk-destination').prop('items')[2].id).toBe('sback2')
+  })
+
+  it('renders no storage message', () => {
+    let newProps: Props = { ...defaultProps }
+    newProps.storageBackends = []
+    let wrapper = wrap(newProps)
+    expect(wrapper.find('noStorage').length).toBe(1)
+    wrapper = wrap(defaultProps)
+    expect(wrapper.find('noStorage').length).toBe(0)
+  })
+
+  it('dispatches change', () => {
+    let newProps: Props = { ...defaultProps, onChange: sinon.spy() }
+    let wrapper = wrap(newProps)
+    wrapper.find('disk-destination').simulate('change', { id: 'sback2', name: 'sback2-name' })
+
+    let arg = newProps.onChange.args[0]
+
+    expect(arg[0].id).toBe('disk1-id')
+    expect(arg[1].id).toBe('sback2')
+    expect(arg[2]).toBe('disk')
+  })
+})

+ 1 - 1
src/types/MainItem.js

@@ -67,6 +67,6 @@ export type MainItem = {
     [string]: {
       source_network: string,
       destination_network: string,
-    } | 'string'
+    } | string
   }
 }

+ 0 - 1
src/types/Project.js

@@ -19,7 +19,6 @@ export type Project = {
   name: string,
   enabled?: boolean,
   description?: string,
-  enabled?: boolean,
 }
 
 export type Role = {

+ 22 - 5
src/utils/TestWrapper.js

@@ -14,14 +14,14 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 // @flow
 
-import type { ShallowWrapper } from 'enzyme'
+import type { ShallowWrapper, ReactWrapper } from 'enzyme'
 
 export default class TestWrapper {
-  shallow: ShallowWrapper<any>
+  shallow: ShallowWrapper<any> | ReactWrapper<any>
   baseId: ?string
   length: number
 
-  constructor(wrapper: ShallowWrapper<any>, baseId?: ?string) {
+  constructor(wrapper: ShallowWrapper<any> | ReactWrapper<any>, baseId?: ?string) {
     this.shallow = wrapper
     this.baseId = baseId
   }
@@ -46,15 +46,32 @@ export default class TestWrapper {
     return render ? this.shallow.render().text() : this.shallow.text()
   }
 
-  find(id: string, isPartialId?: boolean) {
+  find(id: string, opts?: {
+    isPartialId?: boolean,
+    name?: string,
+  }) {
     const actualId = this.baseId ? `${this.baseId}-${id}` : id
+    let o = opts || {}
     let tw = new TestWrapper(this.shallow.findWhere(w =>
-      isPartialId ? w.prop('data-test-id') && w.prop('data-test-id').indexOf(actualId) > -1 : w.prop('data-test-id') === actualId
+      (
+        o.isPartialId ? w.prop('data-test-id') && w.prop('data-test-id').indexOf(actualId) > -1
+          : w.prop('data-test-id') === actualId
+      )
+      &&
+      (o.name ? w.name() === o.name : true)
     ), this.baseId)
     tw.length = tw.shallow.length
     return tw
   }
 
+  findPartialId(partialId: string) {
+    return this.find(partialId, { isPartialId: true })
+  }
+
+  findDiv(id: string) {
+    return this.find(id, { name: 'div' })
+  }
+
   findText(id: string, isHostComponent?: boolean, render?: boolean) {
     const wrapper = this.find(id).shallow
     const text = (component: any) => render ? component.render().text() : component.text()

+ 322 - 76
yarn.lock

@@ -226,10 +226,6 @@
   version "8.5.1"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-8.5.1.tgz#4ec3020bcdfe2abffeef9ba3fbf26fca097514b5"
 
-"@types/node@^6.0.46":
-  version "6.0.88"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.88.tgz#f618f11a944f6a18d92b5c472028728a3e3d4b66"
-
 "@types/react@^16.0.18":
   version "16.0.22"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.22.tgz#19ad106e124aceebd2b4d430a278d55413ee8759"
@@ -512,6 +508,11 @@ array-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
 
+array-filter@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83"
+  integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=
+
 array-find@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8"
@@ -541,6 +542,15 @@ array-unique@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
 
+array.prototype.flat@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz#812db8f02cad24d3fab65dd67eabe3b8903494a4"
+  integrity sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==
+  dependencies:
+    define-properties "^1.1.2"
+    es-abstract "^1.10.0"
+    function-bind "^1.1.1"
+
 arrify@^1.0.0, arrify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
@@ -2147,6 +2157,7 @@ check-more-types@2.24.0:
 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"
+  integrity sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=
   dependencies:
     css-select "~1.2.0"
     dom-serializer "~0.1.0"
@@ -2304,10 +2315,6 @@ colormin@^1.0.5:
     css-color-names "0.0.4"
     has "^1.0.1"
 
-colors@0.5.x:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774"
-
 colors@^1.1.2:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794"
@@ -2330,6 +2337,11 @@ commander@2.12.x, commander@~2.12.1:
   version "2.12.2"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
 
+commander@^2.19.0:
+  version "2.19.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
+  integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
+
 common-tags@1.4.0, common-tags@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.4.0.tgz#1187be4f3d4cf0c0427d43f74eef1f73501614c0"
@@ -2808,6 +2820,13 @@ define-properties@^1.1.2:
     foreach "^2.0.5"
     object-keys "^1.0.8"
 
+define-properties@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
+  integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
+  dependencies:
+    object-keys "^1.0.12"
+
 defined@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
@@ -2872,6 +2891,7 @@ diffie-hellman@^5.0.0:
 discontinuous-range@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a"
+  integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=
 
 doctrine@1.5.0:
   version "1.5.0"
@@ -2903,13 +2923,21 @@ dom-helpers@^3.2.0:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a"
 
-dom-serializer@0, dom-serializer@~0.1.0:
+dom-serializer@0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
   dependencies:
     domelementtype "~1.1.1"
     entities "~1.1.1"
 
+dom-serializer@~0.1.0:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
+  integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==
+  dependencies:
+    domelementtype "^1.3.0"
+    entities "^1.1.1"
+
 dom-walk@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
@@ -2918,10 +2946,15 @@ domain-browser@^1.1.1:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
 
-domelementtype@1, domelementtype@^1.3.0:
+domelementtype@1:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
 
+domelementtype@^1.3.0, domelementtype@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
+  integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
+
 domelementtype@~1.1.1:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
@@ -2933,8 +2966,9 @@ domhandler@2.1:
     domelementtype "1"
 
 domhandler@^2.3.0:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259"
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
+  integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
   dependencies:
     domelementtype "1"
 
@@ -2952,8 +2986,9 @@ domutils@1.5.1:
     domelementtype "1"
 
 domutils@^1.5.1:
-  version "1.6.2"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff"
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
+  integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
   dependencies:
     dom-serializer "0"
     domelementtype "1"
@@ -3046,43 +3081,65 @@ enhanced-resolve@^3.3.0, enhanced-resolve@^3.4.0:
     object-assign "^4.0.1"
     tapable "^0.2.7"
 
-entities@^1.1.1, entities@~1.1.1:
+entities@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
+  integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
+
+entities@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
 
-enzyme-adapter-react-16@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.4.tgz#67f898cc053452f5c786424e395fe0c63a0607fe"
-  dependencies:
-    enzyme-adapter-utils "^1.1.0"
-    lodash "^4.17.4"
-    object.assign "^4.0.4"
-    object.values "^1.0.4"
-    prop-types "^15.5.10"
+enzyme-adapter-react-16@^1.11.2:
+  version "1.11.2"
+  resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.11.2.tgz#8efeafb27e96873a5492fdef3f423693182eb9d4"
+  integrity sha512-2ruTTCPRb0lPuw/vKTXGVZVBZqh83MNDnakMhzxhpJcIbneEwNy2Cv0KvL97pl57/GOazJHflWNLjwWhex5AAA==
+  dependencies:
+    enzyme-adapter-utils "^1.10.1"
+    object.assign "^4.1.0"
+    object.values "^1.1.0"
+    prop-types "^15.7.2"
+    react-is "^16.8.4"
     react-test-renderer "^16.0.0-0"
+    semver "^5.6.0"
 
-enzyme-adapter-utils@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.1.1.tgz#689de8853f0751710590d6dfa730ff4056ea36b2"
+enzyme-adapter-utils@^1.10.1:
+  version "1.10.1"
+  resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.10.1.tgz#58264efa19a7befdbf964fb7981a108a5452ac96"
+  integrity sha512-oasinhhLoBuZsIkTe8mx0HiudtfErUtG0Ooe1FOplu/t4c9rOmyG5gtrBASK6u4whHIRWvv0cbZMElzNTR21SA==
   dependencies:
-    lodash "^4.17.4"
-    object.assign "^4.0.4"
-    prop-types "^15.5.10"
+    function.prototype.name "^1.1.0"
+    object.assign "^4.1.0"
+    object.fromentries "^2.0.0"
+    prop-types "^15.7.2"
+    semver "^5.6.0"
 
-enzyme@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.1.0.tgz#d8ca84085790fbcec6ed40badd14478faee4c25a"
+enzyme@^3.9.0:
+  version "3.9.0"
+  resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.9.0.tgz#2b491f06ca966eb56b6510068c7894a7e0be3909"
+  integrity sha512-JqxI2BRFHbmiP7/UFqvsjxTirWoM1HfeaJrmVSZ9a1EADKkZgdPcAuISPMpoUiHlac9J4dYt81MC5BBIrbJGMg==
   dependencies:
+    array.prototype.flat "^1.2.1"
     cheerio "^1.0.0-rc.2"
-    function.prototype.name "^1.0.3"
+    function.prototype.name "^1.1.0"
+    has "^1.0.3"
+    html-element-map "^1.0.0"
+    is-boolean-object "^1.0.0"
+    is-callable "^1.1.4"
+    is-number-object "^1.0.3"
+    is-regex "^1.0.4"
+    is-string "^1.0.4"
     is-subset "^0.1.1"
-    lodash "^4.17.4"
+    lodash.escape "^4.0.1"
+    lodash.isequal "^4.5.0"
+    object-inspect "^1.6.0"
     object-is "^1.0.1"
-    object.assign "^4.0.4"
+    object.assign "^4.1.0"
     object.entries "^1.0.4"
     object.values "^1.0.4"
-    raf "^3.3.2"
-    rst-selector-parser "^2.2.2"
+    raf "^3.4.0"
+    rst-selector-parser "^2.2.3"
+    string.prototype.trim "^1.1.2"
 
 errno@^0.1.3, errno@^0.1.4:
   version "0.1.4"
@@ -3102,6 +3159,18 @@ error-stack-parser@^1.3.6:
   dependencies:
     stackframe "^0.3.1"
 
+es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.5.0:
+  version "1.13.0"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
+  integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==
+  dependencies:
+    es-to-primitive "^1.2.0"
+    function-bind "^1.1.1"
+    has "^1.0.3"
+    is-callable "^1.1.4"
+    is-regex "^1.0.4"
+    object-keys "^1.0.12"
+
 es-abstract@^1.4.3, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0, es-abstract@^1.9.0:
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.9.0.tgz#690829a07cae36b222e7fd9b75c0d0573eb25227"
@@ -3120,6 +3189,15 @@ es-to-primitive@^1.1.1:
     is-date-object "^1.0.1"
     is-symbol "^1.0.1"
 
+es-to-primitive@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
+  integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==
+  dependencies:
+    is-callable "^1.1.4"
+    is-date-object "^1.0.1"
+    is-symbol "^1.0.2"
+
 es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14:
   version "0.10.30"
   resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.30.tgz#7141a16836697dbabfaaaeee41495ce29f52c939"
@@ -3868,6 +3946,15 @@ function.prototype.name@^1.0.3:
     function-bind "^1.1.0"
     is-callable "^1.1.3"
 
+function.prototype.name@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327"
+  integrity sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==
+  dependencies:
+    define-properties "^1.1.2"
+    function-bind "^1.1.1"
+    is-callable "^1.1.3"
+
 functional-red-black-tree@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
@@ -4082,6 +4169,11 @@ has-symbol-support-x@^1.4.1:
   version "1.4.2"
   resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455"
 
+has-symbols@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
+  integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=
+
 has-to-string-tag-x@^1.2.0:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d"
@@ -4203,6 +4295,13 @@ html-element-attributes@^1.0.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/html-element-attributes/-/html-element-attributes-1.3.0.tgz#f06ebdfce22de979db82020265cac541fb17d4fc"
 
+html-element-map@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/html-element-map/-/html-element-map-1.0.0.tgz#19a41940225153ecdfead74f8509154ff1cdc18b"
+  integrity sha512-/SP6aOiM5Ai9zALvCxDubIeez0LvG3qP7R9GcRDnJEP/HBmv0A8A9K0o8+HFudcFt46+i921ANjzKsjPjb7Enw==
+  dependencies:
+    array-filter "^1.0.0"
+
 html-encoding-sniffer@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
@@ -4242,15 +4341,16 @@ html-webpack-plugin@^2.30.1:
     toposort "^1.0.0"
 
 htmlparser2@^3.9.1:
-  version "3.9.2"
-  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
+  version "3.10.1"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
+  integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
   dependencies:
-    domelementtype "^1.3.0"
+    domelementtype "^1.3.1"
     domhandler "^2.3.0"
     domutils "^1.5.1"
     entities "^1.1.1"
     inherits "^2.0.1"
-    readable-stream "^2.0.2"
+    readable-stream "^3.1.1"
 
 htmlparser2@~3.3.0:
   version "3.3.0"
@@ -4444,6 +4544,11 @@ is-binary-path@^1.0.0:
   dependencies:
     binary-extensions "^1.0.0"
 
+is-boolean-object@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93"
+  integrity sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=
+
 is-buffer@^1.1.5:
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
@@ -4462,6 +4567,11 @@ 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-callable@^1.1.4:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
+  integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==
+
 is-ci@1.0.10, is-ci@^1.0.10:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e"
@@ -4531,6 +4641,11 @@ is-installed-globally@0.1.0:
     global-dirs "^0.1.0"
     is-path-inside "^1.0.0"
 
+is-number-object@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz#f265ab89a9f445034ef6aff15a8f00b00f551799"
+  integrity sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=
+
 is-number@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
@@ -4609,9 +4724,15 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
 
+is-string@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64"
+  integrity sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=
+
 is-subset@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6"
+  integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=
 
 is-svg@^2.0.0:
   version "2.1.0"
@@ -4623,6 +4744,13 @@ is-symbol@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
 
+is-symbol@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38"
+  integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==
+  dependencies:
+    has-symbols "^1.0.0"
+
 is-typedarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -4987,6 +5115,11 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
 
+"js-tokens@^3.0.0 || ^4.0.0":
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
 js-yaml@^3.4.3, js-yaml@^3.7.0, js-yaml@^3.9.1:
   version "3.10.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
@@ -5318,6 +5451,11 @@ lodash.deburr@^3.0.0:
   dependencies:
     lodash._root "^3.0.0"
 
+lodash.escape@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
+  integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=
+
 lodash.flattendeep@^4.4.0:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
@@ -5334,6 +5472,11 @@ lodash.isarray@^3.0.0:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
 
+lodash.isequal@^4.5.0:
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+  integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
+
 lodash.isplainobject@^4.0.6:
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
@@ -5425,6 +5568,13 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3
   dependencies:
     js-tokens "^3.0.0"
 
+loose-envify@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+  integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+  dependencies:
+    js-tokens "^3.0.0 || ^4.0.0"
+
 lower-case@^1.1.1:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
@@ -5649,6 +5799,11 @@ moment@^2.18.1, moment@~2.18.1:
   version "2.18.1"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
 
+moo@^0.4.3:
+  version "0.4.3"
+  resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e"
+  integrity sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==
+
 ms-rest-azure@^2.4.5:
   version "2.4.5"
   resolved "https://registry.yarnpkg.com/ms-rest-azure/-/ms-rest-azure-2.4.5.tgz#bf27a7c8ff5f10a54f0f184130bfd0b7974e7553"
@@ -5700,12 +5855,15 @@ ncname@1.0.x:
     xml-char-classes "^1.0.0"
 
 nearley@^2.7.10:
-  version "2.11.0"
-  resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.11.0.tgz#5e626c79a6cd2f6ab9e7e5d5805e7668967757ae"
+  version "2.16.0"
+  resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.16.0.tgz#77c297d041941d268290ec84b739d0ee297e83a7"
+  integrity sha512-Tr9XD3Vt/EujXbZBv6UAHYoLUSMQAxSsTnm9K3koXzjzNWY195NqALeyrzLZBKzAkL3gl92BcSogqrHjD8QuUg==
   dependencies:
-    nomnom "~1.6.2"
+    commander "^2.19.0"
+    moo "^0.4.3"
     railroad-diagrams "^1.0.0"
-    randexp "^0.4.2"
+    randexp "0.4.6"
+    semver "^5.4.1"
 
 negotiator@0.6.1:
   version "0.6.1"
@@ -5820,13 +5978,6 @@ node-pre-gyp@^0.6.39:
     tar "^2.2.1"
     tar-pack "^3.4.0"
 
-nomnom@~1.6.2:
-  version "1.6.2"
-  resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.6.2.tgz#84a66a260174408fc5b77a18f888eccc44fb6971"
-  dependencies:
-    colors "0.5.x"
-    underscore "~1.4.4"
-
 noms@0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859"
@@ -5914,21 +6065,34 @@ object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
 
+object-inspect@^1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b"
+  integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==
+
 object-is@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6"
+  integrity sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=
 
-object-keys@^1.0.10, object-keys@^1.0.8:
+object-keys@^1.0.11, object-keys@^1.0.12:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032"
+  integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==
+
+object-keys@^1.0.8:
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
 
-object.assign@^4.0.4:
-  version "4.0.4"
-  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc"
+object.assign@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
+  integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
   dependencies:
     define-properties "^1.1.2"
-    function-bind "^1.1.0"
-    object-keys "^1.0.10"
+    function-bind "^1.1.1"
+    has-symbols "^1.0.0"
+    object-keys "^1.0.11"
 
 object.entries@^1.0.4:
   version "1.0.4"
@@ -5939,6 +6103,16 @@ object.entries@^1.0.4:
     function-bind "^1.1.0"
     has "^1.0.1"
 
+object.fromentries@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.0.tgz#49a543d92151f8277b3ac9600f1e930b189d30ab"
+  integrity sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==
+  dependencies:
+    define-properties "^1.1.2"
+    es-abstract "^1.11.0"
+    function-bind "^1.1.1"
+    has "^1.0.1"
+
 object.getownpropertydescriptors@^2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
@@ -5962,6 +6136,16 @@ object.values@^1.0.4:
     function-bind "^1.1.0"
     has "^1.0.1"
 
+object.values@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.0.tgz#bf6810ef5da3e5325790eaaa2be213ea84624da9"
+  integrity sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==
+  dependencies:
+    define-properties "^1.1.3"
+    es-abstract "^1.12.0"
+    function-bind "^1.1.1"
+    has "^1.0.3"
+
 on-finished@~2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
@@ -6112,10 +6296,11 @@ parse5@^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"
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c"
+  integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==
   dependencies:
-    "@types/node" "^6.0.46"
+    "@types/node" "*"
 
 parseurl@~1.3.2:
   version "1.3.2"
@@ -6624,6 +6809,15 @@ prop-types@^15.6.2:
     loose-envify "^1.3.1"
     object-assign "^4.1.1"
 
+prop-types@^15.7.2:
+  version "15.7.2"
+  resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
+  integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
+  dependencies:
+    loose-envify "^1.4.0"
+    object-assign "^4.1.1"
+    react-is "^16.8.1"
+
 proxy-addr@~2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
@@ -6697,23 +6891,32 @@ radium@^0.19.0:
     inline-style-prefixer "^2.0.5"
     prop-types "^15.5.8"
 
-raf@^3.1.0, raf@^3.3.2:
+raf@^3.1.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575"
   dependencies:
     performance-now "^2.1.0"
 
+raf@^3.4.0:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
+  integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
+  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"
+  integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=
 
 ramda@0.24.1:
   version "0.24.1"
   resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857"
 
-randexp@^0.4.2:
+randexp@0.4.6:
   version "0.4.6"
   resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3"
+  integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==
   dependencies:
     discontinuous-range "1.0.0"
     ret "~0.1.10"
@@ -6831,6 +7034,11 @@ react-inspector@^2.2.1:
     babel-runtime "^6.26.0"
     is-dom "^1.0.9"
 
+react-is@^16.8.1, react-is@^16.8.4:
+  version "16.8.4"
+  resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2"
+  integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==
+
 react-komposer@^1.9.0:
   version "1.13.1"
   resolved "https://registry.yarnpkg.com/react-komposer/-/react-komposer-1.13.1.tgz#4b8ac4bcc71323bd7413dcab95c831197f50eed0"
@@ -6951,12 +7159,14 @@ react-test-renderer@^16.0.0:
     object-assign "^4.1.1"
 
 react-test-renderer@^16.0.0-0:
-  version "16.1.0"
-  resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.1.0.tgz#33a1d3ce896311e0dd1547649b1456ffa7fda415"
+  version "16.8.4"
+  resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.4.tgz#abee4c2c3bf967a8892a7b37f77370c5570d5329"
+  integrity sha512-jQ9Tf/ilIGSr55Cz23AZ/7H3ABEdo9oy2zF9nDHZyhLHDSLKuoILxw2ifpBfuuwQvj4LCoqdru9iZf7gwFH28A==
   dependencies:
-    fbjs "^0.8.16"
     object-assign "^4.1.1"
-    prop-types "^15.6.0"
+    prop-types "^15.6.2"
+    react-is "^16.8.4"
+    scheduler "^0.13.4"
 
 react-tooltip@^3.4.0:
   version "3.4.0"
@@ -7058,6 +7268,15 @@ readable-stream@^2.2.2:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
+readable-stream@^3.1.1:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d"
+  integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==
+  dependencies:
+    inherits "^2.0.3"
+    string_decoder "^1.1.1"
+    util-deprecate "^1.0.1"
+
 readable-stream@~2.1.5:
   version "2.1.5"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0"
@@ -7361,6 +7580,7 @@ restore-cursor@^2.0.0:
 ret@~0.1.10:
   version "0.1.15"
   resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
+  integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
 
 right-align@^0.1.1:
   version "0.1.3"
@@ -7381,9 +7601,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
     hash-base "^2.0.0"
     inherits "^2.0.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"
+rst-selector-parser@^2.2.3:
+  version "2.2.3"
+  resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91"
+  integrity sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=
   dependencies:
     lodash.flattendeep "^4.4.0"
     nearley "^2.7.10"
@@ -7444,6 +7665,14 @@ sax@^1.2.1, sax@~1.2.1:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
 
+scheduler@^0.13.4:
+  version "0.13.4"
+  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.4.tgz#8fef05e7a3580c76c0364d2df5e550e4c9140298"
+  integrity sha512-cvSOlRPxOHs5dAhP9yiS/6IDmVAVxmk33f0CtTJRkmUWcb1Us+t7b1wqdzoC0REw2muC9V5f1L/w5R5uKGaepA==
+  dependencies:
+    loose-envify "^1.1.0"
+    object-assign "^4.1.1"
+
 schema-utils@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"
@@ -7458,6 +7687,11 @@ semver@^5.3.0:
   version "5.4.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
 
+semver@^5.4.1, semver@^5.6.0:
+  version "5.6.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
+  integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
+
 send@0.16.1:
   version "0.16.1"
   resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
@@ -7752,10 +7986,26 @@ string.prototype.padstart@^3.0.0:
     es-abstract "^1.4.3"
     function-bind "^1.0.2"
 
+string.prototype.trim@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea"
+  integrity sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=
+  dependencies:
+    define-properties "^1.1.2"
+    es-abstract "^1.5.0"
+    function-bind "^1.0.2"
+
 string_decoder@^0.10.25, string_decoder@~0.10.x:
   version "0.10.31"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
 
+string_decoder@^1.1.1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
+  integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==
+  dependencies:
+    safe-buffer "~5.1.0"
+
 string_decoder@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
@@ -8142,10 +8392,6 @@ uid-number@^0.0.6:
   version "1.8.3"
   resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
 
-underscore@~1.4.4:
-  version "1.4.4"
-  resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604"
-
 uniq@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
@@ -8234,7 +8480,7 @@ url@0.11.0, url@^0.11.0:
     punycode "1.3.2"
     querystring "0.2.0"
 
-util-deprecate@^1.0.2, util-deprecate@~1.0.1:
+util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"