From 828bb6016edcce752caaed75514231478db0a3b2 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Fri, 28 Jun 2019 16:38:54 +0200 Subject: [PATCH 1/4] [Slider] Move to core --- docs/src/pages.js | 2 +- .../components/slider/ContinuousSlider.js | 2 +- .../components/slider/ContinuousSlider.tsx | 2 +- .../components/slider/CustomizedSlider.js | 2 +- .../components/slider/CustomizedSlider.tsx | 2 +- .../pages/components/slider/DiscreteSlider.js | 2 +- .../components/slider/DiscreteSlider.tsx | 2 +- .../pages/components/slider/InputSlider.js | 2 +- .../pages/components/slider/InputSlider.tsx | 2 +- .../pages/components/slider/RangeSlider.js | 2 +- .../pages/components/slider/RangeSlider.tsx | 2 +- .../pages/components/slider/VerticalSlider.js | 2 +- .../components/slider/VerticalSlider.tsx | 2 +- .../pages/customization/color/ColorTool.js | 2 +- .../material-ui-lab/src/Slider/Slider.test.js | 313 ------------ packages/material-ui-lab/src/index.d.ts | 1 - packages/material-ui-lab/src/index.js | 1 - .../src/Slider/Slider.d.ts | 2 +- .../src/Slider/Slider.js | 28 +- .../material-ui/src/Slider/Slider.test.js | 470 ++++++++++++++++++ .../src/Slider/ValueLabel.js | 2 +- .../src/Slider/index.d.ts | 0 .../src/Slider/index.js | 0 packages/material-ui/src/index.d.ts | 1 + packages/material-ui/src/index.js | 1 + .../material-ui/src/styles/overrides.d.ts | 22 +- packages/material-ui/src/styles/props.d.ts | 10 +- .../material-ui/src/test-utils/createMount.js | 4 +- .../src/test-utils/describeConformance.js | 8 + pages/api/slider.md | 8 +- scripts/sizeSnapshot/webpack.config.js | 2 +- 31 files changed, 532 insertions(+), 369 deletions(-) delete mode 100644 packages/material-ui-lab/src/Slider/Slider.test.js rename packages/{material-ui-lab => material-ui}/src/Slider/Slider.d.ts (96%) rename packages/{material-ui-lab => material-ui}/src/Slider/Slider.js (96%) create mode 100644 packages/material-ui/src/Slider/Slider.test.js rename packages/{material-ui-lab => material-ui}/src/Slider/ValueLabel.js (97%) rename packages/{material-ui-lab => material-ui}/src/Slider/index.d.ts (100%) rename packages/{material-ui-lab => material-ui}/src/Slider/index.js (100%) diff --git a/docs/src/pages.js b/docs/src/pages.js index 080a0579646db5..31c38da88e8350 100644 --- a/docs/src/pages.js +++ b/docs/src/pages.js @@ -38,6 +38,7 @@ const pages = [ { pathname: '/components/pickers' }, { pathname: '/components/radio-buttons' }, { pathname: '/components/selects' }, + { pathname: '/components/slider' }, { pathname: '/components/switches' }, { pathname: '/components/text-fields' }, { pathname: '/components/transfer-list' }, @@ -111,7 +112,6 @@ const pages = [ subheader: '/components/lab', children: [ { pathname: '/components/about-the-lab' }, - { pathname: '/components/slider' }, { pathname: '/components/speed-dial' }, { pathname: '/components/toggle-button' }, ], diff --git a/docs/src/pages/components/slider/ContinuousSlider.js b/docs/src/pages/components/slider/ContinuousSlider.js index f3640971671d97..eb20b018d6392d 100644 --- a/docs/src/pages/components/slider/ContinuousSlider.js +++ b/docs/src/pages/components/slider/ContinuousSlider.js @@ -2,7 +2,7 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; import VolumeDown from '@material-ui/icons/VolumeDown'; import VolumeUp from '@material-ui/icons/VolumeUp'; diff --git a/docs/src/pages/components/slider/ContinuousSlider.tsx b/docs/src/pages/components/slider/ContinuousSlider.tsx index 144e2a1836834c..66317d6d31d273 100644 --- a/docs/src/pages/components/slider/ContinuousSlider.tsx +++ b/docs/src/pages/components/slider/ContinuousSlider.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; import VolumeDown from '@material-ui/icons/VolumeDown'; import VolumeUp from '@material-ui/icons/VolumeUp'; diff --git a/docs/src/pages/components/slider/CustomizedSlider.js b/docs/src/pages/components/slider/CustomizedSlider.js index 3b0e156fdabd56..16ab400fae430b 100644 --- a/docs/src/pages/components/slider/CustomizedSlider.js +++ b/docs/src/pages/components/slider/CustomizedSlider.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withStyles, makeStyles } from '@material-ui/core/styles'; import Paper from '@material-ui/core/Paper'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; import Typography from '@material-ui/core/Typography'; import Tooltip from '@material-ui/core/Tooltip'; diff --git a/docs/src/pages/components/slider/CustomizedSlider.tsx b/docs/src/pages/components/slider/CustomizedSlider.tsx index c339983e1a05c5..967700c25969d8 100644 --- a/docs/src/pages/components/slider/CustomizedSlider.tsx +++ b/docs/src/pages/components/slider/CustomizedSlider.tsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { fade, withStyles, makeStyles, Theme, createStyles } from '@material-ui/core/styles'; import Paper from '@material-ui/core/Paper'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; import Typography from '@material-ui/core/Typography'; import Tooltip from '@material-ui/core/Tooltip'; import PopperJs from 'popper.js'; diff --git a/docs/src/pages/components/slider/DiscreteSlider.js b/docs/src/pages/components/slider/DiscreteSlider.js index b2d258a9fd4202..bc806e807795be 100644 --- a/docs/src/pages/components/slider/DiscreteSlider.js +++ b/docs/src/pages/components/slider/DiscreteSlider.js @@ -1,7 +1,7 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; const useStyles = makeStyles(theme => ({ root: { diff --git a/docs/src/pages/components/slider/DiscreteSlider.tsx b/docs/src/pages/components/slider/DiscreteSlider.tsx index 35db3aef2b3b09..9324d54158b8ff 100644 --- a/docs/src/pages/components/slider/DiscreteSlider.tsx +++ b/docs/src/pages/components/slider/DiscreteSlider.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; const useStyles = makeStyles((theme: Theme) => createStyles({ diff --git a/docs/src/pages/components/slider/InputSlider.js b/docs/src/pages/components/slider/InputSlider.js index 24e5159a335f70..8dcbb78d2c26cd 100644 --- a/docs/src/pages/components/slider/InputSlider.js +++ b/docs/src/pages/components/slider/InputSlider.js @@ -2,7 +2,7 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; import Input from '@material-ui/core/Input'; import VolumeUp from '@material-ui/icons/VolumeUp'; diff --git a/docs/src/pages/components/slider/InputSlider.tsx b/docs/src/pages/components/slider/InputSlider.tsx index 2f0b20fc3bb812..4a2df7e109625b 100644 --- a/docs/src/pages/components/slider/InputSlider.tsx +++ b/docs/src/pages/components/slider/InputSlider.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; import Input from '@material-ui/core/Input'; import VolumeUp from '@material-ui/icons/VolumeUp'; diff --git a/docs/src/pages/components/slider/RangeSlider.js b/docs/src/pages/components/slider/RangeSlider.js index 8ef7cb23c305ba..198aa778147628 100644 --- a/docs/src/pages/components/slider/RangeSlider.js +++ b/docs/src/pages/components/slider/RangeSlider.js @@ -1,7 +1,7 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; const useStyles = makeStyles({ root: { diff --git a/docs/src/pages/components/slider/RangeSlider.tsx b/docs/src/pages/components/slider/RangeSlider.tsx index 5d8859b7356ed7..fa42c2dc53927b 100644 --- a/docs/src/pages/components/slider/RangeSlider.tsx +++ b/docs/src/pages/components/slider/RangeSlider.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; import Tooltip from '@material-ui/core/Tooltip'; const useStyles = makeStyles({ diff --git a/docs/src/pages/components/slider/VerticalSlider.js b/docs/src/pages/components/slider/VerticalSlider.js index f582e33dc540fe..dc1171989e59ea 100644 --- a/docs/src/pages/components/slider/VerticalSlider.js +++ b/docs/src/pages/components/slider/VerticalSlider.js @@ -1,7 +1,7 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; const useStyles = makeStyles({ root: { diff --git a/docs/src/pages/components/slider/VerticalSlider.tsx b/docs/src/pages/components/slider/VerticalSlider.tsx index 79289f5a9eb1d0..86d7e945140cf3 100644 --- a/docs/src/pages/components/slider/VerticalSlider.tsx +++ b/docs/src/pages/components/slider/VerticalSlider.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; const useStyles = makeStyles({ root: { diff --git a/docs/src/pages/customization/color/ColorTool.js b/docs/src/pages/customization/color/ColorTool.js index 4000c226bc623e..038dd70d822fe4 100644 --- a/docs/src/pages/customization/color/ColorTool.js +++ b/docs/src/pages/customization/color/ColorTool.js @@ -9,7 +9,7 @@ import Tooltip from '@material-ui/core/Tooltip'; import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; import CheckIcon from '@material-ui/icons/Check'; -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; import { capitalize } from '@material-ui/core/utils'; import ColorDemo from './ColorDemo'; import { DispatchContext } from 'docs/src/modules/components/ThemeContext'; diff --git a/packages/material-ui-lab/src/Slider/Slider.test.js b/packages/material-ui-lab/src/Slider/Slider.test.js deleted file mode 100644 index 3c6add08dc57d0..00000000000000 --- a/packages/material-ui-lab/src/Slider/Slider.test.js +++ /dev/null @@ -1,313 +0,0 @@ -import React from 'react'; -import { spy } from 'sinon'; -import { assert } from 'chai'; -import { - createMount, - getClasses, - findOutermostIntrinsic, - wrapsIntrinsicElement, -} from '@material-ui/core/test-utils'; -import describeConformance from '@material-ui/core/test-utils/describeConformance'; -import Slider from './Slider'; - -function touchList(touchArray) { - touchArray.item = idx => touchArray[idx]; - return touchArray; -} - -function fireBodyMouseEvent(name, properties = {}) { - const event = document.createEvent('MouseEvents'); - event.initEvent(name, true, true); - Object.keys(properties).forEach(key => { - event[key] = properties[key]; - }); - document.body.dispatchEvent(event); - return event; -} - -describe('', () => { - let mount; - let classes; - - before(() => { - classes = getClasses(); - mount = createMount({ strict: false }); - }); - - after(() => { - mount.cleanUp(); - }); - - describeConformance(, () => ({ - classes, - inheritComponent: 'span', - mount, - refInstanceof: window.HTMLSpanElement, - testComponentPropWith: 'span', - })); - - function findThumb(wrapper) { - // Will also match any other react component if not filtered. They won't appear in the DOM - // and are therefore an implementation detail. We're interested in what the user - // interacts with. - return wrapper.find('[role="slider"]').filterWhere(wrapsIntrinsicElement); - } - - it('should call handlers', () => { - const handleChange = spy(); - const handleChangeCommitted = spy(); - - const wrapper = mount( - , - ); - - wrapper.simulate('click'); - wrapper.simulate('mousedown'); - // document.simulate('mouseup') - document.body.dispatchEvent(new window.MouseEvent('mouseup')); - - assert.strictEqual(handleChange.callCount, 1); - assert.strictEqual(handleChangeCommitted.callCount, 1); - - assert.strictEqual(handleChange.args[0].length, 2); - assert.strictEqual(handleChangeCommitted.args[0].length, 2); - }); - - it('should only listen to changes from the same touchpoint', () => { - const handleChange = spy(); - const handleChangeCommitted = spy(); - const touches = [{ pageX: 0, pageY: 0 }]; - const wrapper = mount( - , - ); - - const event = fireBodyMouseEvent('touchstart', { - changedTouches: touchList([{ identifier: 1 }]), - touches, - }); - wrapper.getDOMNode().dispatchEvent(event); - assert.strictEqual(handleChange.callCount, 1); - assert.strictEqual(handleChangeCommitted.callCount, 0); - fireBodyMouseEvent('touchend', { - changedTouches: touchList([{ identifier: 2 }]), - touches, - }); - assert.strictEqual(handleChange.callCount, 1); - assert.strictEqual(handleChangeCommitted.callCount, 0); - fireBodyMouseEvent('touchmove', { - changedTouches: touchList([{ identifier: 1 }]), - touches, - }); - assert.strictEqual(handleChange.callCount, 2); - assert.strictEqual(handleChangeCommitted.callCount, 0); - fireBodyMouseEvent('touchend', { - changedTouches: touchList([{ identifier: 1 }]), - touches, - }); - assert.strictEqual(handleChange.callCount, 2); - assert.strictEqual(handleChangeCommitted.callCount, 1); - }); - - describe('when mouse leaves window', () => { - it('should move to the end', () => { - const handleChange = spy(); - - const wrapper = mount(); - - wrapper.simulate('mousedown'); - document.body.dispatchEvent(new window.MouseEvent('mouseleave')); - - assert.strictEqual(handleChange.callCount, 1); - }); - }); - - describe('when mouse reenters window', () => { - it('should update if mouse is still clicked', () => { - const handleChange = spy(); - - const wrapper = mount(); - - wrapper.simulate('mousedown'); - document.body.dispatchEvent(new window.MouseEvent('mouseleave')); - - const mouseEnter = new window.Event('mouseenter'); - mouseEnter.buttons = 1; - document.body.dispatchEvent(mouseEnter); - document.body.dispatchEvent(new window.MouseEvent('mousemove')); - - assert.strictEqual(handleChange.callCount, 2); - }); - - it('should not update if mouse is not clicked', () => { - const handleChange = spy(); - - const wrapper = mount(); - - wrapper.simulate('mousedown'); - document.body.dispatchEvent(new window.MouseEvent('mouseleave')); - - const mouseEnter = new window.Event('mouseenter'); - mouseEnter.buttons = 0; - document.body.dispatchEvent(mouseEnter); - document.body.dispatchEvent(new window.MouseEvent('mousemove')); - - assert.strictEqual(handleChange.callCount, 1); - }); - }); - - describe('unmount', () => { - it('should not have global event listeners registered after unmount', () => { - const handleChange = spy(); - const handleChangeCommitted = spy(); - - const wrapper = mount( - , - ); - - const callGlobalListeners = () => { - document.body.dispatchEvent(new window.MouseEvent('mousemove')); - document.body.dispatchEvent(new window.MouseEvent('mouseup')); - }; - - wrapper.simulate('mousedown'); - callGlobalListeners(); - // pre condition: the dispatched event actually did something when mounted - assert.strictEqual(handleChange.callCount, 2); - assert.strictEqual(handleChangeCommitted.callCount, 1); - wrapper.unmount(); - // After unmounting global listeners should not be registered anymore since that would - // break component encapsulation. If they are still mounted either react will throw warnings - // or other component logic throws. - // post condition: the dispatched events dont cause errors/warnings - callGlobalListeners(); - assert.strictEqual(handleChange.callCount, 2); - assert.strictEqual(handleChangeCommitted.callCount, 1); - }); - }); - - describe('prop: orientation', () => { - it('should render with the default and vertical classes', () => { - const wrapper = mount(); - assert.strictEqual( - wrapper - .find(`.${classes.root}`) - .first() - .hasClass(classes.vertical), - true, - ); - }); - }); - - describe('prop: disabled', () => { - it('should render the disabled classes', () => { - const wrapper = mount(); - assert.strictEqual(findOutermostIntrinsic(wrapper).hasClass(classes.disabled), true); - }); - }); - - describe('keyboard', () => { - let wrapper; - - const moveLeftEvent = { - key: 'ArrowLeft', - }; - const moveRightEvent = { - key: 'ArrowRight', - }; - - before(() => { - const onChange = (_, value) => { - wrapper.setProps({ value }); - }; - wrapper = mount(); - }); - - it('should reach right edge value', () => { - wrapper.setProps({ value: 90 }); - const thumb = findThumb(wrapper); - - thumb.simulate('keydown', moveRightEvent); - assert.strictEqual(wrapper.props().value, 100); - - thumb.simulate('keydown', moveRightEvent); - assert.strictEqual(wrapper.props().value, 108); - - thumb.simulate('keydown', moveLeftEvent); - assert.strictEqual(wrapper.props().value, 100); - - thumb.simulate('keydown', moveLeftEvent); - assert.strictEqual(wrapper.props().value, 90); - }); - - it('should reach left edge value', () => { - wrapper.setProps({ value: 20 }); - const thumb = findThumb(wrapper); - thumb.simulate('keydown', moveLeftEvent); - assert.strictEqual(wrapper.props().value, 10); - - thumb.simulate('keydown', moveLeftEvent); - assert.strictEqual(wrapper.props().value, 6); - - thumb.simulate('keydown', moveRightEvent); - assert.strictEqual(wrapper.props().value, 20); - - thumb.simulate('keydown', moveRightEvent); - assert.strictEqual(wrapper.props().value, 30); - }); - - it('should round value to step precision', () => { - wrapper.setProps({ value: 0.2, step: 0.1, min: 0 }); - const thumb = findThumb(wrapper); - thumb.simulate('keydown', moveRightEvent); - assert.strictEqual(wrapper.props().value, 0.3); - }); - - it('should not fail to round value to step precision when step is very small', () => { - wrapper.setProps({ value: 0.00000002, step: 0.00000001, min: 0, max: 0.00000005 }); - const thumb = findThumb(wrapper); - thumb.simulate('keydown', moveRightEvent); - assert.strictEqual(wrapper.props().value, 0.00000003); - }); - - it('should not fail to round value to step precision when step is very small and negative', () => { - wrapper.setProps({ value: -0.00000002, step: 0.00000001, min: -0.00000005, max: 0 }); - const thumb = findThumb(wrapper); - thumb.simulate('keydown', moveLeftEvent); - assert.strictEqual(wrapper.props().value, -0.00000003); - }); - }); - - describe('markActive state', () => { - function getActives(wrapper) { - return wrapper - .find(`.${classes.markLabel}`) - .map(node => node.hasClass(classes.markLabelActive)); - } - - it('sets the marks active that are `within` the value', () => { - const marks = [{ value: 5 }, { value: 10 }, { value: 15 }]; - - const singleValueWrapper = mount( - , - ); - assert.deepEqual(getActives(singleValueWrapper), [true, true, false]); - - const rangeValueWrapper = mount( - , - ); - assert.deepEqual(getActives(rangeValueWrapper), [false, true, false]); - }); - - it('uses closed intervals for the within check', () => { - const exactMarkWrapper = mount( - , - ); - assert.deepEqual(getActives(exactMarkWrapper), [true, true, true]); - - const ofByOneWrapper = mount( - , - ); - assert.deepEqual(getActives(ofByOneWrapper), [true, true, false]); - }); - }); -}); diff --git a/packages/material-ui-lab/src/index.d.ts b/packages/material-ui-lab/src/index.d.ts index 2e6be5847f9d2f..31fe668c93d257 100644 --- a/packages/material-ui-lab/src/index.d.ts +++ b/packages/material-ui-lab/src/index.d.ts @@ -1,4 +1,3 @@ -export { default as Slider } from './Slider'; export { default as SpeedDial } from './SpeedDial'; export { default as SpeedDialAction } from './SpeedDialAction'; export { default as SpeedDialIcon } from './SpeedDialIcon'; diff --git a/packages/material-ui-lab/src/index.js b/packages/material-ui-lab/src/index.js index 2e6be5847f9d2f..31fe668c93d257 100644 --- a/packages/material-ui-lab/src/index.js +++ b/packages/material-ui-lab/src/index.js @@ -1,4 +1,3 @@ -export { default as Slider } from './Slider'; export { default as SpeedDial } from './SpeedDial'; export { default as SpeedDialAction } from './SpeedDialAction'; export { default as SpeedDialIcon } from './SpeedDialIcon'; diff --git a/packages/material-ui-lab/src/Slider/Slider.d.ts b/packages/material-ui/src/Slider/Slider.d.ts similarity index 96% rename from packages/material-ui-lab/src/Slider/Slider.d.ts rename to packages/material-ui/src/Slider/Slider.d.ts index f2396f33f27d48..3614369d96a978 100644 --- a/packages/material-ui-lab/src/Slider/Slider.d.ts +++ b/packages/material-ui/src/Slider/Slider.d.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { StandardProps } from '@material-ui/core'; +import { StandardProps } from '..'; export interface Mark { value: number; diff --git a/packages/material-ui-lab/src/Slider/Slider.js b/packages/material-ui/src/Slider/Slider.js similarity index 96% rename from packages/material-ui-lab/src/Slider/Slider.js rename to packages/material-ui/src/Slider/Slider.js index 5be2e044c8508b..3ed73abc0188ed 100644 --- a/packages/material-ui-lab/src/Slider/Slider.js +++ b/packages/material-ui/src/Slider/Slider.js @@ -1,14 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; -import { useTheme, withStyles, fade, lighten } from '@material-ui/core/styles'; -import { - useEventCallback, - useForkRef, - ownerWindow, - useIsFocusVisible, -} from '@material-ui/core/utils'; import { chainPropTypes } from '@material-ui/utils'; +import withStyles from '../styles/withStyles'; +import useTheme from '../styles/useTheme'; +import { fade, lighten } from '../styles/colorManipulator'; +import { useIsFocusVisible } from '../utils/focusVisible'; +import ownerWindow from '../utils/ownerWindow'; +import useEventCallback from '../utils/useEventCallback'; +import { useForkRef } from '../utils/reactHelpers'; import ValueLabel from './ValueLabel'; function asc(a, b) { @@ -125,10 +125,6 @@ const axisProps = { offset: percent => ({ bottom: `${percent}%` }), leap: percent => ({ height: `${percent}%` }), }, - 'vertical-reverse': { - offset: percent => ({ top: `${percent}%` }), - leap: percent => ({ height: `${percent}%` }), - }, }; const defaultMarks = []; @@ -443,7 +439,7 @@ const Slider = React.forwardRef(function Slider(props, ref) { axis += '-reverse'; } - const getNewValue = React.useCallback( + const getFingerNewValue = React.useCallback( ({ finger, move = false, values: values2, source }) => { const { current: slider } = sliderRef; const { width, height, bottom, left } = slider.getBoundingClientRect(); @@ -502,7 +498,7 @@ const Slider = React.forwardRef(function Slider(props, ref) { return; } - const { newValue, activeIndex } = getNewValue({ + const { newValue, activeIndex } = getFingerNewValue({ finger, move: true, values, @@ -525,7 +521,7 @@ const Slider = React.forwardRef(function Slider(props, ref) { return; } - const { newValue } = getNewValue({ finger, values, source: valueDerived }); + const { newValue } = getFingerNewValue({ finger, values, source: valueDerived }); setActive(-1); if (event.type === 'touchend') { @@ -562,7 +558,7 @@ const Slider = React.forwardRef(function Slider(props, ref) { touchId.current = touch.identifier; } const finger = trackFinger(event, touchId); - const { newValue, activeIndex } = getNewValue({ finger, values, source: valueDerived }); + const { newValue, activeIndex } = getFingerNewValue({ finger, values, source: valueDerived }); focusThumb({ sliderRef, activeIndex, setActive }); if (!isControlled) { @@ -605,7 +601,7 @@ const Slider = React.forwardRef(function Slider(props, ref) { event.preventDefault(); const finger = trackFinger(event, touchId); - const { newValue, activeIndex } = getNewValue({ finger, values, source: valueDerived }); + const { newValue, activeIndex } = getFingerNewValue({ finger, values, source: valueDerived }); focusThumb({ sliderRef, activeIndex, setActive }); if (!isControlled) { diff --git a/packages/material-ui/src/Slider/Slider.test.js b/packages/material-ui/src/Slider/Slider.test.js new file mode 100644 index 00000000000000..ab29e44e50361c --- /dev/null +++ b/packages/material-ui/src/Slider/Slider.test.js @@ -0,0 +1,470 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { spy, stub } from 'sinon'; +import { expect } from 'chai'; +import { createMount, getClasses } from '@material-ui/core/test-utils'; +import describeConformance from '@material-ui/core/test-utils/describeConformance'; +import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; +import { cleanup, createClientRender, fireEvent } from 'test/utils/createClientRender'; +import consoleErrorMock from 'test/utils/consoleErrorMock'; +import Slider from './Slider'; + +function touchList(touchArray) { + touchArray.item = idx => touchArray[idx]; + return touchArray; +} + +function fireBodyMouseEvent(name, properties = {}) { + const event = document.createEvent('MouseEvents'); + event.initEvent(name, true, true); + Object.keys(properties).forEach(key => { + event[key] = properties[key]; + }); + document.body.dispatchEvent(event); + return event; +} + +describe('', () => { + let mount; + let render; + let classes; + + before(() => { + render = createClientRender({ strict: true }); + classes = getClasses(); + mount = createMount({ strict: true }); + }); + + after(() => { + cleanup(); + mount.cleanUp(); + }); + + describeConformance(, () => ({ + classes, + inheritComponent: 'span', + mount, + refInstanceof: window.HTMLSpanElement, + testComponentPropWith: 'span', + })); + + it('should call handlers', () => { + const handleChange = spy(); + const handleChangeCommitted = spy(); + + const { container, getByRole } = render( + , + ); + + fireEvent.mouseDown(container.firstChild); + document.body.dispatchEvent(new window.MouseEvent('mouseup')); + + expect(handleChange.callCount).to.equal(1); + expect(handleChangeCommitted.callCount).to.equal(1); + + fireEvent.keyDown(getByRole('slider'), { + key: 'Home', + }); + expect(handleChange.callCount).to.equal(2); + expect(handleChangeCommitted.callCount).to.equal(2); + }); + + it('should only listen to changes from the same touchpoint', () => { + const handleChange = spy(); + const handleChangeCommitted = spy(); + const { container } = render( + , + ); + + const event = fireBodyMouseEvent('touchstart', { + changedTouches: touchList([{ identifier: 1 }]), + }); + container.firstChild.dispatchEvent(event); + expect(handleChange.callCount).to.equal(1); + expect(handleChangeCommitted.callCount).to.equal(0); + + fireBodyMouseEvent('touchend', { + changedTouches: touchList([{ identifier: 2 }]), + }); + expect(handleChange.callCount).to.equal(1); + expect(handleChangeCommitted.callCount).to.equal(0); + + fireBodyMouseEvent('touchmove', { + changedTouches: touchList([{ identifier: 1 }]), + }); + expect(handleChange.callCount).to.equal(2); + expect(handleChangeCommitted.callCount).to.equal(0); + + fireBodyMouseEvent('touchmove', { + changedTouches: touchList([{ identifier: 2 }]), + }); + expect(handleChange.callCount).to.equal(2); + expect(handleChangeCommitted.callCount).to.equal(0); + + fireBodyMouseEvent('touchend', { + changedTouches: touchList([{ identifier: 1 }]), + }); + expect(handleChange.callCount).to.equal(2); + expect(handleChangeCommitted.callCount).to.equal(1); + }); + + describe('when mouse reenters window', () => { + it('should update if mouse is still clicked', () => { + const handleChange = spy(); + const { container } = render(); + + fireEvent.mouseDown(container.firstChild); + document.body.dispatchEvent(new window.MouseEvent('mouseleave')); + const mouseEnter = new window.Event('mouseenter'); + mouseEnter.buttons = 1; + document.body.dispatchEvent(mouseEnter); + expect(handleChange.callCount).to.equal(1); + + document.body.dispatchEvent(new window.MouseEvent('mousemove')); + expect(handleChange.callCount).to.equal(2); + }); + + it('should not update if mouse is not clicked', () => { + const handleChange = spy(); + const { container } = render(); + + fireEvent.mouseDown(container.firstChild); + document.body.dispatchEvent(new window.MouseEvent('mouseleave')); + const mouseEnter = new window.Event('mouseenter'); + mouseEnter.buttons = 0; + document.body.dispatchEvent(mouseEnter); + expect(handleChange.callCount).to.equal(1); + + document.body.dispatchEvent(new window.MouseEvent('mousemove')); + expect(handleChange.callCount).to.equal(1); + }); + }); + + describe('prop: orientation', () => { + it('should render with the default and vertical classes', () => { + const { container } = render(); + expect(container.firstChild).to.have.class(classes.vertical); + }); + + it('should report the right position', () => { + const handleChange = spy(); + const { container } = render( + , + ); + stub(container.firstChild, 'getBoundingClientRect').callsFake(() => ({ + width: 10, + height: 100, + bottom: 100, + left: 0, + })); + + const event = fireBodyMouseEvent('touchstart', { + changedTouches: touchList([{ identifier: 1, pageX: 0, pageY: 20 }]), + }); + container.firstChild.dispatchEvent(event); + + fireBodyMouseEvent('touchmove', { + changedTouches: touchList([{ identifier: 1, pageX: 0, pageY: 22 }]), + }); + + expect(handleChange.callCount).to.equal(2); + expect(handleChange.args[0][1]).to.equal(80); + expect(handleChange.args[1][1]).to.equal(78); + }); + }); + + describe('range', () => { + it('should support keyboard', () => { + const { container } = render(); + const thumb1 = container.querySelectorAll('[role="slider"]')[0]; + const thumb2 = container.querySelectorAll('[role="slider"]')[1]; + + fireEvent.keyDown(thumb1, { + key: 'ArrowRight', + }); + expect(thumb1.getAttribute('aria-valuenow')).to.equal('21'); + + fireEvent.keyDown(thumb2, { + key: 'ArrowLeft', + }); + expect(thumb2.getAttribute('aria-valuenow')).to.equal('29'); + }); + + it('should support mouse events', () => { + const handleChange = spy(); + const { container } = render(); + stub(container.firstChild, 'getBoundingClientRect').callsFake(() => ({ + width: 100, + height: 10, + bottom: 10, + left: 0, + })); + + const event = fireBodyMouseEvent('touchstart', { + changedTouches: touchList([{ identifier: 1, pageX: 21, pageY: 0 }]), + }); + container.firstChild.dispatchEvent(event); + + fireBodyMouseEvent('touchmove', { + changedTouches: touchList([{ identifier: 1, pageX: 22, pageY: 0 }]), + }); + fireBodyMouseEvent('touchmove', { + changedTouches: touchList([{ identifier: 1, pageX: 22, pageY: 0 }]), + }); + + expect(handleChange.callCount).to.equal(3); + expect(handleChange.args[0][1]).to.deep.equal([21, 30]); + expect(handleChange.args[1][1]).to.deep.equal([22, 30]); + expect(handleChange.args[2][1]).to.equal(handleChange.args[1][1]); + }); + }); + + describe('prop: step', () => { + it('should handle a null step', () => { + const { getByRole, container } = render( + , + ); + stub(container.firstChild, 'getBoundingClientRect').callsFake(() => ({ + width: 100, + height: 10, + bottom: 10, + left: 0, + })); + const thumb = getByRole('slider'); + + const event = fireBodyMouseEvent('touchstart', { + changedTouches: touchList([{ identifier: 1, pageX: 21, pageY: 0 }]), + }); + container.firstChild.dispatchEvent(event); + expect(thumb.getAttribute('aria-valuenow')).to.equal('20'); + + fireEvent.keyDown(thumb, { + key: 'ArrowUp', + }); + expect(thumb.getAttribute('aria-valuenow')).to.equal('30'); + + fireEvent.keyDown(thumb, { + key: 'ArrowDown', + }); + expect(thumb.getAttribute('aria-valuenow')).to.equal('20'); + }); + }); + + describe('prop: disabled', () => { + it('should render the disabled classes', () => { + const { container } = render(); + expect(container.firstChild).to.have.class(classes.disabled); + }); + }); + + describe('keyboard', () => { + it('should handle all the keys', () => { + const { getByRole } = render(); + const thumb = getByRole('slider'); + + fireEvent.keyDown(thumb, { + key: 'Home', + }); + expect(thumb.getAttribute('aria-valuenow')).to.equal('0'); + + fireEvent.keyDown(thumb, { + key: 'End', + }); + expect(thumb.getAttribute('aria-valuenow')).to.equal('100'); + + fireEvent.keyDown(thumb, { + key: 'PageDown', + }); + expect(thumb.getAttribute('aria-valuenow')).to.equal('90'); + + fireEvent.keyDown(thumb, { + key: 'Escape', + }); + expect(thumb.getAttribute('aria-valuenow')).to.equal('90'); + + fireEvent.keyDown(thumb, { + key: 'PageUp', + }); + expect(thumb.getAttribute('aria-valuenow')).to.equal('100'); + }); + + const moveLeftEvent = { + key: 'ArrowLeft', + }; + const moveRightEvent = { + key: 'ArrowRight', + }; + + it('should reach right edge value', () => { + const { getByRole } = render(); + const thumb = getByRole('slider'); + + fireEvent.keyDown(thumb, moveRightEvent); + expect(thumb.getAttribute('aria-valuenow')).to.equal('100'); + + fireEvent.keyDown(thumb, moveRightEvent); + expect(thumb.getAttribute('aria-valuenow')).to.equal('108'); + + fireEvent.keyDown(thumb, moveLeftEvent); + expect(thumb.getAttribute('aria-valuenow')).to.equal('100'); + + fireEvent.keyDown(thumb, moveLeftEvent); + expect(thumb.getAttribute('aria-valuenow')).to.equal('90'); + }); + + it('should reach left edge value', () => { + const { getByRole } = render(); + const thumb = getByRole('slider'); + + fireEvent.keyDown(thumb, moveLeftEvent); + expect(thumb.getAttribute('aria-valuenow')).to.equal('10'); + + fireEvent.keyDown(thumb, moveLeftEvent); + expect(thumb.getAttribute('aria-valuenow')).to.equal('6'); + + fireEvent.keyDown(thumb, moveRightEvent); + expect(thumb.getAttribute('aria-valuenow')).to.equal('20'); + + fireEvent.keyDown(thumb, moveRightEvent); + expect(thumb.getAttribute('aria-valuenow')).to.equal('30'); + }); + + it('should round value to step precision', () => { + const { getByRole } = render(); + const thumb = getByRole('slider'); + + fireEvent.keyDown(thumb, moveRightEvent); + expect(thumb.getAttribute('aria-valuenow')).to.equal('0.3'); + }); + + it('should not fail to round value to step precision when step is very small', () => { + const { getByRole } = render( + , + ); + const thumb = getByRole('slider'); + + fireEvent.keyDown(thumb, moveRightEvent); + expect(thumb.getAttribute('aria-valuenow')).to.equal('3e-8'); + }); + + it('should not fail to round value to step precision when step is very small and negative', () => { + const { getByRole } = render( + , + ); + const thumb = getByRole('slider'); + + fireEvent.keyDown(thumb, moveLeftEvent); + expect(thumb.getAttribute('aria-valuenow')).to.equal('-3e-8'); + }); + }); + + describe('prop: valueLabelDisplay', () => { + it('should always display the value label', () => { + const { getByRole, setProps } = render(); + const thumb = getByRole('slider'); + expect(thumb.textContent).to.equal('50'); + setProps({ + valueLabelDisplay: 'off', + }); + expect(thumb.textContent).to.equal(''); + }); + }); + + describe('markActive state', () => { + function getActives(container) { + return Array.from(container.querySelectorAll(`.${classes.markLabel}`)).map(node => + node.classList.contains(classes.markLabelActive), + ); + } + + it('sets the marks active that are `within` the value', () => { + const marks = [{ value: 5 }, { value: 10 }, { value: 15 }]; + + const { container: container1 } = render( + , + ); + expect(getActives(container1)).to.deep.equal([true, true, false]); + + const { container: container2 } = render( + , + ); + expect(getActives(container2)).to.deep.equal([false, true, false]); + }); + + it('uses closed intervals for the within check', () => { + const { container: container1 } = render( + , + ); + expect(getActives(container1)).to.deep.equal([true, true, true]); + + const { container: container2 } = render( + , + ); + expect(getActives(container2)).to.deep.equal([true, true, false]); + }); + }); + + it('should forward mouseDown', () => { + const handleMouseDown = spy(); + const { container } = render(); + fireEvent.mouseDown(container.firstChild); + expect(handleMouseDown.callCount).to.equal(1); + }); + + it('should handle RTL', () => { + const handleChange = spy(); + const { container, getByRole } = render( + + + , + ); + const thumb = getByRole('slider'); + expect(thumb.style.right).to.equal('30%'); + + stub(container.firstChild, 'getBoundingClientRect').callsFake(() => ({ + width: 100, + height: 10, + bottom: 10, + left: 0, + })); + + const event = fireBodyMouseEvent('touchstart', { + changedTouches: touchList([{ identifier: 1, pageX: 20, pageY: 0 }]), + }); + container.firstChild.dispatchEvent(event); + + fireBodyMouseEvent('touchmove', { + changedTouches: touchList([{ identifier: 1, pageX: 22, pageY: 0 }]), + }); + + expect(handleChange.callCount).to.equal(2); + expect(handleChange.args[0][1]).to.equal(80); + expect(handleChange.args[1][1]).to.equal(78); + }); + + describe('warnings', () => { + beforeEach(() => { + consoleErrorMock.spy(); + }); + + afterEach(() => { + consoleErrorMock.reset(); + PropTypes.resetWarningCache(); + }); + + it('should warn if aria-valuetext is a string', () => { + render(); + expect(consoleErrorMock.args()[0][0]).to.include( + 'you need to use the `getAriaValueText` prop instead of', + ); + }); + }); +}); diff --git a/packages/material-ui-lab/src/Slider/ValueLabel.js b/packages/material-ui/src/Slider/ValueLabel.js similarity index 97% rename from packages/material-ui-lab/src/Slider/ValueLabel.js rename to packages/material-ui/src/Slider/ValueLabel.js index b98dc47c3e51ff..e8729a660a60f4 100644 --- a/packages/material-ui-lab/src/Slider/ValueLabel.js +++ b/packages/material-ui/src/Slider/ValueLabel.js @@ -1,6 +1,6 @@ import React from 'react'; -import { withStyles } from '@material-ui/core/styles'; import clsx from 'clsx'; +import withStyles from '../styles/withStyles'; const styles = theme => ({ thumb: { diff --git a/packages/material-ui-lab/src/Slider/index.d.ts b/packages/material-ui/src/Slider/index.d.ts similarity index 100% rename from packages/material-ui-lab/src/Slider/index.d.ts rename to packages/material-ui/src/Slider/index.d.ts diff --git a/packages/material-ui-lab/src/Slider/index.js b/packages/material-ui/src/Slider/index.js similarity index 100% rename from packages/material-ui-lab/src/Slider/index.js rename to packages/material-ui/src/Slider/index.js diff --git a/packages/material-ui/src/index.d.ts b/packages/material-ui/src/index.d.ts index 7d6eddf1797346..6ab85f2a3e587d 100644 --- a/packages/material-ui/src/index.d.ts +++ b/packages/material-ui/src/index.d.ts @@ -153,6 +153,7 @@ export { default as RadioGroup } from './RadioGroup'; export { default as RootRef } from './RootRef'; export { default as Select } from './Select'; export { default as Slide } from './Slide'; +export { default as Slider } from './Slider'; export { default as Snackbar } from './Snackbar'; export { default as SnackbarContent } from './SnackbarContent'; export { default as Step } from './Step'; diff --git a/packages/material-ui/src/index.js b/packages/material-ui/src/index.js index e4f4e8796fc885..141554757f3af7 100644 --- a/packages/material-ui/src/index.js +++ b/packages/material-ui/src/index.js @@ -94,6 +94,7 @@ export { default as RadioGroup } from './RadioGroup'; export { default as RootRef } from './RootRef'; export { default as Select } from './Select'; export { default as Slide } from './Slide'; +export { default as Slider } from './Slider'; export { default as Snackbar } from './Snackbar'; export { default as SnackbarContent } from './SnackbarContent'; export { default as Step } from './Step'; diff --git a/packages/material-ui/src/styles/overrides.d.ts b/packages/material-ui/src/styles/overrides.d.ts index 7f657016561b59..db9e47105aca68 100644 --- a/packages/material-ui/src/styles/overrides.d.ts +++ b/packages/material-ui/src/styles/overrides.d.ts @@ -16,21 +16,20 @@ import { CardClassKey } from '../Card'; import { CardContentClassKey } from '../CardContent'; import { CardHeaderClassKey } from '../CardHeader'; import { CardMediaClassKey } from '../CardMedia'; -import { TouchRippleClassKey } from '../ButtonBase/TouchRipple'; import { CheckboxClassKey } from '../Checkbox'; import { ChipClassKey } from '../Chip'; import { CircularProgressClassKey } from '../CircularProgress'; import { CollapseClassKey } from '../Collapse'; import { CssBaselineClassKey } from '../CssBaseline'; -import { DialogClassKey } from '../Dialog'; import { DialogActionsClassKey } from '../DialogActions'; +import { DialogClassKey } from '../Dialog'; import { DialogContentClassKey } from '../DialogContent'; import { DialogContentTextClassKey } from '../DialogContentText'; import { DialogTitleClassKey } from '../DialogTitle'; import { DividerClassKey } from '../Divider'; import { DrawerClassKey } from '../Drawer'; -import { ExpansionPanelClassKey } from '../ExpansionPanel'; import { ExpansionPanelActionsClassKey } from '../ExpansionPanelActions'; +import { ExpansionPanelClassKey } from '../ExpansionPanel'; import { ExpansionPanelDetailsClassKey } from '../ExpansionPanelDetails'; import { ExpansionPanelSummaryClassKey } from '../ExpansionPanelSummary'; import { FabClassKey } from '../Fab'; @@ -42,20 +41,19 @@ import { FormHelperTextClassKey } from '../FormHelperText'; import { FormLabelClassKey } from '../FormLabel'; import { GridClassKey } from '../Grid'; import { GridListClassKey } from '../GridList'; -import { GridListTileClassKey } from '../GridListTile'; import { GridListTileBarClassKey } from '../GridListTileBar'; -import { IconClassKey } from '../Icon'; +import { GridListTileClassKey } from '../GridListTile'; import { IconButtonClassKey } from '../IconButton'; -import { InputClassKey } from '../Input'; +import { IconClassKey } from '../Icon'; import { InputAdornmentClassKey } from '../InputAdornment'; import { InputBaseClassKey } from '../InputBase'; +import { InputClassKey } from '../Input'; import { InputLabelClassKey } from '../InputLabel'; -import { SwitchBaseClassKey } from '../internal/SwitchBase'; import { LinearProgressClassKey } from '../LinearProgress'; import { LinkClassKey } from '../Link'; import { ListClassKey } from '../List'; -import { ListItemClassKey } from '../ListItem'; import { ListItemAvatarClassKey } from '../ListItemAvatar'; +import { ListItemClassKey } from '../ListItem'; import { ListItemIconClassKey } from '../ListItemIcon'; import { ListItemSecondaryActionClassKey } from '../ListItemSecondaryAction'; import { ListItemTextClassKey } from '../ListItemText'; @@ -70,10 +68,11 @@ import { PaperClassKey } from '../Paper'; import { PopoverClassKey } from '../Popover'; import { RadioClassKey } from '../Radio'; import { SelectClassKey } from '../Select'; +import { SliderClassKey } from '../Slider'; import { SnackbarClassKey } from '../Snackbar'; import { SnackbarContentClassKey } from '../SnackbarContent'; -import { StepClasskey } from '../Step'; import { StepButtonClasskey } from '../StepButton'; +import { StepClasskey } from '../Step'; import { StepConnectorClasskey } from '../StepConnector'; import { StepContentClasskey } from '../StepContent'; import { StepIconClasskey } from '../StepIcon'; @@ -82,9 +81,9 @@ import { StepperClasskey } from '../Stepper'; import { SvgIconClassKey } from '../SvgIcon'; import { SwitchClassKey } from '../Switch'; import { TabClassKey } from '../Tab'; -import { TableClassKey } from '../Table'; import { TableBodyClassKey } from '../TableBody'; import { TableCellClassKey } from '../TableCell'; +import { TableClassKey } from '../Table'; import { TableFooterClassKey } from '../TableFooter'; import { TableHeadClassKey } from '../TableHead'; import { TablePaginationClassKey } from '../TablePagination'; @@ -94,6 +93,7 @@ import { TabsClassKey } from '../Tabs'; import { TextFieldClassKey } from '../TextField'; import { ToolbarClassKey } from '../Toolbar'; import { TooltipClassKey } from '../Tooltip'; +import { TouchRippleClassKey } from '../ButtonBase/TouchRipple'; import { TypographyClassKey } from '../Typography'; export type Overrides = { @@ -169,6 +169,7 @@ export interface ComponentNameToClassKey { MuiPopover: PopoverClassKey; MuiRadio: RadioClassKey; MuiSelect: SelectClassKey; + MuiSlider: SliderClassKey; MuiSnackbar: SnackbarClassKey; MuiSnackbarContent: SnackbarContentClassKey; MuiStep: StepClasskey; @@ -180,7 +181,6 @@ export interface ComponentNameToClassKey { MuiStepper: StepperClasskey; MuiSvgIcon: SvgIconClassKey; MuiSwitch: SwitchClassKey; - MuiSwitchBase: SwitchBaseClassKey; MuiTab: TabClassKey; MuiTable: TableClassKey; MuiTableBody: TableBodyClassKey; diff --git a/packages/material-ui/src/styles/props.d.ts b/packages/material-ui/src/styles/props.d.ts index 0058d0da3bb397..ec32b89c65fd2e 100644 --- a/packages/material-ui/src/styles/props.d.ts +++ b/packages/material-ui/src/styles/props.d.ts @@ -6,10 +6,10 @@ import { BottomNavigationActionProps } from '../BottomNavigationAction'; import { BottomNavigationProps } from '../BottomNavigation'; import { BreadcrumbsProps } from '../Breadcrumbs'; import { ButtonBaseProps } from '../ButtonBase'; -import { ButtonProps } from '../Button'; import { ButtonGroupProps } from '../ButtonGroup'; -import { CardActionsProps } from '../CardActions'; +import { ButtonProps } from '../Button'; import { CardActionAreaProps } from '../CardActionArea'; +import { CardActionsProps } from '../CardActions'; import { CardContentProps } from '../CardContent'; import { CardHeaderProps } from '../CardHeader'; import { CardMediaProps } from '../CardMedia'; @@ -64,6 +64,7 @@ import { PopoverProps } from '../Popover'; import { RadioGroupProps } from '../RadioGroup'; import { RadioProps } from '../Radio'; import { SelectProps } from '../Select'; +import { SliderProps } from '../Slider'; import { SnackbarContentProps } from '../SnackbarContent'; import { SnackbarProps } from '../Snackbar'; import { StepButtonProps } from '../StepButton'; @@ -74,7 +75,6 @@ import { StepLabelProps } from '../StepLabel'; import { StepperProps } from '../Stepper'; import { StepProps } from '../Step'; import { SvgIconProps } from '../SvgIcon'; -import { SwitchBaseProps } from '../internal/SwitchBase'; import { SwitchProps } from '../Switch'; import { TableBodyProps } from '../TableBody'; import { TableCellProps } from '../TableCell'; @@ -108,8 +108,8 @@ export interface ComponentsPropsList { MuiButtonBase: ButtonBaseProps; MuiButtonGroup: ButtonGroupProps; MuiCard: CardProps; - MuiCardActions: CardActionsProps; MuiCardActionArea: CardActionAreaProps; + MuiCardActions: CardActionsProps; MuiCardContent: CardContentProps; MuiCardHeader: CardHeaderProps; MuiCardMedia: CardMediaProps; @@ -163,6 +163,7 @@ export interface ComponentsPropsList { MuiRadio: RadioProps; MuiRadioGroup: RadioGroupProps; MuiSelect: SelectProps; + MuiSlider: SliderProps; MuiSnackbar: SnackbarProps; MuiSnackbarContent: SnackbarContentProps; MuiStep: StepProps; @@ -174,7 +175,6 @@ export interface ComponentsPropsList { MuiStepper: StepperProps; MuiSvgIcon: SvgIconProps; MuiSwitch: SwitchProps; - MuiSwitchBase: SwitchBaseProps; MuiTab: TabProps; MuiTable: TableProps; MuiTableBody: TableBodyProps; diff --git a/packages/material-ui/src/test-utils/createMount.js b/packages/material-ui/src/test-utils/createMount.js index 3a4c14c6d5d8df..e0de01a86e8ad3 100644 --- a/packages/material-ui/src/test-utils/createMount.js +++ b/packages/material-ui/src/test-utils/createMount.js @@ -59,7 +59,9 @@ export default function createMount(options = {}) { mountWithContext.attachTo = attachTo; mountWithContext.cleanUp = () => { ReactDOM.unmountComponentAtNode(attachTo); - attachTo.parentNode.removeChild(attachTo); + if (attachTo.parentNode) { + attachTo.parentNode.removeChild(attachTo); + } }; return mountWithContext; diff --git a/packages/material-ui/src/test-utils/describeConformance.js b/packages/material-ui/src/test-utils/describeConformance.js index 932fd7e3093595..c4b91f7d7e9b30 100644 --- a/packages/material-ui/src/test-utils/describeConformance.js +++ b/packages/material-ui/src/test-utils/describeConformance.js @@ -1,5 +1,6 @@ import { assert } from 'chai'; import React from 'react'; +import ReactDOM from 'react-dom'; import findOutermostIntrinsic from './findOutermostIntrinsic'; import testRef from './testRef'; @@ -176,6 +177,13 @@ const fullSuite = { export default function describeConformance(minimalElement, getOptions) { const { only = Object.keys(fullSuite), skip = [] } = getOptions(); describe('Material-UI component API', () => { + after(() => { + const { mount } = getOptions(); + if (mount.attachTo) { + ReactDOM.unmountComponentAtNode(mount.attachTo); + } + }); + Object.keys(fullSuite) .filter(testKey => only.indexOf(testKey) !== -1 && skip.indexOf(testKey) === -1) .forEach(testKey => { diff --git a/pages/api/slider.md b/pages/api/slider.md index d5ad85b9472bb3..4f28dd346f05c2 100644 --- a/pages/api/slider.md +++ b/pages/api/slider.md @@ -1,5 +1,5 @@ --- -filename: /packages/material-ui-lab/src/Slider/Slider.js +filename: /packages/material-ui/src/Slider/Slider.js --- @@ -9,7 +9,7 @@ filename: /packages/material-ui-lab/src/Slider/Slider.js

The API documentation of the Slider React component. Learn more about the properties and the CSS customization points.

```js -import Slider from '@material-ui/lab/Slider'; +import Slider from '@material-ui/core/Slider'; ``` @@ -68,7 +68,7 @@ This property accepts the following keys: | markLabelActive | Styles applied to the mark label element if active (depending on the value). Have a look at the [overriding styles with classes](/customization/components/#overriding-styles-with-classes) section -and the [implementation of the component](https://github.com/mui-org/material-ui/blob/master/packages/material-ui-lab/src/Slider/Slider.js) +and the [implementation of the component](https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Slider/Slider.js) for more detail. If using the `overrides` [key of the theme](/customization/themes/#css), @@ -76,7 +76,7 @@ you need to use the following style sheet name: `MuiSlider`. ## Notes -The component can cause issues in [StrictMode](https://reactjs.org/docs/strict-mode.html). +The component is fully [StrictMode](https://reactjs.org/docs/strict-mode.html) compatible. ## Demos diff --git a/scripts/sizeSnapshot/webpack.config.js b/scripts/sizeSnapshot/webpack.config.js index 1b27f516cc1f99..72bfc49e886dc6 100644 --- a/scripts/sizeSnapshot/webpack.config.js +++ b/scripts/sizeSnapshot/webpack.config.js @@ -100,7 +100,7 @@ async function getSizeLimitBundles() { // vs https://bundlephobia.com/result?p=rc-slider name: 'Slider', webpack: true, - path: 'packages/material-ui-lab/build/esm/Slider/index.js', + path: 'packages/material-ui/build/esm/Slider/index.js', }, { // vs https://bundlephobia.com/result?p=react-portal From b08f759541ed8de067c502edf72f200575de417e Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Mon, 1 Jul 2019 21:28:15 +0200 Subject: [PATCH 2/4] Sebastian review --- .../material-ui-lab/src/Slider/index.d.ts | 3 + packages/material-ui-lab/src/Slider/index.js | 16 +++++ packages/material-ui-lab/src/index.js | 1 + .../material-ui/src/Slider/Slider.test.js | 68 +++++++++++-------- 4 files changed, 60 insertions(+), 28 deletions(-) create mode 100644 packages/material-ui-lab/src/Slider/index.d.ts create mode 100644 packages/material-ui-lab/src/Slider/index.js diff --git a/packages/material-ui-lab/src/Slider/index.d.ts b/packages/material-ui-lab/src/Slider/index.d.ts new file mode 100644 index 00000000000000..9e13d71f7708a4 --- /dev/null +++ b/packages/material-ui-lab/src/Slider/index.d.ts @@ -0,0 +1,3 @@ +import { Slider } from '@material-ui/core'; + +export default Slider; diff --git a/packages/material-ui-lab/src/Slider/index.js b/packages/material-ui-lab/src/Slider/index.js new file mode 100644 index 00000000000000..6bd4faa20ed154 --- /dev/null +++ b/packages/material-ui-lab/src/Slider/index.js @@ -0,0 +1,16 @@ +import React from 'react'; +import warning from 'warning'; +import Slider from '@material-ui/core/Slider'; + +export default React.forwardRef(function DeprecatedSlider(props, ref) { + warning( + false, + [ + 'Material-UI: the Slider component was moved from the lab to the core.', + "You should use `import { Slider } from '@material-ui/core'`", + "or `import Slider from '@material-ui/core/Slider'`", + ].join('\n'), + ); + + return ; +}); diff --git a/packages/material-ui-lab/src/index.js b/packages/material-ui-lab/src/index.js index 31fe668c93d257..2e6be5847f9d2f 100644 --- a/packages/material-ui-lab/src/index.js +++ b/packages/material-ui-lab/src/index.js @@ -1,3 +1,4 @@ +export { default as Slider } from './Slider'; export { default as SpeedDial } from './SpeedDial'; export { default as SpeedDialAction } from './SpeedDialAction'; export { default as SpeedDialIcon } from './SpeedDialIcon'; diff --git a/packages/material-ui/src/Slider/Slider.test.js b/packages/material-ui/src/Slider/Slider.test.js index ab29e44e50361c..dac213d549c321 100644 --- a/packages/material-ui/src/Slider/Slider.test.js +++ b/packages/material-ui/src/Slider/Slider.test.js @@ -57,12 +57,13 @@ describe('', () => { ); fireEvent.mouseDown(container.firstChild); - document.body.dispatchEvent(new window.MouseEvent('mouseup')); + fireEvent.mouseUp(document.body); expect(handleChange.callCount).to.equal(1); expect(handleChangeCommitted.callCount).to.equal(1); - fireEvent.keyDown(getByRole('slider'), { + getByRole('slider').focus(); + fireEvent.keyDown(document.activeElement, { key: 'Home', }); expect(handleChange.callCount).to.equal(2); @@ -141,9 +142,10 @@ describe('', () => { }); describe('prop: orientation', () => { - it('should render with the default and vertical classes', () => { - const { container } = render(); + it('should render with the vertical classes', () => { + const { container, getByRole } = render(); expect(container.firstChild).to.have.class(classes.vertical); + expect(getByRole('slider').getAttribute('aria-orientation')).to.equal('vertical'); }); it('should report the right position', () => { @@ -175,16 +177,18 @@ describe('', () => { describe('range', () => { it('should support keyboard', () => { - const { container } = render(); - const thumb1 = container.querySelectorAll('[role="slider"]')[0]; - const thumb2 = container.querySelectorAll('[role="slider"]')[1]; + const { getAllByRole } = render(); + const thumb1 = getAllByRole('slider')[0]; + const thumb2 = getAllByRole('slider')[1]; - fireEvent.keyDown(thumb1, { + thumb1.focus(); + fireEvent.keyDown(document.activeElement, { key: 'ArrowRight', }); expect(thumb1.getAttribute('aria-valuenow')).to.equal('21'); - fireEvent.keyDown(thumb2, { + thumb2.focus(); + fireEvent.keyDown(document.activeElement, { key: 'ArrowLeft', }); expect(thumb2.getAttribute('aria-valuenow')).to.equal('29'); @@ -242,12 +246,13 @@ describe('', () => { container.firstChild.dispatchEvent(event); expect(thumb.getAttribute('aria-valuenow')).to.equal('20'); - fireEvent.keyDown(thumb, { + thumb.focus(); + fireEvent.keyDown(document.activeElement, { key: 'ArrowUp', }); expect(thumb.getAttribute('aria-valuenow')).to.equal('30'); - fireEvent.keyDown(thumb, { + fireEvent.keyDown(document.activeElement, { key: 'ArrowDown', }); expect(thumb.getAttribute('aria-valuenow')).to.equal('20'); @@ -256,8 +261,9 @@ describe('', () => { describe('prop: disabled', () => { it('should render the disabled classes', () => { - const { container } = render(); + const { container, getByRole } = render(); expect(container.firstChild).to.have.class(classes.disabled); + expect(getByRole('slider').getAttribute('tabIndex')).to.equal(null); }); }); @@ -265,28 +271,29 @@ describe('', () => { it('should handle all the keys', () => { const { getByRole } = render(); const thumb = getByRole('slider'); + thumb.focus(); - fireEvent.keyDown(thumb, { + fireEvent.keyDown(document.activeElement, { key: 'Home', }); expect(thumb.getAttribute('aria-valuenow')).to.equal('0'); - fireEvent.keyDown(thumb, { + fireEvent.keyDown(document.activeElement, { key: 'End', }); expect(thumb.getAttribute('aria-valuenow')).to.equal('100'); - fireEvent.keyDown(thumb, { + fireEvent.keyDown(document.activeElement, { key: 'PageDown', }); expect(thumb.getAttribute('aria-valuenow')).to.equal('90'); - fireEvent.keyDown(thumb, { + fireEvent.keyDown(document.activeElement, { key: 'Escape', }); expect(thumb.getAttribute('aria-valuenow')).to.equal('90'); - fireEvent.keyDown(thumb, { + fireEvent.keyDown(document.activeElement, { key: 'PageUp', }); expect(thumb.getAttribute('aria-valuenow')).to.equal('100'); @@ -302,42 +309,45 @@ describe('', () => { it('should reach right edge value', () => { const { getByRole } = render(); const thumb = getByRole('slider'); + thumb.focus(); - fireEvent.keyDown(thumb, moveRightEvent); + fireEvent.keyDown(document.activeElement, moveRightEvent); expect(thumb.getAttribute('aria-valuenow')).to.equal('100'); - fireEvent.keyDown(thumb, moveRightEvent); + fireEvent.keyDown(document.activeElement, moveRightEvent); expect(thumb.getAttribute('aria-valuenow')).to.equal('108'); - fireEvent.keyDown(thumb, moveLeftEvent); + fireEvent.keyDown(document.activeElement, moveLeftEvent); expect(thumb.getAttribute('aria-valuenow')).to.equal('100'); - fireEvent.keyDown(thumb, moveLeftEvent); + fireEvent.keyDown(document.activeElement, moveLeftEvent); expect(thumb.getAttribute('aria-valuenow')).to.equal('90'); }); it('should reach left edge value', () => { const { getByRole } = render(); const thumb = getByRole('slider'); + thumb.focus(); - fireEvent.keyDown(thumb, moveLeftEvent); + fireEvent.keyDown(document.activeElement, moveLeftEvent); expect(thumb.getAttribute('aria-valuenow')).to.equal('10'); - fireEvent.keyDown(thumb, moveLeftEvent); + fireEvent.keyDown(document.activeElement, moveLeftEvent); expect(thumb.getAttribute('aria-valuenow')).to.equal('6'); - fireEvent.keyDown(thumb, moveRightEvent); + fireEvent.keyDown(document.activeElement, moveRightEvent); expect(thumb.getAttribute('aria-valuenow')).to.equal('20'); - fireEvent.keyDown(thumb, moveRightEvent); + fireEvent.keyDown(document.activeElement, moveRightEvent); expect(thumb.getAttribute('aria-valuenow')).to.equal('30'); }); it('should round value to step precision', () => { const { getByRole } = render(); const thumb = getByRole('slider'); + thumb.focus(); - fireEvent.keyDown(thumb, moveRightEvent); + fireEvent.keyDown(document.activeElement, moveRightEvent); expect(thumb.getAttribute('aria-valuenow')).to.equal('0.3'); }); @@ -346,8 +356,9 @@ describe('', () => { , ); const thumb = getByRole('slider'); + thumb.focus(); - fireEvent.keyDown(thumb, moveRightEvent); + fireEvent.keyDown(document.activeElement, moveRightEvent); expect(thumb.getAttribute('aria-valuenow')).to.equal('3e-8'); }); @@ -356,8 +367,9 @@ describe('', () => { , ); const thumb = getByRole('slider'); + thumb.focus(); - fireEvent.keyDown(thumb, moveLeftEvent); + fireEvent.keyDown(document.activeElement, moveLeftEvent); expect(thumb.getAttribute('aria-valuenow')).to.equal('-3e-8'); }); }); From 586005a51154af26568b3fa96639d0349e3e768e Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Wed, 3 Jul 2019 18:35:09 +0300 Subject: [PATCH 3/4] second iteration of the reviews --- packages/material-ui-lab/src/Slider/index.js | 3 + .../src/ButtonBase/ButtonBase.test.js | 2 +- packages/material-ui/src/Slider/Slider.js | 6 +- .../material-ui/src/Slider/Slider.test.js | 175 +++++++----------- .../material-ui/src/test-utils/createMount.js | 4 +- .../src/test-utils/describeConformance.js | 8 - test/utils/createDOM.js | 28 +++ 7 files changed, 98 insertions(+), 128 deletions(-) diff --git a/packages/material-ui-lab/src/Slider/index.js b/packages/material-ui-lab/src/Slider/index.js index 6bd4faa20ed154..2c4634b81fcfda 100644 --- a/packages/material-ui-lab/src/Slider/index.js +++ b/packages/material-ui-lab/src/Slider/index.js @@ -7,6 +7,9 @@ export default React.forwardRef(function DeprecatedSlider(props, ref) { false, [ 'Material-UI: the Slider component was moved from the lab to the core.', + '', + 'Yay, the component is stable! 🎉', + '', "You should use `import { Slider } from '@material-ui/core'`", "or `import Slider from '@material-ui/core/Slider'`", ].join('\n'), diff --git a/packages/material-ui/src/ButtonBase/ButtonBase.test.js b/packages/material-ui/src/ButtonBase/ButtonBase.test.js index 831e8f40d0ef7c..167aa5ab8f65b2 100644 --- a/packages/material-ui/src/ButtonBase/ButtonBase.test.js +++ b/packages/material-ui/src/ButtonBase/ButtonBase.test.js @@ -169,7 +169,7 @@ describe('', () => { // only run in supported browsers if (typeof Touch !== 'undefined') { - const touch = new Touch({ identifier: 0, target: button }); + const touch = new Touch({ identifier: 0, target: button, clientX: 0, clientY: 0 }); fireEvent.touchStart(button, { touches: [touch] }); expect(onTouchStart.callCount).to.equal(1); diff --git a/packages/material-ui/src/Slider/Slider.js b/packages/material-ui/src/Slider/Slider.js index 3ed73abc0188ed..f96acaf3ab1b06 100644 --- a/packages/material-ui/src/Slider/Slider.js +++ b/packages/material-ui/src/Slider/Slider.js @@ -47,8 +47,8 @@ function trackFinger(event, touchId) { const touch = event.changedTouches[i]; if (touch.identifier === touchId.current) { return { - x: event.changedTouches[i].pageX, - y: event.changedTouches[i].pageY, + x: touch.pageX, + y: touch.pageY, }; } } @@ -552,7 +552,7 @@ const Slider = React.forwardRef(function Slider(props, ref) { const handleTouchStart = useEventCallback(event => { // Workaround as Safari has partial support for touchAction: 'none'. event.preventDefault(); - const touch = event.changedTouches.item(0); + const touch = event.changedTouches[0]; if (touch != null) { // A number that uniquely identifies the current finger in the touch session. touchId.current = touch.identifier; diff --git a/packages/material-ui/src/Slider/Slider.test.js b/packages/material-ui/src/Slider/Slider.test.js index dac213d549c321..92fbc426fda04d 100644 --- a/packages/material-ui/src/Slider/Slider.test.js +++ b/packages/material-ui/src/Slider/Slider.test.js @@ -9,28 +9,29 @@ import { cleanup, createClientRender, fireEvent } from 'test/utils/createClientR import consoleErrorMock from 'test/utils/consoleErrorMock'; import Slider from './Slider'; -function touchList(touchArray) { - touchArray.item = idx => touchArray[idx]; - return touchArray; -} - -function fireBodyMouseEvent(name, properties = {}) { - const event = document.createEvent('MouseEvents'); - event.initEvent(name, true, true); - Object.keys(properties).forEach(key => { - event[key] = properties[key]; - }); - document.body.dispatchEvent(event); - return event; +function createTouches(touches) { + return { + changedTouches: touches.map( + touch => + new Touch({ + target: document.body, + ...touch, + }), + ), + }; } describe('', () => { + // Not support by IE 11 + if (typeof Touch === 'undefined') { + return; + } + let mount; - let render; let classes; + const render = createClientRender({ strict: true }); before(() => { - render = createClientRender({ strict: true }); classes = getClasses(); mount = createMount({ strict: true }); }); @@ -77,75 +78,32 @@ describe('', () => { , ); - const event = fireBodyMouseEvent('touchstart', { - changedTouches: touchList([{ identifier: 1 }]), - }); - container.firstChild.dispatchEvent(event); + fireEvent.touchStart(container.firstChild, createTouches([{ identifier: 1 }])); expect(handleChange.callCount).to.equal(1); expect(handleChangeCommitted.callCount).to.equal(0); - fireBodyMouseEvent('touchend', { - changedTouches: touchList([{ identifier: 2 }]), - }); + fireEvent.touchEnd(document.body, createTouches([{ identifier: 2 }])); expect(handleChange.callCount).to.equal(1); expect(handleChangeCommitted.callCount).to.equal(0); - fireBodyMouseEvent('touchmove', { - changedTouches: touchList([{ identifier: 1 }]), - }); + fireEvent.touchMove(document.body, createTouches([{ identifier: 1 }])); expect(handleChange.callCount).to.equal(2); expect(handleChangeCommitted.callCount).to.equal(0); - fireBodyMouseEvent('touchmove', { - changedTouches: touchList([{ identifier: 2 }]), - }); + fireEvent.touchMove(document.body, createTouches([{ identifier: 2 }])); expect(handleChange.callCount).to.equal(2); expect(handleChangeCommitted.callCount).to.equal(0); - fireBodyMouseEvent('touchend', { - changedTouches: touchList([{ identifier: 1 }]), - }); + fireEvent.touchEnd(document.body, createTouches([{ identifier: 1 }])); expect(handleChange.callCount).to.equal(2); expect(handleChangeCommitted.callCount).to.equal(1); }); - describe('when mouse reenters window', () => { - it('should update if mouse is still clicked', () => { - const handleChange = spy(); - const { container } = render(); - - fireEvent.mouseDown(container.firstChild); - document.body.dispatchEvent(new window.MouseEvent('mouseleave')); - const mouseEnter = new window.Event('mouseenter'); - mouseEnter.buttons = 1; - document.body.dispatchEvent(mouseEnter); - expect(handleChange.callCount).to.equal(1); - - document.body.dispatchEvent(new window.MouseEvent('mousemove')); - expect(handleChange.callCount).to.equal(2); - }); - - it('should not update if mouse is not clicked', () => { - const handleChange = spy(); - const { container } = render(); - - fireEvent.mouseDown(container.firstChild); - document.body.dispatchEvent(new window.MouseEvent('mouseleave')); - const mouseEnter = new window.Event('mouseenter'); - mouseEnter.buttons = 0; - document.body.dispatchEvent(mouseEnter); - expect(handleChange.callCount).to.equal(1); - - document.body.dispatchEvent(new window.MouseEvent('mousemove')); - expect(handleChange.callCount).to.equal(1); - }); - }); - describe('prop: orientation', () => { it('should render with the vertical classes', () => { const { container, getByRole } = render(); expect(container.firstChild).to.have.class(classes.vertical); - expect(getByRole('slider').getAttribute('aria-orientation')).to.equal('vertical'); + expect(getByRole('slider')).to.have.attribute('aria-orientation', 'vertical'); }); it('should report the right position', () => { @@ -160,14 +118,11 @@ describe('', () => { left: 0, })); - const event = fireBodyMouseEvent('touchstart', { - changedTouches: touchList([{ identifier: 1, pageX: 0, pageY: 20 }]), - }); - container.firstChild.dispatchEvent(event); - - fireBodyMouseEvent('touchmove', { - changedTouches: touchList([{ identifier: 1, pageX: 0, pageY: 22 }]), - }); + fireEvent.touchStart( + container.firstChild, + createTouches([{ identifier: 1, pageX: 0, pageY: 20 }]), + ); + fireEvent.touchMove(document.body, createTouches([{ identifier: 1, pageX: 0, pageY: 22 }])); expect(handleChange.callCount).to.equal(2); expect(handleChange.args[0][1]).to.equal(80); @@ -204,17 +159,13 @@ describe('', () => { left: 0, })); - const event = fireBodyMouseEvent('touchstart', { - changedTouches: touchList([{ identifier: 1, pageX: 21, pageY: 0 }]), - }); - container.firstChild.dispatchEvent(event); + fireEvent.touchStart( + container.firstChild, + createTouches([{ identifier: 1, pageX: 21, pageY: 0 }]), + ); - fireBodyMouseEvent('touchmove', { - changedTouches: touchList([{ identifier: 1, pageX: 22, pageY: 0 }]), - }); - fireBodyMouseEvent('touchmove', { - changedTouches: touchList([{ identifier: 1, pageX: 22, pageY: 0 }]), - }); + fireEvent.touchMove(document.body, createTouches([{ identifier: 1, pageX: 22, pageY: 0 }])); + fireEvent.touchMove(document.body, createTouches([{ identifier: 1, pageX: 22, pageY: 0 }])); expect(handleChange.callCount).to.equal(3); expect(handleChange.args[0][1]).to.deep.equal([21, 30]); @@ -240,22 +191,22 @@ describe('', () => { })); const thumb = getByRole('slider'); - const event = fireBodyMouseEvent('touchstart', { - changedTouches: touchList([{ identifier: 1, pageX: 21, pageY: 0 }]), - }); - container.firstChild.dispatchEvent(event); - expect(thumb.getAttribute('aria-valuenow')).to.equal('20'); + fireEvent.touchStart( + container.firstChild, + createTouches([{ identifier: 1, pageX: 21, pageY: 0 }]), + ); + expect(thumb).to.have.attribute('aria-valuenow', '20'); thumb.focus(); fireEvent.keyDown(document.activeElement, { key: 'ArrowUp', }); - expect(thumb.getAttribute('aria-valuenow')).to.equal('30'); + expect(thumb).to.have.attribute('aria-valuenow', '30'); fireEvent.keyDown(document.activeElement, { key: 'ArrowDown', }); - expect(thumb.getAttribute('aria-valuenow')).to.equal('20'); + expect(thumb).to.have.attribute('aria-valuenow', '20'); }); }); @@ -263,7 +214,7 @@ describe('', () => { it('should render the disabled classes', () => { const { container, getByRole } = render(); expect(container.firstChild).to.have.class(classes.disabled); - expect(getByRole('slider').getAttribute('tabIndex')).to.equal(null); + expect(getByRole('slider')).to.not.have.attribute('tabIndex'); }); }); @@ -276,27 +227,27 @@ describe('', () => { fireEvent.keyDown(document.activeElement, { key: 'Home', }); - expect(thumb.getAttribute('aria-valuenow')).to.equal('0'); + expect(thumb).to.have.attribute('aria-valuenow', '0'); fireEvent.keyDown(document.activeElement, { key: 'End', }); - expect(thumb.getAttribute('aria-valuenow')).to.equal('100'); + expect(thumb).to.have.attribute('aria-valuenow', '100'); fireEvent.keyDown(document.activeElement, { key: 'PageDown', }); - expect(thumb.getAttribute('aria-valuenow')).to.equal('90'); + expect(thumb).to.have.attribute('aria-valuenow', '90'); fireEvent.keyDown(document.activeElement, { key: 'Escape', }); - expect(thumb.getAttribute('aria-valuenow')).to.equal('90'); + expect(thumb).to.have.attribute('aria-valuenow', '90'); fireEvent.keyDown(document.activeElement, { key: 'PageUp', }); - expect(thumb.getAttribute('aria-valuenow')).to.equal('100'); + expect(thumb).to.have.attribute('aria-valuenow', '100'); }); const moveLeftEvent = { @@ -312,16 +263,16 @@ describe('', () => { thumb.focus(); fireEvent.keyDown(document.activeElement, moveRightEvent); - expect(thumb.getAttribute('aria-valuenow')).to.equal('100'); + expect(thumb).to.have.attribute('aria-valuenow', '100'); fireEvent.keyDown(document.activeElement, moveRightEvent); - expect(thumb.getAttribute('aria-valuenow')).to.equal('108'); + expect(thumb).to.have.attribute('aria-valuenow', '108'); fireEvent.keyDown(document.activeElement, moveLeftEvent); - expect(thumb.getAttribute('aria-valuenow')).to.equal('100'); + expect(thumb).to.have.attribute('aria-valuenow', '100'); fireEvent.keyDown(document.activeElement, moveLeftEvent); - expect(thumb.getAttribute('aria-valuenow')).to.equal('90'); + expect(thumb).to.have.attribute('aria-valuenow', '90'); }); it('should reach left edge value', () => { @@ -330,16 +281,16 @@ describe('', () => { thumb.focus(); fireEvent.keyDown(document.activeElement, moveLeftEvent); - expect(thumb.getAttribute('aria-valuenow')).to.equal('10'); + expect(thumb).to.have.attribute('aria-valuenow', '10'); fireEvent.keyDown(document.activeElement, moveLeftEvent); - expect(thumb.getAttribute('aria-valuenow')).to.equal('6'); + expect(thumb).to.have.attribute('aria-valuenow', '6'); fireEvent.keyDown(document.activeElement, moveRightEvent); - expect(thumb.getAttribute('aria-valuenow')).to.equal('20'); + expect(thumb).to.have.attribute('aria-valuenow', '20'); fireEvent.keyDown(document.activeElement, moveRightEvent); - expect(thumb.getAttribute('aria-valuenow')).to.equal('30'); + expect(thumb).to.have.attribute('aria-valuenow', '30'); }); it('should round value to step precision', () => { @@ -348,7 +299,7 @@ describe('', () => { thumb.focus(); fireEvent.keyDown(document.activeElement, moveRightEvent); - expect(thumb.getAttribute('aria-valuenow')).to.equal('0.3'); + expect(thumb).to.have.attribute('aria-valuenow', '0.3'); }); it('should not fail to round value to step precision when step is very small', () => { @@ -359,7 +310,7 @@ describe('', () => { thumb.focus(); fireEvent.keyDown(document.activeElement, moveRightEvent); - expect(thumb.getAttribute('aria-valuenow')).to.equal('3e-8'); + expect(thumb).to.have.attribute('aria-valuenow', '3e-8'); }); it('should not fail to round value to step precision when step is very small and negative', () => { @@ -370,7 +321,7 @@ describe('', () => { thumb.focus(); fireEvent.keyDown(document.activeElement, moveLeftEvent); - expect(thumb.getAttribute('aria-valuenow')).to.equal('-3e-8'); + expect(thumb).to.have.attribute('aria-valuenow', '-3e-8'); }); }); @@ -448,14 +399,12 @@ describe('', () => { left: 0, })); - const event = fireBodyMouseEvent('touchstart', { - changedTouches: touchList([{ identifier: 1, pageX: 20, pageY: 0 }]), - }); - container.firstChild.dispatchEvent(event); + fireEvent.touchStart( + container.firstChild, + createTouches([{ identifier: 1, pageX: 20, pageY: 0 }]), + ); - fireBodyMouseEvent('touchmove', { - changedTouches: touchList([{ identifier: 1, pageX: 22, pageY: 0 }]), - }); + fireEvent.touchMove(document.body, createTouches([{ identifier: 1, pageX: 22, pageY: 0 }])); expect(handleChange.callCount).to.equal(2); expect(handleChange.args[0][1]).to.equal(80); diff --git a/packages/material-ui/src/test-utils/createMount.js b/packages/material-ui/src/test-utils/createMount.js index e0de01a86e8ad3..3a4c14c6d5d8df 100644 --- a/packages/material-ui/src/test-utils/createMount.js +++ b/packages/material-ui/src/test-utils/createMount.js @@ -59,9 +59,7 @@ export default function createMount(options = {}) { mountWithContext.attachTo = attachTo; mountWithContext.cleanUp = () => { ReactDOM.unmountComponentAtNode(attachTo); - if (attachTo.parentNode) { - attachTo.parentNode.removeChild(attachTo); - } + attachTo.parentNode.removeChild(attachTo); }; return mountWithContext; diff --git a/packages/material-ui/src/test-utils/describeConformance.js b/packages/material-ui/src/test-utils/describeConformance.js index c4b91f7d7e9b30..932fd7e3093595 100644 --- a/packages/material-ui/src/test-utils/describeConformance.js +++ b/packages/material-ui/src/test-utils/describeConformance.js @@ -1,6 +1,5 @@ import { assert } from 'chai'; import React from 'react'; -import ReactDOM from 'react-dom'; import findOutermostIntrinsic from './findOutermostIntrinsic'; import testRef from './testRef'; @@ -177,13 +176,6 @@ const fullSuite = { export default function describeConformance(minimalElement, getOptions) { const { only = Object.keys(fullSuite), skip = [] } = getOptions(); describe('Material-UI component API', () => { - after(() => { - const { mount } = getOptions(); - if (mount.attachTo) { - ReactDOM.unmountComponentAtNode(mount.attachTo); - } - }); - Object.keys(fullSuite) .filter(testKey => only.indexOf(testKey) !== -1 && skip.indexOf(testKey) === -1) .forEach(testKey => { diff --git a/test/utils/createDOM.js b/test/utils/createDOM.js index 9ff1c90bbc5069..4c7710bc93ab92 100644 --- a/test/utils/createDOM.js +++ b/test/utils/createDOM.js @@ -19,6 +19,34 @@ function createDOM() { ownerDocument: document, }, }); + // Not yet supported: https://github.com/jsdom/jsdom/issues/2152 + class Touch { + constructor(instance) { + this.instance = instance; + } + + get identifier() { + return this.instance.identifier; + } + + get pageX() { + return this.instance.pageX; + } + + get pageY() { + return this.instance.pageY; + } + + get clientX() { + return this.instance.clientX; + } + + get clientY() { + return this.instance.clientY; + } + } + global.window.Touch = Touch; + global.navigator = { userAgent: 'node.js', }; From 605ea68598322350c624dbed6f9454db80c5e523 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Thu, 4 Jul 2019 11:27:53 +0300 Subject: [PATCH 4/4] add the mouse exit test back, but skipped --- .../material-ui/src/Slider/Slider.test.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/material-ui/src/Slider/Slider.test.js b/packages/material-ui/src/Slider/Slider.test.js index 92fbc426fda04d..926dadaa58f1ca 100644 --- a/packages/material-ui/src/Slider/Slider.test.js +++ b/packages/material-ui/src/Slider/Slider.test.js @@ -130,6 +130,39 @@ describe('', () => { }); }); + // TODO: use fireEvent for all the events. + // describe.skip('when mouse reenters window', () => { + // it('should update if mouse is still clicked', () => { + // const handleChange = spy(); + // const { container } = render(); + + // fireEvent.mouseDown(container.firstChild); + // document.body.dispatchEvent(new window.MouseEvent('mouseleave')); + // const mouseEnter = new window.Event('mouseenter'); + // mouseEnter.buttons = 1; + // document.body.dispatchEvent(mouseEnter); + // expect(handleChange.callCount).to.equal(1); + + // document.body.dispatchEvent(new window.MouseEvent('mousemove')); + // expect(handleChange.callCount).to.equal(2); + // }); + + // it('should not update if mouse is not clicked', () => { + // const handleChange = spy(); + // const { container } = render(); + + // fireEvent.mouseDown(container.firstChild); + // document.body.dispatchEvent(new window.MouseEvent('mouseleave')); + // const mouseEnter = new window.Event('mouseenter'); + // mouseEnter.buttons = 0; + // document.body.dispatchEvent(mouseEnter); + // expect(handleChange.callCount).to.equal(1); + + // document.body.dispatchEvent(new window.MouseEvent('mousemove')); + // expect(handleChange.callCount).to.equal(1); + // }); + // }); + describe('range', () => { it('should support keyboard', () => { const { getAllByRole } = render();