diff --git a/package/__test__/SliderNativeComponent.web.test.tsx b/package/__test__/SliderNativeComponent.web.test.tsx new file mode 100644 index 00000000..566857da --- /dev/null +++ b/package/__test__/SliderNativeComponent.web.test.tsx @@ -0,0 +1,95 @@ +import React from "react"; +import {fireEvent, render} from "@testing-library/react-native"; +import RCTSliderWebComponent from "../src/SliderNativeComponent.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 = async (props: object, pageX: number) => { + const onSlidingComplete = jest.fn(); + const {getByTestId} = await render( + , + ); + await 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 = async (props: object, actionName: string) => { + const onSlidingStart = jest.fn(); + const {getByTestId} = await render( + , + ); + await fireEvent(getByTestId("slider"), "accessibilityAction", { + nativeEvent: {actionName}, + }); + await fireEvent(getByTestId("slider"), "responderGrant"); + return onSlidingStart.mock.calls[0][0].nativeEvent.value; + }; + + it("Counts steps from the minimum value", async () => { + const props = { + minimumValue: 1, + maximumValue: 10, + lowerLimit: 1, + upperLimit: 10, + step: 2, + }; + // 1 + 9 * 0.25 = 3.25, the nearest step is 3 + expect(await releaseAt(props, 0.25)).toBe(3); + // 1 + 9 * 0.5 = 5.5, the nearest step is 5 + expect(await releaseAt(props, 0.5)).toBe(5); + }); + + it("Counts steps from the minimum value when inverted", async () => { + 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(await releaseAt(props, 0.75)).toBe(3); + }); + + it("Counts accessibility action steps from the minimum value", async () => { + const props = { + value: 5, + minimumValue: 1, + maximumValue: 13, + lowerLimit: 1, + upperLimit: 13, + step: 3, + }; + // 5 + 3 = 8, the nearest step is 7 + expect(await valueAfterAction(props, "increment")).toBe(7); + // 5 - 3 = 2, the nearest step is 1 + expect(await valueAfterAction(props, "decrement")).toBe(1); + }); +}); diff --git a/package/src/SliderNativeComponent.web.tsx b/package/src/SliderNativeComponent.web.tsx index e406cc97..afb71358 100644 --- a/package/src/SliderNativeComponent.web.tsx +++ b/package/src/SliderNativeComponent.web.tsx @@ -281,7 +281,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; @@ -307,10 +307,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; } };