2
0
Эх сурвалжийг харах

Merge pull request #342 from smiclea/CORWEB-191

Improve unit testing framework and add new tests CORWEB-191
Dorin Paslaru 7 жил өмнө
parent
commit
6207d5221c
32 өөрчлөгдсөн 972 нэмэгдсэн , 136 устгасан
  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"