Skip to content

Commit

Permalink
fix: extra onChange when value out of range (#711)
Browse files Browse the repository at this point in the history
* fix extra onChange when value out of range

* improve test case
  • Loading branch information
afc163 authored Nov 10, 2020
1 parent cd8b6ac commit bcce99d
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 49 deletions.
15 changes: 6 additions & 9 deletions src/Range.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import classNames from 'classnames';
import shallowEqual from 'shallowequal';
import Track from './common/Track';
import createSlider from './common/createSlider';
import * as utils from './utils';
Expand Down Expand Up @@ -132,7 +131,9 @@ class Range extends React.Component<RangeProps, RangeState> {
}

static getDerivedStateFromProps(props, state) {
if (!('value' in props || 'min' in props || 'max' in props)) return null;
if (!('value' in props || 'min' in props || 'max' in props)) {
return null;
}

const value = props.value || state.bounds;
let nextBounds = value.map((v, i) =>
Expand Down Expand Up @@ -165,17 +166,13 @@ class Range extends React.Component<RangeProps, RangeState> {
}

componentDidUpdate(prevProps, prevState) {
if (!('value' in this.props || 'min' in this.props || 'max' in this.props)) {
const { onChange, value, min, max } = this.props;
if (!('min' in this.props || 'max' in this.props)) {
return;
}
if (
this.props.min === prevProps.min &&
this.props.max === prevProps.max &&
shallowEqual(this.props.value, prevProps.value)
) {
if (min === prevProps.min && max === prevProps.max) {
return;
}
const { onChange, value } = this.props;
const currentValue = value || prevState.bounds;
if (currentValue.some(v => utils.isValueOutOfRange(v, this.props))) {
const newValues = currentValue.map(v => utils.ensureValueInRange(v, this.props));
Expand Down
22 changes: 13 additions & 9 deletions src/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,23 @@ class Slider extends React.Component<SliderProps, SliderState> {

prevMovedHandleIndex: number;

componentDidUpdate(_: SliderProps, prevState: SliderState) {
if (!('value' in this.props || 'min' in this.props || 'max' in this.props)) {
componentDidUpdate(prevProps: SliderProps, prevState: SliderState) {
const { min, max, value, onChange } = this.props;
if (!('min' in this.props || 'max' in this.props)) {
return;
}
const { value, onChange } = this.props;
const theValue = value !== undefined ? value : prevState.value;
const nextValue = this.trimAlignValue(theValue, this.props);
if (nextValue !== prevState.value) {
// eslint-disable-next-line
this.setState({ value: nextValue });
if (utils.isValueOutOfRange(theValue, this.props)) {
onChange(nextValue);
}
if (nextValue === prevState.value) {
return;
}
// eslint-disable-next-line
this.setState({ value: nextValue });
if (
!(min === prevProps.min && max === prevProps.max) &&
utils.isValueOutOfRange(theValue, this.props)
) {
onChange(nextValue);
}
}

Expand Down
16 changes: 8 additions & 8 deletions src/common/createSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,19 +232,19 @@ export default function createSlider<
}

focus() {
if (!this.props.disabled) {
this.handlesRefs[0].focus();
if (this.props.disabled) {
return;
}
this.handlesRefs[0]?.focus();
}

blur() {
if (!this.props.disabled) {
Object.keys(this.handlesRefs).forEach(key => {
if (this.handlesRefs[key] && this.handlesRefs[key].blur) {
this.handlesRefs[key].blur();
}
});
if (this.props.disabled) {
return;
}
Object.keys(this.handlesRefs).forEach(key => {
this.handlesRefs[key]?.blur?.();
});
}

calcValue(offset: number) {
Expand Down
5 changes: 4 additions & 1 deletion tests/common/SliderTooltip.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import SliderTooltip from '../../src/common/SliderTooltip';

it('should keepAlign by calling forcePopupAlign', async () => {
Expand All @@ -16,6 +17,8 @@ it('should keepAlign by calling forcePopupAlign', async () => {
</SliderTooltip>,
);
ref.forcePopupAlign = jest.fn();
await new Promise(res => setTimeout(res, 200));
await act(async () => {
await new Promise(res => setTimeout(res, 200));
});
expect(ref.forcePopupAlign).toHaveBeenCalled();
});
101 changes: 79 additions & 22 deletions tests/common/createSlider.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,42 @@ describe('createSlider', () => {
expect(rangeOnChange).toHaveBeenLastCalledWith([10, 10]);
});

it('should not trigger onChange when no min and max', () => {
const sliderOnChange = jest.fn();
const sliderWrapper = mount(<Slider onChange={sliderOnChange} />);
sliderWrapper.setProps({ value: 100 });
expect(sliderOnChange).not.toHaveBeenCalled();

const rangeOnChange = jest.fn();
const rangeWrapper = mount(<Range onChange={rangeOnChange} />);
rangeWrapper.setProps({ value: [0, 100] });
expect(rangeOnChange).not.toHaveBeenCalled();
});

it('should not trigger onChange when value is out of range', () => {
const sliderOnChange = jest.fn();
const sliderWrapper = mount(<Slider value={9} max={10} onChange={sliderOnChange} />);
sliderWrapper.setProps({ value: 11 });
expect(sliderWrapper.state('value')).toBe(10);
expect(sliderOnChange).not.toHaveBeenCalled();

const rangeOnChange = jest.fn();
const rangeWrapper = mount(<Range max={10} onChange={rangeOnChange} />);
rangeWrapper.setProps({ value: [0, 100] });
expect(rangeWrapper.state('bounds')).toEqual([0, 10]);
expect(rangeOnChange).not.toHaveBeenCalled();
});

it('should not call onChange when value is the same', () => {
const handler = jest.fn();

const sliderWrapper = mount(<Slider onChange={handler}/>);
const sliderWrapper = mount(<Slider onChange={handler} />);
const sliderHandleWrapper = sliderWrapper.find('.rc-slider-handle').at(1);
sliderHandleWrapper.simulate('mousedown');
sliderHandleWrapper.simulate('mousemove');
sliderHandleWrapper.simulate('mouseup');

const rangeWrapper = mount(<Range onChange={handler}/>);
const rangeWrapper = mount(<Range onChange={handler} />);
const rangeHandleWrapper = rangeWrapper.find('.rc-slider-handle-1').at(1);
rangeHandleWrapper.simulate('mousedown');
rangeHandleWrapper.simulate('mousemove');
Expand Down Expand Up @@ -117,11 +143,15 @@ describe('createSlider', () => {
it('should set `dragOffset` to correct value when the left handle is clicked off-center', () => {
const wrapper = mount(<Slider />);
setWidth(wrapper.instance().sliderRef, 100);
const leftHandle = wrapper.find('.rc-slider-handle').at(1).instance();
const leftHandle = wrapper
.find('.rc-slider-handle')
.at(1)
.instance();
wrapper.simulate('mousedown', {
type: 'mousedown',
target: leftHandle,
pageX: 5, button: 0,
pageX: 5,
button: 0,
stopPropagation() {},
preventDefault() {},
});
Expand All @@ -131,19 +161,25 @@ describe('createSlider', () => {
it('should respect `dragOffset` while dragging the handle via MouseEvents', () => {
const wrapper = mount(<Slider />);
setWidth(wrapper.instance().sliderRef, 100);
const leftHandle = wrapper.find('.rc-slider-handle').at(1).instance();
const leftHandle = wrapper
.find('.rc-slider-handle')
.at(1)
.instance();
wrapper.simulate('mousedown', {
type: 'mousedown',
target: leftHandle,
pageX: 5, button: 0,
pageX: 5,
button: 0,
stopPropagation() {},
preventDefault() {},
});
expect(wrapper.instance().dragOffset).toBe(5);
wrapper.instance().onMouseMove({ // to propagation
wrapper.instance().onMouseMove({
// to propagation
type: 'mousemove',
target: leftHandle,
pageX: 14, button: 0,
pageX: 14,
button: 0,
stopPropagation() {},
preventDefault() {},
});
Expand All @@ -153,33 +189,40 @@ describe('createSlider', () => {
it('should not go to right direction when mouse go to the left', () => {
const wrapper = mount(<Slider />);
setWidth(wrapper.instance().sliderRef, 100);
const leftHandle = wrapper.find('.rc-slider-handle').at(1).instance();
const leftHandle = wrapper
.find('.rc-slider-handle')
.at(1)
.instance();
wrapper.simulate('mousedown', {
type: 'mousedown',
target: leftHandle,
pageX: 5, button: 0,
pageX: 5,
button: 0,
stopPropagation() {},
preventDefault() {},
});
expect(wrapper.instance().getValue()).toBe(0); // zero on start
wrapper.instance().onMouseMove({ // to propagation
wrapper.instance().onMouseMove({
// to propagation
type: 'mousemove',
target: leftHandle,
pageX: 0, button: 0,
pageX: 0,
button: 0,
stopPropagation() {},
preventDefault() {},
});
expect(wrapper.instance().getValue()).toBe(0); // still zero
});

it('should set `dragOffset` to 0 when the MouseEvent target isn\'t a handle', () => {
it("should set `dragOffset` to 0 when the MouseEvent target isn't a handle", () => {
const wrapper = mount(<Slider />);
setWidth(wrapper.instance().sliderRef, 100);
const sliderTrack = wrapper.find('.rc-slider-track').get(0);
wrapper.simulate('mousedown', {
type: 'mousedown',
target: sliderTrack,
pageX: 5, button: 0,
pageX: 5,
button: 0,
stopPropagation() {},
preventDefault() {},
});
Expand All @@ -189,7 +232,10 @@ describe('createSlider', () => {
it('should set `dragOffset` to correct value when the left handle is touched off-center', () => {
const wrapper = mount(<Slider />);
setWidth(wrapper.instance().sliderRef, 100);
const leftHandle = wrapper.find('.rc-slider-handle').at(1).instance();
const leftHandle = wrapper
.find('.rc-slider-handle')
.at(1)
.instance();
wrapper.simulate('touchstart', {
type: 'touchstart',
target: leftHandle,
Expand All @@ -203,7 +249,10 @@ describe('createSlider', () => {
it('should respect `dragOffset` while dragging the handle via TouchEvents', () => {
const wrapper = mount(<Slider />);
setWidth(wrapper.instance().sliderRef, 100);
const leftHandle = wrapper.find('.rc-slider-handle').at(1).instance();
const leftHandle = wrapper
.find('.rc-slider-handle')
.at(1)
.instance();
wrapper.simulate('touchstart', {
type: 'touchstart',
target: leftHandle,
Expand All @@ -212,7 +261,8 @@ describe('createSlider', () => {
preventDefault() {},
});
expect(wrapper.instance().dragOffset).toBe(5);
wrapper.instance().onTouchMove({ // to propagation
wrapper.instance().onTouchMove({
// to propagation
type: 'touchmove',
target: leftHandle,
touches: [{ pageX: 14 }],
Expand All @@ -222,7 +272,7 @@ describe('createSlider', () => {
expect(wrapper.instance().getValue()).toBe(9);
});

it('should set `dragOffset` to 0 when the TouchEvent target isn\'t a handle', () => {
it("should set `dragOffset` to 0 when the TouchEvent target isn't a handle", () => {
const wrapper = mount(<Slider />);
setWidth(wrapper.instance().sliderRef, 100);
const sliderTrack = wrapper.find('.rc-slider-track').get(0);
Expand All @@ -240,18 +290,22 @@ describe('createSlider', () => {
const labelId = 'to-be-clicked';
const marks = {
0: 'some other label',
100: <span id={labelId}>some label</span>
100: <span id={labelId}>some label</span>,
};

const sliderOnAfterChange = jest.fn();
const sliderWrapper = mount(<Slider value={0} marks={marks} onAfterChange={sliderOnAfterChange} />);
const sliderWrapper = mount(
<Slider value={0} marks={marks} onAfterChange={sliderOnAfterChange} />,
);
const sliderHandleWrapper = sliderWrapper.find(`#${labelId}`).at(0);
sliderHandleWrapper.simulate('mousedown');
sliderHandleWrapper.simulate('mouseup');
expect(sliderOnAfterChange).toHaveBeenCalled();

const rangeOnAfterChange = jest.fn();
const rangeWrapper = mount(<Range value={[0, 1]} marks={marks} onAfterChange={rangeOnAfterChange} />);
const rangeWrapper = mount(
<Range value={[0, 1]} marks={marks} onAfterChange={rangeOnAfterChange} />,
);
const rangeHandleWrapper = rangeWrapper.find(`#${labelId}`).at(0);
rangeHandleWrapper.simulate('mousedown');
rangeHandleWrapper.simulate('mouseup');
Expand All @@ -278,7 +332,10 @@ describe('createSlider', () => {
const tooltipParent = document.createElement('div');
tooltipParent.setAttribute('id', 'tooltip');
const wrapper = mount(
<SliderWithTooltip tipProps={tooltipPrefixer} getTooltipContainer={() => document.getElementById('tooltip')} />
<SliderWithTooltip
tipProps={tooltipPrefixer}
getTooltipContainer={() => document.getElementById('tooltip')}
/>,
);
expect(wrapper.instance().props.getTooltipContainer).toBeTruthy();
});
Expand Down

1 comment on commit bcce99d

@vercel
Copy link

@vercel vercel bot commented on bcce99d Nov 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.