Browse Source

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 years ago
parent
commit
d0c46ab377
32 changed files with 972 additions and 136 deletions
  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",
   "name": "coriolis-web",
-  "version": "1.4.4",
+  "version": "1.4.5",
   "license": "AGPL-3.0",
   "license": "AGPL-3.0",
   "scripts": {
   "scripts": {
     "start": "npm run env:dev && node server.js --dev",
     "start": "npm run env:dev && node server.js --dev",
@@ -8,14 +8,13 @@
     "env:prod": "cross-env NODE_ENV=production",
     "env:prod": "cross-env NODE_ENV=production",
     "cypress": "cypress open",
     "cypress": "cypress open",
     "test": "jest",
     "test": "jest",
-    "testc": "jest --runInBand -t 'WizardOptions Component'",
+    "testc": "jest --runInBand -t 'WizardStorage Component'",
     "storybook": "start-storybook -p 9001 -c private/storybook",
     "storybook": "start-storybook -p 9001 -c private/storybook",
     "lint": "eslint src private webpack.config.js --ext js,jsx",
     "lint": "eslint src private webpack.config.js --ext js,jsx",
     "build:clean": "rimraf \"dist/!(.git*|Procfile)**\"",
     "build:clean": "rimraf \"dist/!(.git*|Procfile)**\"",
     "build:copy": "copyfiles -u 1 public/* public/**/* dist",
     "build:copy": "copyfiles -u 1 public/* public/**/* dist",
     "prebuild": "npm run build:clean && npm run build:copy",
     "prebuild": "npm run build:clean && npm run build:copy",
     "build": "npm run env:prod -- webpack",
     "build": "npm run env:prod -- webpack",
-    "postinstall": "npm run flow-typed",
     "flow": "flow",
     "flow": "flow",
     "flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true"
     "flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true"
   },
   },
@@ -39,8 +38,8 @@
     "babel-eslint": "^8.0.1",
     "babel-eslint": "^8.0.1",
     "babel-jest": "^21.2.0",
     "babel-jest": "^21.2.0",
     "cypress": "3.0.3",
     "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": "^4.8.0",
     "eslint-config-airbnb": "^15.1.0",
     "eslint-config-airbnb": "^15.1.0",
     "eslint-plugin-cypress": "^2.0.1",
     "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};
   transition: stroke-dashoffset ${StyleProps.animations.swift};
 `
 `
 
 
-type Props = {
+export const TEST_ID = 'smallLoading'
+
+export type Props = {
   loadingProgress: number,
   loadingProgress: number,
 }
 }
 
 
