diff --git a/package/__test__/RNCSliderNativeComponent.web.test.tsx b/package/__test__/RNCSliderNativeComponent.web.test.tsx new file mode 100644 index 00000000..272de863 --- /dev/null +++ b/package/__test__/RNCSliderNativeComponent.web.test.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import {fireEvent, render} from '@testing-library/react-native'; +import RCTSliderWebComponent from '../src/RNCSliderNativeComponent.web'; + +// Only the props relevant to the tests are passed +const WebSlider = RCTSliderWebComponent as React.ComponentType; + +describe('RCTSliderWebComponent', () => { + beforeAll(() => { + // The web component subscribes to DOM events, which the + // react-native test environment doesn't provide + const listeners = { + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + }; + Object.assign(global, listeners, {document: listeners}); + }); + + // Without a layout the track is 1px wide and starts at x = 0, + // so `pageX` is the fraction of the track left of the touch + const releaseAt = (props: object, pageX: number) => { + const onSlidingComplete = jest.fn(); + const {getByTestId} = render( + , + ); + fireEvent(getByTestId('slider'), 'responderRelease', { + nativeEvent: {pageX}, + }); + return onSlidingComplete.mock.calls[0][0].nativeEvent.value; + }; + + // Accessibility actions don't report a value change, so the value is + // read from the sliding start event that the next touch emits + const valueAfterAction = (props: object, actionName: string) => { + const onSlidingStart = jest.fn(); + const {getByTestId} = render( + , + ); + fireEvent(getByTestId('slider'), 'accessibilityAction', { + nativeEvent: {actionName}, + }); + fireEvent(getByTestId('slider'), 'responderGrant'); + return onSlidingStart.mock.calls[0][0].nativeEvent.value; + }; + + it('Counts steps from the minimum value', () => { + const props = { + minimumValue: 1, + maximumValue: 10, + lowerLimit: 1, + upperLimit: 10, + step: 2, + }; + // 1 + 9 * 0.25 = 3.25, the nearest step is 3 + expect(releaseAt(props, 0.25)).toBe(3); + // 1 + 9 * 0.5 = 5.5, the nearest step is 5 + expect(releaseAt(props, 0.5)).toBe(5); + }); + + it('Counts steps from the minimum value when inverted', () => { + const props = { + minimumValue: 1, + maximumValue: 10, + lowerLimit: 1, + upperLimit: 10, + step: 2, + inverted: true, + }; + // 10 - 9 * 0.75 = 3.25, the nearest step is 3 + expect(releaseAt(props, 0.75)).toBe(3); + }); + + it('Counts accessibility action steps from the minimum value', () => { + const props = { + value: 5, + minimumValue: 1, + maximumValue: 13, + lowerLimit: 1, + upperLimit: 13, + step: 3, + }; + // 5 + 3 = 8, the nearest step is 7 + expect(valueAfterAction(props, 'increment')).toBe(7); + // 5 - 3 = 2, the nearest step is 1 + expect(valueAfterAction(props, 'decrement')).toBe(1); + }); +}); diff --git a/package/src/RNCSliderNativeComponent.web.tsx b/package/src/RNCSliderNativeComponent.web.tsx index e11e1853..19c73cb2 100644 --- a/package/src/RNCSliderNativeComponent.web.tsx +++ b/package/src/RNCSliderNativeComponent.web.tsx @@ -291,7 +291,7 @@ const RCTSliderWebComponent = React.forwardRef( : minimumValue + ((maximumValue - minimumValue) * x) / width; const valueAfterStep = step - ? Math.round(newValue / step) * step + ? minimumValue + Math.round((newValue - minimumValue) / step) * step : newValue; const valueAfterLowerLimit = valueAfterStep < lowerLimit ? lowerLimit : valueAfterStep; @@ -317,10 +317,20 @@ const RCTSliderWebComponent = React.forwardRef( const tenth = (maximumValue - minimumValue) / 10; switch (event.nativeEvent.actionName) { case 'increment': - updateValue(value + (step || tenth)); + updateValue( + step + ? Math.round((value + step - minimumValue) / step) * step + + minimumValue + : value + tenth, + ); break; case 'decrement': - updateValue(value - (step || tenth)); + updateValue( + step + ? Math.round((value - step - minimumValue) / step) * step + + minimumValue + : value - tenth, + ); break; } };