diff --git a/packages/material-ui/src/Slider/Slider.js b/packages/material-ui/src/Slider/Slider.js
index 157bdaaabc12ff..22492e9f43520d 100644
--- a/packages/material-ui/src/Slider/Slider.js
+++ b/packages/material-ui/src/Slider/Slider.js
@@ -124,10 +124,6 @@ const axisProps = {
offset: percent => ({ bottom: `${percent}%` }),
leap: percent => ({ height: `${percent}%` }),
},
- 'vertical-reverse': {
- offset: percent => ({ top: `${percent}%` }),
- leap: percent => ({ height: `${percent}%` }),
- },
};
const defaultMarks = [];
diff --git a/packages/material-ui/src/Slider/Slider.test.js b/packages/material-ui/src/Slider/Slider.test.js
index 3c6add08dc57d0..501b3c8f4022d9 100644
--- a/packages/material-ui/src/Slider/Slider.test.js
+++ b/packages/material-ui/src/Slider/Slider.test.js
@@ -1,13 +1,12 @@
import React from 'react';
-import { spy } from 'sinon';
-import { assert } from 'chai';
-import {
- createMount,
- getClasses,
- findOutermostIntrinsic,
- wrapsIntrinsicElement,
-} from '@material-ui/core/test-utils';
+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) {
@@ -27,14 +26,17 @@ function fireBodyMouseEvent(name, properties = {}) {
describe('', () => {
let mount;
+ let render;
let classes;
before(() => {
+ render = createClientRender({ strict: true });
classes = getClasses();
- mount = createMount({ strict: false });
+ mount = createMount({ strict: true });
});
after(() => {
+ cleanup();
mount.cleanUp();
});
@@ -46,167 +48,177 @@ describe('', () => {
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(
+ const { container, getByRole } = render(
,
);
- wrapper.simulate('click');
- wrapper.simulate('mousedown');
- // document.simulate('mouseup')
+ fireEvent.mouseDown(container.firstChild);
document.body.dispatchEvent(new window.MouseEvent('mouseup'));
- assert.strictEqual(handleChange.callCount, 1);
- assert.strictEqual(handleChangeCommitted.callCount, 1);
+ expect(handleChange.callCount).to.equal(1);
+ expect(handleChangeCommitted.callCount).to.equal(1);
- assert.strictEqual(handleChange.args[0].length, 2);
- assert.strictEqual(handleChangeCommitted.args[0].length, 2);
+ 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 touches = [{ pageX: 0, pageY: 0 }];
- const wrapper = mount(
+ const { container } = render(
,
);
const event = fireBodyMouseEvent('touchstart', {
changedTouches: touchList([{ identifier: 1 }]),
- touches,
});
- wrapper.getDOMNode().dispatchEvent(event);
- assert.strictEqual(handleChange.callCount, 1);
- assert.strictEqual(handleChangeCommitted.callCount, 0);
+ container.firstChild.dispatchEvent(event);
+ expect(handleChange.callCount).to.equal(1);
+ expect(handleChangeCommitted.callCount).to.equal(0);
fireBodyMouseEvent('touchend', {
changedTouches: touchList([{ identifier: 2 }]),
- touches,
});
- assert.strictEqual(handleChange.callCount, 1);
- assert.strictEqual(handleChangeCommitted.callCount, 0);
+ expect(handleChange.callCount).to.equal(1);
+ expect(handleChangeCommitted.callCount).to.equal(0);
fireBodyMouseEvent('touchmove', {
changedTouches: touchList([{ identifier: 1 }]),
- touches,
});
- assert.strictEqual(handleChange.callCount, 2);
- assert.strictEqual(handleChangeCommitted.callCount, 0);
+ expect(handleChange.callCount).to.equal(2);
+ expect(handleChangeCommitted.callCount).to.equal(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);
});
+ 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();
- const wrapper = mount();
-
- wrapper.simulate('mousedown');
+ fireEvent.mouseDown(container.firstChild);
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'));
+ expect(handleChange.callCount).to.equal(1);
- assert.strictEqual(handleChange.callCount, 2);
+ 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();
- const wrapper = mount();
-
- wrapper.simulate('mousedown');
+ fireEvent.mouseDown(container.firstChild);
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'));
+ expect(handleChange.callCount).to.equal(1);
- assert.strictEqual(handleChange.callCount, 1);
+ document.body.dispatchEvent(new window.MouseEvent('mousemove'));
+ expect(handleChange.callCount).to.equal(1);
});
});
- describe('unmount', () => {
- it('should not have global event listeners registered after unmount', () => {
- const handleChange = spy();
- const handleChangeCommitted = spy();
-
- const wrapper = mount(
- ,
- );
+ describe('prop: orientation', () => {
+ it('should render with the default and vertical classes', () => {
+ const { container } = render();
+ expect(container.firstChild).to.have.class(classes.vertical);
+ });
+ });
- 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('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');
});
});
- 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: 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 touches = { pageX: 21, pageY: 0 };
+ const event = fireBodyMouseEvent('touchstart', {
+ changedTouches: touchList([{ identifier: 1, ...touches }]),
+ });
+ container.firstChild.dispatchEvent(event);
+ expect(thumb.getAttribute('aria-valuenow')).to.equal('20');
});
});
describe('prop: disabled', () => {
it('should render the disabled classes', () => {
- const wrapper = mount();
- assert.strictEqual(findOutermostIntrinsic(wrapper).hasClass(classes.disabled), true);
+ const { container } = render();
+ expect(container.firstChild).to.have.class(classes.disabled);
});
});
describe('keyboard', () => {
- let wrapper;
+ 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',
@@ -215,99 +227,151 @@ describe('', () => {
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);
+ const { getByRole } = render();
+ const thumb = getByRole('slider');
- thumb.simulate('keydown', moveRightEvent);
- assert.strictEqual(wrapper.props().value, 100);
+ fireEvent.keyDown(thumb, moveRightEvent);
+ expect(thumb.getAttribute('aria-valuenow')).to.equal('100');
- thumb.simulate('keydown', moveRightEvent);
- assert.strictEqual(wrapper.props().value, 108);
+ fireEvent.keyDown(thumb, moveRightEvent);
+ expect(thumb.getAttribute('aria-valuenow')).to.equal('108');
- thumb.simulate('keydown', moveLeftEvent);
- assert.strictEqual(wrapper.props().value, 100);
+ fireEvent.keyDown(thumb, moveLeftEvent);
+ expect(thumb.getAttribute('aria-valuenow')).to.equal('100');
- thumb.simulate('keydown', moveLeftEvent);
- assert.strictEqual(wrapper.props().value, 90);
+ fireEvent.keyDown(thumb, moveLeftEvent);
+ expect(thumb.getAttribute('aria-valuenow')).to.equal('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);
+ const { getByRole } = render();
+ const thumb = getByRole('slider');
+
+ fireEvent.keyDown(thumb, moveLeftEvent);
+ expect(thumb.getAttribute('aria-valuenow')).to.equal('10');
- thumb.simulate('keydown', moveLeftEvent);
- assert.strictEqual(wrapper.props().value, 6);
+ fireEvent.keyDown(thumb, moveLeftEvent);
+ expect(thumb.getAttribute('aria-valuenow')).to.equal('6');
- thumb.simulate('keydown', moveRightEvent);
- assert.strictEqual(wrapper.props().value, 20);
+ fireEvent.keyDown(thumb, moveRightEvent);
+ expect(thumb.getAttribute('aria-valuenow')).to.equal('20');
- thumb.simulate('keydown', moveRightEvent);
- assert.strictEqual(wrapper.props().value, 30);
+ fireEvent.keyDown(thumb, moveRightEvent);
+ expect(thumb.getAttribute('aria-valuenow')).to.equal('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);
+ 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', () => {
- 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);
+ 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', () => {
- 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);
+ 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(wrapper) {
- return wrapper
- .find(`.${classes.markLabel}`)
- .map(node => node.hasClass(classes.markLabelActive));
+ 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 singleValueWrapper = mount(
+ const { container: container1 } = render(
,
);
- assert.deepEqual(getActives(singleValueWrapper), [true, true, false]);
+ expect(getActives(container1)).to.deep.equal([true, true, false]);
- const rangeValueWrapper = mount(
+ const { container: container2 } = render(
,
);
- assert.deepEqual(getActives(rangeValueWrapper), [false, true, false]);
+ expect(getActives(container2)).to.deep.equal([false, true, false]);
});
it('uses closed intervals for the within check', () => {
- const exactMarkWrapper = mount(
+ const { container: container1 } = render(
,
);
- assert.deepEqual(getActives(exactMarkWrapper), [true, true, true]);
+ expect(getActives(container1)).to.deep.equal([true, true, true]);
- const ofByOneWrapper = mount(
+ const { container: container2 } = render(
,
);
- assert.deepEqual(getActives(ofByOneWrapper), [true, true, false]);
+ 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 { getByRole } = render(
+
+
+ ,
+ );
+ const thumb = getByRole('slider');
+ expect(thumb.style.right).to.equal('30%');
+ });
+
+ 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/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 590969bcb57d15..4f28dd346f05c2 100644
--- a/pages/api/slider.md
+++ b/pages/api/slider.md
@@ -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