@@ -97,7 +99,9 @@ class SmallLoading extends React.Component<Props> {
     }
     }
 
 
     return (
     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};
     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 = {
 export type Action = {
   label: string,
   label: string,
   color?: string,
   color?: string,
@@ -58,14 +63,10 @@ export type Action = {
   disabled?: boolean,
   disabled?: boolean,
   hidden?: 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,
   label: string,
   actions: Action[],
   actions: Action[],
-  style: any,
+  style?: any,
   'data-test-id'?: string,
   'data-test-id'?: string,
 }
 }
 
 
@@ -157,6 +158,7 @@ class ActionDropdown extends React.Component<Props, State> {
             onClick={() => { this.handleItemClick(action) }}
             onClick={() => { this.handleItemClick(action) }}
             color={action.color}
             color={action.color}
             disabled={action.disabled}
             disabled={action.disabled}
+            data-test-id={`${TEST_ID}-listItem-${action.label}`}
           >
           >
             {action.label}
             {action.label}
           </ListItem>
           </ListItem>
@@ -177,6 +179,7 @@ class ActionDropdown extends React.Component<Props, State> {
         width={`${StyleProps.inputSizes.regular.width}px`}
         width={`${StyleProps.inputSizes.regular.width}px`}
         padding={0}
         padding={0}
         customStyle={ListStyle}
         customStyle={ListStyle}
+        data-test-id={`${TEST_ID}-list`}
       >
       >
         <Tip innerRef={ref => { this.tipRef = ref }} borderColor={'rgba(111, 114, 118, 0.2)'} />
         <Tip innerRef={ref => { this.tipRef = ref }} borderColor={'rgba(111, 114, 118, 0.2)'} />
         {this.renderListItems()}
         {this.renderListItems()}
@@ -193,6 +196,7 @@ class ActionDropdown extends React.Component<Props, State> {
           value={this.props.label}
           value={this.props.label}
           customRef={ref => { this.buttonRef = ref }}
           customRef={ref => { this.buttonRef = ref }}
           onClick={() => { this.handleButtonClick() }}
           onClick={() => { this.handleButtonClick() }}
+          data-test-id={`${TEST_ID}-dropdownButton`}
         />
         />
         {this.renderList()}
         {this.renderList()}
       </Wrapper>
       </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', () => {
 describe('LoginOptions Component', () => {
   it('renders with all buttons', () => {
   it('renders with all buttons', () => {
     let wrapper = wrap({ buttons })
     let wrapper = wrap({ buttons })
-    expect(wrapper.find('button', true).length).toBe(4)
+    expect(wrapper.findPartialId('button').length).toBe(4)
     buttons.forEach(button => {
     buttons.forEach(button => {
       expect(wrapper.findText(`button-${button.id}`)).toBe(`<styled.div />Sign in with ${button.name}`)
       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)
       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;
   background: url('${arrowIcon}') center no-repeat;
   margin-left: 16px;
   margin-left: 16px;
 `
 `
+export const TEST_ID = 'mainDetailsTable'
 
 
-type Props = {
+export type Props = {
   item: ?MainItem,
   item: ?MainItem,
   instancesDetails: Instance[],
   instancesDetails: Instance[],
   networks?: Network[],
   networks?: Network[],
@@ -193,11 +194,11 @@ class MainDetailsTable extends React.Component<Props, State> {
         <RowHeader>
         <RowHeader>
           <RowHeaderColumn>
           <RowHeaderColumn>
             <HeaderIcon icon={icon} />
             <HeaderIcon icon={icon} />
-            <HeaderName source>{sourceName}</HeaderName>
+            <HeaderName source data-test-id={`${TEST_ID}-source-${icon}`}>{sourceName}</HeaderName>
             {destinationName ? <ArrowIcon /> : null}
             {destinationName ? <ArrowIcon /> : null}
           </RowHeaderColumn>
           </RowHeaderColumn>
           <RowHeaderColumn>
           <RowHeaderColumn>
-            <HeaderName>{destinationName}</HeaderName>
+            <HeaderName data-test-id={`${TEST_ID}-destination-${icon}`}>{destinationName}</HeaderName>
           </RowHeaderColumn>
           </RowHeaderColumn>
         </RowHeader>
         </RowHeader>
         <Collapse isOpened={isOpened} springConfig={{ stiffness: 100, damping: 20 }}>
         <Collapse isOpened={isOpened} springConfig={{ stiffness: 100, damping: 20 }}>
@@ -347,8 +348,8 @@ class MainDetailsTable extends React.Component<Props, State> {
     return this.renderRow(
     return this.renderRow(
       instance.instance_name,
       instance.instance_name,
       'instance',
       'instance',
-      `${instance.instance_name}`,
-      `${destinationName}`,
+      instance.instance_name,
+      destinationName,
       sourceBody,
       sourceBody,
       destinationBody
       destinationBody
     )
     )
@@ -367,7 +368,7 @@ class MainDetailsTable extends React.Component<Props, State> {
         </Header>
         </Header>
         {this.props.instancesDetails.map(instance => (
         {this.props.instancesDetails.map(instance => (
           <InstanceInfo key={instance.name}>
           <InstanceInfo key={instance.name}>
-            <InstanceName>{instance.name}</InstanceName>
+            <InstanceName data-test-id={`${TEST_ID}-instanceName-${instance.name}`}>{instance.name}</InstanceName>
             <InstanceBody>
             <InstanceBody>
               {this.renderInstanceDetails(instance)}
               {this.renderInstanceDetails(instance)}
               {this.renderNetworks(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', () => {
 describe('MainListFilter Component', () => {
   it('renders given items', () => {
   it('renders given items', () => {
     let wrapper = wrap({ items, actions, selectionInfo })
     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 => {
     items.forEach(item => {
       expect(wrapper.findText(`filterItem-${item.value}`)).toBe(item.label)
       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', () => {
 describe('MainListItem Component', () => {
   it('renders with given status', () => {
   it('renders with given status', () => {
     let wrapper = wrap({ item, endpointType })
     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', () => {
   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};
   transition: left ${StyleProps.animations.swift};
 `
 `
 
 
+export const TEST_ID = 'navigationMini'
+
 type State = {
 type State = {
   open: boolean,
   open: boolean,
 }
 }
@@ -77,7 +79,7 @@ class NavigationMini extends React.Component<{}, State> {
           open={this.state.open}
           open={this.state.open}
           onClick={() => { this.handleMenuToggleClick() }}
           onClick={() => { this.handleMenuToggleClick() }}
           dangerouslySetInnerHTML={{ __html: menuImage() }}
           dangerouslySetInnerHTML={{ __html: menuImage() }}
-          data-test-id="sideMenu-toggle"
+          data-test-id={`${TEST_ID}-toggleButton`}
         />
         />
         {this.state.open ? <Stub /> : null}
         {this.state.open ? <Stub /> : null}
         <NavigationStyled
         <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', () => {
 describe('NewItemDropdown Component', () => {
   it('opens list on click', () => {
   it('opens list on click', () => {
     let wrapper = wrap()
     let wrapper = wrap()
-    expect(wrapper.find('listItem', true).length).toBe(0)
+    expect(wrapper.findPartialId('listItem').length).toBe(0)
     wrapper.find('button').simulate('click')
     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', () => {
   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,
   value: string,
 }
 }
 
 
-type Props = {
+export type Props = {
   navigationItems: NavigationItem[],
   navigationItems: NavigationItem[],
   content: React.Node,
   content: React.Node,
   selectedValue: string,
   selectedValue: string,
@@ -63,6 +63,8 @@ type Props = {
   style?: any,
   style?: any,
 }
 }
 
 
+export const TEST_ID = 'panel'
+
 @observer
 @observer
 class Panel extends React.Component<Props> {
 class Panel extends React.Component<Props> {
   handleItemClick(item: NavigationItem) {
   handleItemClick(item: NavigationItem) {
@@ -79,9 +81,10 @@ class Panel extends React.Component<Props> {
             key={item.value}
             key={item.value}
             selected={this.props.selectedValue === item.value}
             selected={this.props.selectedValue === item.value}
             onClick={() => { this.handleItemClick(item) }}
             onClick={() => { this.handleItemClick(item) }}
+            data-test-id={`${TEST_ID}-navItem-${item.value}`}
           >{item.label}</NavigationItemDiv>
           >{item.label}</NavigationItemDiv>
         ))}</Navigation>
         ))}</Navigation>
-        <Content>{this.props.content}</Content>
+        <Content data-test-id={`${TEST_ID}-content`}>{this.props.content}</Content>
       </Wrapper>
       </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', () => {
 describe('PropertiesTable Component', () => {
   it('renders all properties', () => {
   it('renders all properties', () => {
     const wrapper = wrap({ properties, valueCallback })
     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', () => {
   it('renders boolean properties', () => {

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

@@ -35,7 +35,7 @@ describe('TTable Component', () => {
 
 
   it('renders header', () => {
   it('renders header', () => {
     let wrapper = wrap({ items, header: headerItems })
     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) => {
     headerItems.forEach((headerItem, i) => {
       expect(wrapper.findText(`header-${i}`)).toBe(headerItem)
       expect(wrapper.findText(`header-${i}`)).toBe(headerItem)
     })
     })
@@ -43,6 +43,6 @@ describe('TTable Component', () => {
 
 
   it('renders header with calculated widths', () => {
   it('renders header with calculated widths', () => {
     let wrapper = wrap({ items, header: headerItems })
     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', () => {
 describe('Timeline Component', () => {
   it('renders with correct dates', () => {
   it('renders with correct dates', () => {
     let wrapper = wrap({ items, selectedItem: items[2] })
     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 => {
     items.forEach(item => {
       expect(wrapper.findText(`label-${item.id}`)).toBe(moment(item.created_at).format('DD MMM YYYY'))
       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 React from 'react'
 import { shallow } from 'enzyme'
 import { shallow } from 'enzyme'
-import sinon from 'sinon'
 import TW from '../../../utils/TestWrapper'
 import TW from '../../../utils/TestWrapper'
 import DetailsContentHeader from '.'
 import DetailsContentHeader from '.'
 
 
@@ -69,11 +68,11 @@ describe('DetailsContentHeader Component', () => {
 
 
   it('renders correct STATUS pill', () => {
   it('renders correct STATUS pill', () => {
     let wrapper = wrap({ item })
     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] }
     let newItem = { ...item, executions: [...item.executions] }
     newItem.executions.push({ status: 'RUNNING', created_at: new Date() })
     newItem.executions.push({ status: 'RUNNING', created_at: new Date() })
     wrapper = wrap({ item: newItem })
     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', () => {
   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', () => {
 describe('DropdownFilterGroup Component', () => {
   it('renders correct dropdowns', () => {
   it('renders correct dropdowns', () => {
     let wrapper = wrap()
     let wrapper = wrap()
-    expect(wrapper.find('dropdown-', true).length).toBe(dropdowns.length)
+    expect(wrapper.findPartialId('dropdown-').length).toBe(dropdowns.length)
     dropdowns.forEach(dropdown => {
     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('items')[0].value).toBe(dropdown.items[0].value)
       expect(wrapper.find(`dropdown-${dropdown.key}`).prop('selectedItem')).toBe(dropdown.selectedItem || undefined)
       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,
   user: ?User,
   loading: boolean,
   loading: boolean,
   projects: Project[],
   projects: Project[],
@@ -89,7 +91,7 @@ type Props = {
   onUpdatePasswordClick: () => void,
   onUpdatePasswordClick: () => void,
   onDeleteClick: () => void,
   onDeleteClick: () => void,
 }
 }
-const testName = 'udContent'
+
 @observer
 @observer
 class UserDetailsContent extends React.Component<Props> {
 class UserDetailsContent extends React.Component<Props> {
   renderLoading() {
   renderLoading() {
@@ -110,7 +112,11 @@ class UserDetailsContent extends React.Component<Props> {
     return (
     return (
       <Buttons>
       <Buttons>
         <ButtonsColumn>
         <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>
         <ButtonsColumn>
         <ButtonsColumn>
           <Button
           <Button
@@ -118,6 +124,7 @@ class UserDetailsContent extends React.Component<Props> {
             hollow
             hollow
             onClick={() => { this.props.onDeleteClick() }}
             onClick={() => { this.props.onDeleteClick() }}
             disabled={this.props.isLoggedUser}
             disabled={this.props.isLoggedUser}
+            data-test-id={`${TEST_ID}-deleteUserButton`}
           >Delete user</Button>
           >Delete user</Button>
         </ButtonsColumn>
         </ButtonsColumn>
       </Buttons>
       </Buttons>
@@ -128,7 +135,7 @@ class UserDetailsContent extends React.Component<Props> {
     return projects.map((project, i) => (
     return projects.map((project, i) => (
       <span key={project.id}>
       <span key={project.id}>
         {project.label ? (
         {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}
             {project.label}
           </LinkStyled>
           </LinkStyled>
         ) : project.id}
         ) : project.id}
@@ -169,11 +176,11 @@ class UserDetailsContent extends React.Component<Props> {
         </Field>
         </Field>
         <Field>
         <Field>
           <Label>Email</Label>
           <Label>Email</Label>
-          {this.renderValue(user.email || '-')}
+          {this.renderValue(user.email || '-', 'email')}
         </Field>
         </Field>
         <Field>
         <Field>
           <Label>Primary Project</Label>
           <Label>Primary Project</Label>
-          {this.renderValue(primaryProjectName || '-')}
+          {this.renderValue(primaryProjectName || '-', 'primaryProject')}
         </Field>
         </Field>
         <Field>
         <Field>
           <Label>Project Membership</Label>
           <Label>Project Membership</Label>
@@ -181,7 +188,7 @@ class UserDetailsContent extends React.Component<Props> {
         </Field>
         </Field>
         <Field>
         <Field>
           <Label>Enabled</Label>
           <Label>Enabled</Label>
-          <Value>{user.enabled ? 'Yes' : 'No'}</Value>
+          <Value data-test-id={`${TEST_ID}-enabled`}>{user.enabled ? 'Yes' : 'No'}</Value>
         </Field>
         </Field>
       </Info>
       </Info>
     )
     )
@@ -190,7 +197,7 @@ class UserDetailsContent extends React.Component<Props> {
   renderValue(value: string, dataTestId?: string) {
   renderValue(value: string, dataTestId?: string) {
     return value !== '-' ? (
     return value !== '-' ? (
       <CopyValue
       <CopyValue
-        data-test-id={`${testName}-${dataTestId || ''}`}
+        data-test-id={`${TEST_ID}-${dataTestId || ''}`}
         value={value}
         value={value}
         maxWidth="90%"
         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', () => {
 describe('WizardEndpointList Component', () => {
   it('renders correct number of providers', () => {
   it('renders correct number of providers', () => {
     let wrapper = wrap({ endpoints, 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', () => {
   it('renders correct providers type', () => {

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

@@ -33,7 +33,7 @@ let instances = [
 describe('WizardInstances Component', () => {
 describe('WizardInstances Component', () => {
   it('has correct number of instances', () => {
   it('has correct number of instances', () => {
     let wrapper = wrap({ instances, currentPage: 1 })
     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', () => {
   it('has correct instances info', () => {

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

@@ -54,7 +54,7 @@ let selectedNetworks = [
 describe('WizardNetworks Component', () => {
 describe('WizardNetworks Component', () => {
   it('renders correct number of instance details', () => {
   it('renders correct number of instance details', () => {
     let wrapper = wrap({ networks, instancesDetails })
     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', () => {
   it('renders correct info for instance details', () => {

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

@@ -58,7 +58,7 @@ let fields = [
 describe('WizardOptions Component', () => {
 describe('WizardOptions Component', () => {
   it('has description and required field in simple tab', () => {
   it('has description and required field in simple tab', () => {
     let wrapper = wrap({ fields, selectedInstances: [], wizardType: 'migration' })
     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-description').length).toBe(1)
     expect(wrapper.find('field-required_string_field').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', () => {
   it('renders correct number of fields in advanced tab', () => {
     let wrapper = wrap({ fields, selectedInstances: [], useAdvancedOptions: true, wizardType: 'migration' })
     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', () => {
   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
   return disks
 }
 }
 
 
-type Props = {
+export const TEST_ID = 'wizardStorage'
+
+export type Props = {
   storageBackends: StorageBackend[],
   storageBackends: StorageBackend[],
   instancesDetails: Instance[],
   instancesDetails: Instance[],
   storageMap: ?StorageMap[],
   storageMap: ?StorageMap[],
@@ -147,7 +149,7 @@ type Props = {
 class WizardStorage extends React.Component<Props> {
 class WizardStorage extends React.Component<Props> {
   renderNoStorage() {
   renderNoStorage() {
     return (
     return (
-      <NoStorageMessage>
+      <NoStorageMessage data-test-id={`${TEST_ID}-noStorage`}>
         <BigStorageImage />
         <BigStorageImage />
         <NoStorageTitle>No storage backends were found</NoStorageTitle>
         <NoStorageTitle>No storage backends were found</NoStorageTitle>
         <NoStorageSubtitle>We could not find any storage backends. Coriolis will skip this step.</NoStorageSubtitle>
         <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]}>
               <StorageItem key={disk[diskFieldName]}>
                 <StorageImage backend={type === 'backend'} />
                 <StorageImage backend={type === 'backend'} />
                 <StorageTitle>
                 <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>
                 </StorageTitle>
                 <ArrowImage />
                 <ArrowImage />
                 <Dropdown
                 <Dropdown
@@ -201,6 +205,7 @@ class WizardStorage extends React.Component<Props> {
                   labelField="name"
                   labelField="name"
                   valueField="id"
                   valueField="id"
                   onChange={(item: StorageBackend) => { this.props.onChange(disk, item, type) }}
                   onChange={(item: StorageBackend) => { this.props.onChange(disk, item, type) }}
+                  data-test-id={`${TEST_ID}-${type}-destination`}
                 />
                 />
               </StorageItem>
               </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]: {
     [string]: {
       source_network: string,
       source_network: string,
       destination_network: string,
       destination_network: string,
-    } | 'string'
+    } | string
   }
   }
 }
 }

+ 0 - 1
src/types/Project.js

@@ -19,7 +19,6 @@ export type Project = {
   name: string,
   name: string,
   enabled?: boolean,
   enabled?: boolean,
   description?: string,
   description?: string,
-  enabled?: boolean,
 }
 }
 
 
 export type Role = {
 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
 // @flow
 
 
-import type { ShallowWrapper } from 'enzyme'
+import type { ShallowWrapper, ReactWrapper } from 'enzyme'
 
 
 export default class TestWrapper {
 export default class TestWrapper {
-  shallow: ShallowWrapper<any>
+  shallow: ShallowWrapper<any> | ReactWrapper<any>
   baseId: ?string
   baseId: ?string
   length: number
   length: number
 
 
-  constructor(wrapper: ShallowWrapper<any>, baseId?: ?string) {
+  constructor(wrapper: ShallowWrapper<any> | ReactWrapper<any>, baseId?: ?string) {
     this.shallow = wrapper
     this.shallow = wrapper
     this.baseId = baseId
     this.baseId = baseId
   }
   }
@@ -46,15 +46,32 @@ export default class TestWrapper {
     return render ? this.shallow.render().text() : this.shallow.text()
     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
     const actualId = this.baseId ? `${this.baseId}-${id}` : id
+    let o = opts || {}
     let tw = new TestWrapper(this.shallow.findWhere(w =>
     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)
     ), this.baseId)
     tw.length = tw.shallow.length
     tw.length = tw.shallow.length
     return tw
     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) {
   findText(id: string, isHostComponent?: boolean, render?: boolean) {
     const wrapper = this.find(id).shallow
     const wrapper = this.find(id).shallow
     const text = (component: any) => render ? component.render().text() : component.text()
     const text = (component: any) => render ? component.render().text() : component.text()

+ 322 - 76
yarn.lock

@@ -226,10 +226,6 @@
   version "8.5.1"
   version "8.5.1"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-8.5.1.tgz#4ec3020bcdfe2abffeef9ba3fbf26fca097514b5"
   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":
 "@types/react@^16.0.18":
   version "16.0.22"
   version "16.0.22"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.22.tgz#19ad106e124aceebd2b4d430a278d55413ee8759"
   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"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
   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:
 array-find@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8"
   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"
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
   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:
 arrify@^1.0.0, arrify@^1.0.1:
   version "1.0.1"
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
   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:
 cheerio@^1.0.0-rc.2:
   version "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"
   resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db"
+  integrity sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=
   dependencies:
   dependencies:
     css-select "~1.2.0"
     css-select "~1.2.0"
     dom-serializer "~0.1.0"
     dom-serializer "~0.1.0"
@@ -2304,10 +2315,6 @@ colormin@^1.0.5:
     css-color-names "0.0.4"
     css-color-names "0.0.4"
     has "^1.0.1"
     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:
 colors@^1.1.2:
   version "1.2.1"
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794"
   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"
   version "2.12.2"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
   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:
 common-tags@1.4.0, common-tags@^1.4.0:
   version "1.4.0"
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.4.0.tgz#1187be4f3d4cf0c0427d43f74eef1f73501614c0"
   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"
     foreach "^2.0.5"
     object-keys "^1.0.8"
     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:
 defined@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
   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:
 discontinuous-range@1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a"
   resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a"
+  integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=
 
 
 doctrine@1.5.0:
 doctrine@1.5.0:
   version "1.5.0"
   version "1.5.0"
@@ -2903,13 +2923,21 @@ dom-helpers@^3.2.0:
   version "3.2.1"
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a"
   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"
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
   resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
   dependencies:
   dependencies:
     domelementtype "~1.1.1"
     domelementtype "~1.1.1"
     entities "~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:
 dom-walk@^0.1.0:
   version "0.1.1"
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
   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"
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
   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"
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
   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:
 domelementtype@~1.1.1:
   version "1.1.3"
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
@@ -2933,8 +2966,9 @@ domhandler@2.1:
     domelementtype "1"
     domelementtype "1"
 
 
 domhandler@^2.3.0:
 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:
   dependencies:
     domelementtype "1"
     domelementtype "1"
 
 
@@ -2952,8 +2986,9 @@ domutils@1.5.1:
     domelementtype "1"
     domelementtype "1"
 
 
 domutils@^1.5.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:
   dependencies:
     dom-serializer "0"
     dom-serializer "0"
     domelementtype "1"
     domelementtype "1"
@@ -3046,43 +3081,65 @@ enhanced-resolve@^3.3.0, enhanced-resolve@^3.4.0:
     object-assign "^4.0.1"
     object-assign "^4.0.1"
     tapable "^0.2.7"
     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"
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
   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"
     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:
   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:
   dependencies:
+    array.prototype.flat "^1.2.1"
     cheerio "^1.0.0-rc.2"
     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"
     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-is "^1.0.1"
-    object.assign "^4.0.4"
+    object.assign "^4.1.0"
     object.entries "^1.0.4"
     object.entries "^1.0.4"
     object.values "^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:
 errno@^0.1.3, errno@^0.1.4:
   version "0.1.4"
   version "0.1.4"
@@ -3102,6 +3159,18 @@ error-stack-parser@^1.3.6:
   dependencies:
   dependencies:
     stackframe "^0.3.1"
     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:
 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"
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.9.0.tgz#690829a07cae36b222e7fd9b75c0d0573eb25227"
   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-date-object "^1.0.1"
     is-symbol "^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:
 es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14:
   version "0.10.30"
   version "0.10.30"
   resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.30.tgz#7141a16836697dbabfaaaeee41495ce29f52c939"
   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"
     function-bind "^1.1.0"
     is-callable "^1.1.3"
     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:
 functional-red-black-tree@^1.0.1:
   version "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"
   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"
   version "1.4.2"
   resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455"
   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:
 has-to-string-tag-x@^1.2.0:
   version "1.4.1"
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d"
   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"
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/html-element-attributes/-/html-element-attributes-1.3.0.tgz#f06ebdfce22de979db82020265cac541fb17d4fc"
   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:
 html-encoding-sniffer@^1.0.1:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
   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"
     toposort "^1.0.0"
 
 
 htmlparser2@^3.9.1:
 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:
   dependencies:
-    domelementtype "^1.3.0"
+    domelementtype "^1.3.1"
     domhandler "^2.3.0"
     domhandler "^2.3.0"
     domutils "^1.5.1"
     domutils "^1.5.1"
     entities "^1.1.1"
     entities "^1.1.1"
     inherits "^2.0.1"
     inherits "^2.0.1"
-    readable-stream "^2.0.2"
+    readable-stream "^3.1.1"
 
 
 htmlparser2@~3.3.0:
 htmlparser2@~3.3.0:
   version "3.3.0"
   version "3.3.0"
@@ -4444,6 +4544,11 @@ is-binary-path@^1.0.0:
   dependencies:
   dependencies:
     binary-extensions "^1.0.0"
     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:
 is-buffer@^1.1.5:
   version "1.1.5"
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
   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"
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2"
   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:
 is-ci@1.0.10, is-ci@^1.0.10:
   version "1.0.10"
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e"
   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"
     global-dirs "^0.1.0"
     is-path-inside "^1.0.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:
 is-number@^2.1.0:
   version "2.1.0"
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
   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"
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
   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:
 is-subset@^0.1.1:
   version "0.1.1"
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6"
   resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6"
+  integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=
 
 
 is-svg@^2.0.0:
 is-svg@^2.0.0:
   version "2.1.0"
   version "2.1.0"
@@ -4623,6 +4744,13 @@ is-symbol@^1.0.1:
   version "1.0.1"
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
   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:
 is-typedarray@~1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
   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"
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
   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:
 js-yaml@^3.4.3, js-yaml@^3.7.0, js-yaml@^3.9.1:
   version "3.10.0"
   version "3.10.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
@@ -5318,6 +5451,11 @@ lodash.deburr@^3.0.0:
   dependencies:
   dependencies:
     lodash._root "^3.0.0"
     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:
 lodash.flattendeep@^4.4.0:
   version "4.4.0"
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
   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"
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
   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:
 lodash.isplainobject@^4.0.6:
   version "4.0.6"
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
   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:
   dependencies:
     js-tokens "^3.0.0"
     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:
 lower-case@^1.1.1:
   version "1.1.4"
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
   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"
   version "2.18.1"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
   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:
 ms-rest-azure@^2.4.5:
   version "2.4.5"
   version "2.4.5"
   resolved "https://registry.yarnpkg.com/ms-rest-azure/-/ms-rest-azure-2.4.5.tgz#bf27a7c8ff5f10a54f0f184130bfd0b7974e7553"
   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"
     xml-char-classes "^1.0.0"
 
 
 nearley@^2.7.10:
 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:
   dependencies:
-    nomnom "~1.6.2"
+    commander "^2.19.0"
+    moo "^0.4.3"
     railroad-diagrams "^1.0.0"
     railroad-diagrams "^1.0.0"
-    randexp "^0.4.2"
+    randexp "0.4.6"
+    semver "^5.4.1"
 
 
 negotiator@0.6.1:
 negotiator@0.6.1:
   version "0.6.1"
   version "0.6.1"
@@ -5820,13 +5978,6 @@ node-pre-gyp@^0.6.39:
     tar "^2.2.1"
     tar "^2.2.1"
     tar-pack "^3.4.0"
     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:
 noms@0.0.0:
   version "0.0.0"
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859"
   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"
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
   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:
 object-is@^1.0.1:
   version "1.0.1"
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6"
   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"
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
   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:
   dependencies:
     define-properties "^1.1.2"
     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:
 object.entries@^1.0.4:
   version "1.0.4"
   version "1.0.4"
@@ -5939,6 +6103,16 @@ object.entries@^1.0.4:
     function-bind "^1.1.0"
     function-bind "^1.1.0"
     has "^1.0.1"
     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:
 object.getownpropertydescriptors@^2.0.3:
   version "2.0.3"
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
   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"
     function-bind "^1.1.0"
     has "^1.0.1"
     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:
 on-finished@~2.3.0:
   version "2.3.0"
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
   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"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94"
 
 
 parse5@^3.0.1:
 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:
   dependencies:
-    "@types/node" "^6.0.46"
+    "@types/node" "*"
 
 
 parseurl@~1.3.2:
 parseurl@~1.3.2:
   version "1.3.2"
   version "1.3.2"
@@ -6624,6 +6809,15 @@ prop-types@^15.6.2:
     loose-envify "^1.3.1"
     loose-envify "^1.3.1"
     object-assign "^4.1.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:
 proxy-addr@~2.0.2:
   version "2.0.2"
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
   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"
     inline-style-prefixer "^2.0.5"
     prop-types "^15.5.8"
     prop-types "^15.5.8"
 
 
-raf@^3.1.0, raf@^3.3.2:
+raf@^3.1.0:
   version "3.4.0"
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575"
   resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575"
   dependencies:
   dependencies:
     performance-now "^2.1.0"
     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:
 railroad-diagrams@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
   resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
+  integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=
 
 
 ramda@0.24.1:
 ramda@0.24.1:
   version "0.24.1"
   version "0.24.1"
   resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857"
   resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857"
 
 
-randexp@^0.4.2:
+randexp@0.4.6:
   version "0.4.6"
   version "0.4.6"
   resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3"
   resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3"
+  integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==
   dependencies:
   dependencies:
     discontinuous-range "1.0.0"
     discontinuous-range "1.0.0"
     ret "~0.1.10"
     ret "~0.1.10"
@@ -6831,6 +7034,11 @@ react-inspector@^2.2.1:
     babel-runtime "^6.26.0"
     babel-runtime "^6.26.0"
     is-dom "^1.0.9"
     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:
 react-komposer@^1.9.0:
   version "1.13.1"
   version "1.13.1"
   resolved "https://registry.yarnpkg.com/react-komposer/-/react-komposer-1.13.1.tgz#4b8ac4bcc71323bd7413dcab95c831197f50eed0"
   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"
     object-assign "^4.1.1"
 
 
 react-test-renderer@^16.0.0-0:
 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:
   dependencies:
-    fbjs "^0.8.16"
     object-assign "^4.1.1"
     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:
 react-tooltip@^3.4.0:
   version "3.4.0"
   version "3.4.0"
@@ -7058,6 +7268,15 @@ readable-stream@^2.2.2:
     string_decoder "~1.1.1"
     string_decoder "~1.1.1"
     util-deprecate "~1.0.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:
 readable-stream@~2.1.5:
   version "2.1.5"
   version "2.1.5"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0"
   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:
 ret@~0.1.10:
   version "0.1.15"
   version "0.1.15"
   resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
   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:
 right-align@^0.1.1:
   version "0.1.3"
   version "0.1.3"
@@ -7381,9 +7601,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
     hash-base "^2.0.0"
     hash-base "^2.0.0"
     inherits "^2.0.1"
     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:
   dependencies:
     lodash.flattendeep "^4.4.0"
     lodash.flattendeep "^4.4.0"
     nearley "^2.7.10"
     nearley "^2.7.10"
@@ -7444,6 +7665,14 @@ sax@^1.2.1, sax@~1.2.1:
   version "1.2.4"
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
   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:
 schema-utils@^0.3.0:
   version "0.3.0"
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"
   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"
   version "5.4.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
   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:
 send@0.16.1:
   version "0.16.1"
   version "0.16.1"
   resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
   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"
     es-abstract "^1.4.3"
     function-bind "^1.0.2"
     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:
 string_decoder@^0.10.25, string_decoder@~0.10.x:
   version "0.10.31"
   version "0.10.31"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
   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:
 string_decoder@~1.0.3:
   version "1.0.3"
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
   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"
   version "1.8.3"
   resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
   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:
 uniq@^1.0.1:
   version "1.0.1"
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
   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"
     punycode "1.3.2"
     querystring "0.2.0"
     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"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"