diff --git a/.github/ISSUE_TEMPLATE/pull-reqeust.md b/.github/ISSUE_TEMPLATE/pull-reqeust.md index b51ef5d..87cb97c 100644 --- a/.github/ISSUE_TEMPLATE/pull-reqeust.md +++ b/.github/ISSUE_TEMPLATE/pull-reqeust.md @@ -18,3 +18,10 @@ assignees: '' ### Screenshots And/Or GIFs + +### Checks + +[] Added or updated tests +[] Added or updated type definitions (in alphabetical order) +[] Updated readme file +[] Tested manually with demo app or with an other apporach \ No newline at end of file diff --git a/LICENSE b/LICENSE index 5e33223..9112f38 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Uday Sravan Kumar Kamineni +Copyright (c) 2024 Uday Sravan Kumar Kamineni Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 1885487..0000000 --- a/docs/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-midnight \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 3a15860..0000000 --- a/docs/index.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - -

React Native Swipe Button Component

-
-
- -

npm install rn-swipe-button --save

-

import SwipeButton from 'rn-swipe-button';

- <SwipeButton /> -
-
-
-
-

NPM Package

- https://www.npmjs.com/package/rn-swipe-button -
-
-
-

Screenshots

- - - - - - - - - - - -

Android

iOS

iOS gif

-
-
-
-

Component properties

-
-    containerStyles: PropTypes.object,
-
-    disabled: PropTypes.bool,
-
-    disableResetOnTap: PropTypes.bool, // After swipe success, tapping on the button resets it. Set this prop to true to disable this behavior.
-
-    disabledRailBackgroundColor: PropTypes.string,
-
-    disabledThumbIconBackgroundColor: PropTypes.string,
-
-    disabledThumbIconBorderColor: PropTypes.string,
-
-    enableReverseSwipe: PropTypes.bool,
-
-    finishRemainingSwipeAnimationDuration: PropTypes.number,
-
-    forceCompleteSwipe: PropTypes.func, // RNSwipeButton will call this function by passing a "forceCompleteSwipe" function as an argument. Calling the returned function will force complete the swipe.
-
-    forceReset: PropTypes.func, // RNSwipeButton will call this function by passing a "reset" function as an argument. Calling "reset" will reset the swipe thumb.
-    
-    height: PropTypes.number,
-
-    onSwipeFail: PropTypes.func,
-
-    onSwipeStart: PropTypes.func,
-
-    onSwipeSuccess: PropTypes.func, // Returns a boolean to indicate the swipe completed with real gesture or forceCompleteSwipe was called
-
-    railBackgroundColor: PropTypes.string,
-
-    railBorderColor: PropTypes.string,
-
-    railFillBackgroundColor: PropTypes.string,
-
-    railFillBorderColor: PropTypes.string,
-
-    railStyles: PropTypes.object,
-
-    resetAfterSuccessAnimDelay: PropTypes.number,  This is delay before resetting the button after successful swipe When shouldResetAfterSuccess = true
-    
-    resetAfterSuccessAnimDuration: PropTypes.number,
-    
-    screenReaderEnabled: PropTypes.bool,
-    
-    shouldResetAfterSuccess: PropTypes.bool, // If set to true, buttun resets automatically after swipe success with default delay of 1000ms
-
-    swipeSuccessThreshold: PropTypes.number, // Ex: 70. Swipping 70% will be considered as successful swipe
-
-    thumbIconBackgroundColor: PropTypes.string,
-
-    thumbIconBorderColor: PropTypes.string,
-
-    thumbIconComponent: PropTypes.node, Pass any react component to replace swipable thumb icon
-
-    thumbIconImageSource: PropTypes.oneOfType([
-      PropTypes.string,
-      PropTypes.number,
-    ]),
-
-    thumbIconStyles: PropTypes.object,
-
-    thumbIconWidth: PropTypes.number,
-
-    title: PropTypes.string,
-
-    titleColor: PropTypes.string,
-
-    titleFontSize: PropTypes.number,
-
-    titleMaxFontScale: PropTypes.number, // Ex: 2. will limit font size increasing to 200% when user increases font size in device properties
-    
-    titleMaxLines: PropTypes.number, // Use other title related props for additional UI customization
-
-    titleStyles: PropTypes.object,
-
-    width: PropTypes.number,
-
-    
-
- -

-

Note:

- -

-
- - diff --git a/package-lock.json b/package-lock.json index a9dfb76..08f7cba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rn-swipe-button", - "version": "2.1.0", + "version": "3.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rn-swipe-button", - "version": "2.1.0", + "version": "3.0.0", "license": "MIT", "devDependencies": { "@babel/core": "^7.26.0", @@ -21,7 +21,6 @@ "@testing-library/react-native": "^12.8.1", "@tsconfig/react-native": "^2.0.2", "@types/jest": "^29.5.14", - "@types/react-test-renderer": "^18.3.0", "babel-jest": "^29.7.0", "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.2", @@ -30,7 +29,6 @@ "jest": "^29.7.0", "metro-react-native-babel-preset": "^0.77.0", "pretty-quick": "^2.0.1", - "react-test-renderer": "^18.3.1", "stylelint-config-prettier": "^8.0.1", "stylelint-prettier": "^1.1.2", "ts-jest": "^29.2.5", @@ -3277,27 +3275,20 @@ "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/@types/react": { "version": "18.3.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, - "node_modules/@types/react-test-renderer": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.3.0.tgz", - "integrity": "sha512-HW4MuEYxfDbOHQsVlY/XtOvNHftCVEPhJF2pQXXwcUiUF+Oyb0usgp48HSgpK5rt8m9KZb22yqOeZm+rrVG8gw==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -4587,7 +4578,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/data-view-buffer": { "version": "1.0.1", @@ -9605,6 +9597,7 @@ "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", "dev": true, + "peer": true, "dependencies": { "object-assign": "^4.1.1", "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" @@ -9618,6 +9611,7 @@ "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz", "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==", "dev": true, + "peer": true, "dependencies": { "react-is": "^18.3.1", "react-shallow-renderer": "^16.15.0", @@ -9632,6 +9626,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dev": true, + "peer": true, "dependencies": { "loose-envify": "^1.1.0" } diff --git a/package.json b/package.json index d6e2191..eb5637f 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,12 @@ "slide" ], "author": "Uday Sravan Kumar Kamineni", + "maintainers": [ + "UdaySravanK" + ], + "contributors": [ + "UdaySravanK" + ], "license": "MIT", "bugs": { "url": "https://github.com/UdaySravanK/RNSwipeButton/issues" @@ -52,7 +58,6 @@ "@testing-library/react-native": "^12.8.1", "@tsconfig/react-native": "^2.0.2", "@types/jest": "^29.5.14", - "@types/react-test-renderer": "^18.3.0", "babel-jest": "^29.7.0", "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.2", @@ -61,7 +66,6 @@ "jest": "^29.7.0", "metro-react-native-babel-preset": "^0.77.0", "pretty-quick": "^2.0.1", - "react-test-renderer": "^18.3.1", "stylelint-config-prettier": "^8.0.1", "stylelint-prettier": "^1.1.2", "ts-jest": "^29.2.5", diff --git a/src/components/SwipeButton/__tests__/SwipeButton.test.tsx b/src/components/SwipeButton/__tests__/SwipeButton.test.tsx index 91a015e..7c79846 100644 --- a/src/components/SwipeButton/__tests__/SwipeButton.test.tsx +++ b/src/components/SwipeButton/__tests__/SwipeButton.test.tsx @@ -1,5 +1,11 @@ import React from "react"; -import { render, screen, fireEvent } from "@testing-library/react-native"; +import { + render, + screen, + fireEvent, + waitFor, + act, +} from "@testing-library/react-native"; import SwipeButton from "../index"; import { expect } from "@jest/globals"; @@ -12,19 +18,34 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { it("should render correctly with default props", async () => { // Setup - render(); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); - + const { getByTestId } = render(); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); }); it("should render correctly with containerStyles prop", async () => { // Setup - render(); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + const { getByTestId } = render( + , + ); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); @@ -32,9 +53,16 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { it("should render with correct styles when disable", async () => { // Setup - render(); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + const { getByTestId } = render(); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); @@ -42,9 +70,16 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { it("should render correctly with custom height", async () => { // Setup - render(); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + const { getByTestId } = render(); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); @@ -52,9 +87,16 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { it("should render correctly with custom width", async () => { // Setup - render(); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + const { getByTestId } = render(); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); @@ -62,7 +104,7 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { it("should render correctly with custom rail background color and border color", async () => { // Setup - render( + const { getByTestId } = render( { railFillBorderColor="#0000FF" />, ); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); @@ -79,9 +128,18 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { it("should render correctly with custom rail styles", async () => { // Setup - render(); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + const { getByTestId } = render( + , + ); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); @@ -95,8 +153,18 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { thumbIconBorderColor="000FFF" />, ); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + const { getByTestId } = render( + , + ); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); @@ -104,7 +172,7 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { it("Thumb icon customm styles should not override important styles", async () => { // Setup - render( + const { getByTestId } = render( { }} />, ); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); @@ -125,9 +200,16 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { it("should apply thumbIconWidth", async () => { // Setup - render(); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + const { getByTestId } = render(); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); @@ -135,7 +217,7 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { it("should be able to change title styling", async () => { // Setup - render( + const { getByTestId } = render( { titleStyles={{ fontWeight: "bold" }} />, ); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); @@ -156,9 +245,18 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { const CustomComp = () => { return USK; }; - render(); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + const { getByTestId } = render( + , + ); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); @@ -166,9 +264,16 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { it("should render correctly with screen reader enabled", async () => { // Setup - render(); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + const { getByTestId } = render(); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); @@ -176,9 +281,36 @@ describe("Component: SwipeButton UI Rendering Tree & Props", () => { it("should render correctly with screen reader disabled", async () => { // Setup - render(); - const button = screen.getAllByTestId("SwipeButton")[0]; - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + const { getByTestId } = render(); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); + + // Assert + expect(screen.toJSON()).toMatchSnapshot(); + }); + + it("should render with custom title component", async () => { + // Setup + const CustomComp = () => { + return USK; + }; + const { getByTestId } = render(); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(screen.toJSON()).toMatchSnapshot(); diff --git a/src/components/SwipeButton/__tests__/__snapshots__/SwipeButton.test.tsx.snap b/src/components/SwipeButton/__tests__/__snapshots__/SwipeButton.test.tsx.snap index b9de80e..de889bb 100644 --- a/src/components/SwipeButton/__tests__/__snapshots__/SwipeButton.test.tsx.snap +++ b/src/components/SwipeButton/__tests__/__snapshots__/SwipeButton.test.tsx.snap @@ -923,7 +923,7 @@ exports[`Component: SwipeButton UI Rendering Tree & Props should render correctl "borderColor": "#00000000", "borderRadius": 50, "borderRightWidth": 0, - "borderWidth": 3, + "borderWidth": 20, "margin": 1, "width": 50, } @@ -941,8 +941,8 @@ exports[`Component: SwipeButton UI Rendering Tree & Props should render correctl "marginVertical": -3, }, { - "backgroundColor": "#0FF000", - "borderColor": "000FFF", + "backgroundColor": "#D27030", + "borderColor": "#3D797C", "height": 50, "overflow": "hidden", "width": 50, @@ -1675,3 +1675,116 @@ exports[`Component: SwipeButton UI Rendering Tree & Props should render with cus `; + +exports[`Component: SwipeButton UI Rendering Tree & Props should render with custom title component 1`] = ` + + + + USK + + + + + + +`; diff --git a/src/components/SwipeButton/__tests__/functionality.test.tsx b/src/components/SwipeButton/__tests__/functionality.test.tsx index be7a560..38a4747 100644 --- a/src/components/SwipeButton/__tests__/functionality.test.tsx +++ b/src/components/SwipeButton/__tests__/functionality.test.tsx @@ -1,4 +1,10 @@ -import { render, screen, fireEvent } from "@testing-library/react-native"; +import { + render, + screen, + fireEvent, + waitFor, + act, +} from "@testing-library/react-native"; import SwipeButton from "../"; import { expect } from "@jest/globals"; @@ -6,50 +12,79 @@ import React from "react"; import { AccessibilityInfo } from "react-native"; describe("Component: SwipeButton Functionality", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + afterEach(() => { jest.clearAllMocks(); + jest.clearAllTimers(); }); - it("moves the thumb icon when swiped", () => { + it("moves the thumb icon when swiped", async () => { + jest.useRealTimers(); const { getByTestId } = render(); - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); - - const thumb = getByTestId("SwipeThumb"); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + await act(async () => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); - fireEvent(thumb, "onPanResponderMove", { - nativeEvent: { touches: [{ clientX: 50 }] }, + let thumb; + await waitFor(() => { + thumb = getByTestId("SwipeThumb"); }); - expect(thumb).toHaveStyle({ width: 50 }); - }); + await act(async () => { + fireEvent(thumb, "onPanResponderMove", { + nativeEvent: { touches: [{ clientX: 50 }] }, + }); + }); + await waitFor(async () => { + expect(thumb).toHaveStyle({ width: 50 }); + }); + }, 10000); it("should call onSwipeSuccess when swipe completed with forceCompleteSwipe", async () => { // Setup const onSwipeSuccess = jest.fn(); let forceComplete; - render( + const { getByTestId } = render( (forceComplete = complete)} />, ); - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); - // Execute - forceComplete(); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); - // Assert - expect(onSwipeSuccess).toHaveBeenCalledTimes(1); + await act(async () => { + // Execute + forceComplete(); + + // Assert + expect(onSwipeSuccess).toHaveBeenCalledTimes(1); + }); }); it("should return forceReset callback", async () => { // Setup let forceReset; - render( + const { getByTestId } = render( (forceReset = reset)} @@ -57,138 +92,180 @@ describe("Component: SwipeButton Functionality", () => { ); // Execute - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + act(() => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + }); // Assert expect(forceReset).not.toBeNull(); }); - it("triggers onSwipeSuccess when swipe threshold is met", () => { + it("triggers onSwipeSuccess when swipe threshold is met", async () => { const onSwipeStart = jest.fn(); const onSwipeSuccess = jest.fn(); const onSwipeFail = jest.fn(); - const { getByTestId } = render( - , - ); + let getByTestId; + await waitFor(async () => { + getByTestId = render( + , + ).getByTestId; + }); // Simulate the onLayout event to set the layoutWidth - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "layout", { - nativeEvent: { - layout: { - width: 300, // Set a realistic width for the button - height: 50, + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + + await act(async () => { + fireEvent(button, "layout", { + nativeEvent: { + layout: { + width: 300, // Set a realistic width for the button + height: 50, + }, }, - }, + }); }); // Get the thumb element - const thumb = getByTestId("SwipeThumb"); - - // Simulate the start of the gesture - fireEvent(thumb, "responderGrant", { - nativeEvent: { - touches: [{ pageX: 0, pageY: 0 }], // Initial touch position - changedTouches: [], - target: thumb, - identifier: 1, - }, - touchHistory: { mostRecentTimeStamp: "2", touchBank: [] }, - }); - - // Simulate the movement during the gesture - fireEvent(thumb, "responderMove", { - touchHistory: { - mostRecentTimeStamp: "1", - touchBank: [ - { - touchActive: true, - currentTimeStamp: 1, - currentPageX: 200, - previousPageX: 0, - }, - ], - numberActiveTouches: 1, - indexOfSingleActiveTouch: 0, - }, + let thumb; + await waitFor(() => { + thumb = getByTestId("SwipeThumb"); + }); + + act(() => { + // Simulate the start of the gesture + fireEvent(thumb, "responderGrant", { + nativeEvent: { + touches: [{ pageX: 0, pageY: 0 }], // Initial touch position + changedTouches: [], + target: thumb, + identifier: 1, + }, + touchHistory: { mostRecentTimeStamp: "2", touchBank: [] }, + }); }); - // Simulate the end of the gesture - fireEvent(thumb, "responderRelease", { - touchHistory: { mostRecentTimeStamp: "1", touchBank: [] }, + await waitFor(() => { + // Simulate the movement during the gesture + fireEvent(thumb, "responderMove", { + touchHistory: { + mostRecentTimeStamp: "1", + touchBank: [ + { + touchActive: true, + currentTimeStamp: 1, + currentPageX: 200, + previousPageX: 0, + }, + ], + numberActiveTouches: 1, + indexOfSingleActiveTouch: 0, + }, + }); + }); + + await waitFor(() => { + // Simulate the end of the gesture + fireEvent(thumb, "responderRelease", { + touchHistory: { mostRecentTimeStamp: "1", touchBank: [] }, + }); + expect(onSwipeStart).toHaveBeenCalled(); + expect(onSwipeSuccess).toHaveBeenCalled(); + expect(onSwipeFail).not.toHaveBeenCalled(); }); - expect(onSwipeStart).toHaveBeenCalled(); - expect(onSwipeSuccess).toHaveBeenCalled(); - expect(onSwipeFail).not.toHaveBeenCalled(); }); - it("should trigger onSwipeFail when swipe threshold is not met", () => { + it("should trigger onSwipeFail when swipe threshold is not met", async () => { const onSwipeStart = jest.fn(); const onSwipeFail = jest.fn(); const onSwipeSuccess = jest.fn(); - const { getByTestId } = render( - , - ); + let getByTestId; + await waitFor(async () => { + getByTestId = render( + , + ).getByTestId; + }); // Simulate the onLayout event to set the layoutWidth - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "layout", { - nativeEvent: { - layout: { - width: 300, // Set a realistic width for the button - height: 50, + let button; + await waitFor(() => { + button = getByTestId("SwipeButton"); + }); + await act(async () => { + fireEvent(button, "layout", { + nativeEvent: { + layout: { + width: 300, // Set a realistic width for the button + height: 50, + }, }, - }, + }); }); // Get the thumb element - const thumb = getByTestId("SwipeThumb"); - - // Simulate the start of the gesture - fireEvent(thumb, "responderGrant", { - nativeEvent: { - touches: [{ pageX: 0, pageY: 0 }], // Initial touch position - changedTouches: [], - target: thumb, - identifier: 1, - }, - touchHistory: { mostRecentTimeStamp: "2", touchBank: [] }, - }); - - // Simulate the movement during the gesture - fireEvent(thumb, "responderMove", { - touchHistory: { - mostRecentTimeStamp: "1", - touchBank: [ - { - touchActive: true, - currentTimeStamp: 1, - currentPageX: 100, - previousPageX: 0, - }, - ], - numberActiveTouches: 1, - indexOfSingleActiveTouch: 0, - }, + let thumb; + await waitFor(() => { + thumb = getByTestId("SwipeThumb"); + }); + + act(() => { + // Simulate the start of the gesture + fireEvent(thumb, "responderGrant", { + nativeEvent: { + touches: [{ pageX: 0, pageY: 0 }], // Initial touch position + changedTouches: [], + target: thumb, + identifier: 1, + }, + touchHistory: { mostRecentTimeStamp: "2", touchBank: [] }, + }); }); - // Simulate the end of the gesture - fireEvent(thumb, "responderRelease", { - touchHistory: { mostRecentTimeStamp: "1", touchBank: [] }, + await waitFor(() => { + // Simulate the movement during the gesture + fireEvent(thumb, "responderMove", { + touchHistory: { + mostRecentTimeStamp: "1", + touchBank: [ + { + touchActive: true, + currentTimeStamp: 1, + currentPageX: 100, + previousPageX: 0, + }, + ], + numberActiveTouches: 1, + indexOfSingleActiveTouch: 0, + }, + }); }); - expect(onSwipeStart).toHaveBeenCalled(); - expect(onSwipeFail).toHaveBeenCalled(); - expect(onSwipeSuccess).not.toHaveBeenCalled(); + await waitFor(() => { + // Simulate the end of the gesture + fireEvent(thumb, "responderRelease", { + touchHistory: { mostRecentTimeStamp: "1", touchBank: [] }, + }); + + expect(onSwipeStart).toHaveBeenCalled(); + expect(onSwipeFail).toHaveBeenCalled(); + expect(onSwipeSuccess).not.toHaveBeenCalled(); + }); }); it("should not call onSwipeStart when disabled", async () => { @@ -205,116 +282,144 @@ describe("Component: SwipeButton Functionality", () => { ); // Simulate the onLayout event to set the layoutWidth - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "layout", { - nativeEvent: { - layout: { - width: 300, // Set a realistic width for the button - height: 50, + let button; + await waitFor(() => { + button = screen.getByTestId("SwipeButton"); + }); + await act(async () => { + fireEvent(button, "layout", { + nativeEvent: { + layout: { + width: 300, // Set a realistic width for the button + height: 50, + }, }, - }, + }); }); // Get the thumb element - const thumb = getByTestId("SwipeThumb"); - - // Simulate the start of the gesture - fireEvent(thumb, "responderGrant", { - nativeEvent: { - touches: [{ pageX: 0, pageY: 0 }], // Initial touch position - changedTouches: [], - target: thumb, - identifier: 1, - }, - touchHistory: { mostRecentTimeStamp: "2", touchBank: [] }, - }); - - // Simulate the movement during the gesture - fireEvent(thumb, "responderMove", { - touchHistory: { - mostRecentTimeStamp: "1", - touchBank: [ - { - touchActive: true, - currentTimeStamp: 1, - currentPageX: 100, - previousPageX: 0, - }, - ], - numberActiveTouches: 1, - indexOfSingleActiveTouch: 0, - }, + let thumb; + await waitFor(() => { + thumb = getByTestId("SwipeThumb"); }); - // Simulate the end of the gesture - fireEvent(thumb, "responderRelease", { - touchHistory: { mostRecentTimeStamp: "1", touchBank: [] }, - }); + act(() => { + // Simulate the start of the gesture + fireEvent(thumb, "responderGrant", { + nativeEvent: { + touches: [{ pageX: 0, pageY: 0 }], // Initial touch position + changedTouches: [], + target: thumb, + identifier: 1, + }, + touchHistory: { mostRecentTimeStamp: "2", touchBank: [] }, + }); + + // Simulate the movement during the gesture + fireEvent(thumb, "responderMove", { + touchHistory: { + mostRecentTimeStamp: "1", + touchBank: [ + { + touchActive: true, + currentTimeStamp: 1, + currentPageX: 100, + previousPageX: 0, + }, + ], + numberActiveTouches: 1, + indexOfSingleActiveTouch: 0, + }, + }); + + // Simulate the end of the gesture + fireEvent(thumb, "responderRelease", { + touchHistory: { mostRecentTimeStamp: "1", touchBank: [] }, + }); - expect(onSwipeStart).not.toHaveBeenCalled(); - expect(onSwipeFail).not.toHaveBeenCalled(); - expect(onSwipeSuccess).not.toHaveBeenCalled(); + expect(onSwipeStart).not.toHaveBeenCalled(); + expect(onSwipeFail).not.toHaveBeenCalled(); + expect(onSwipeSuccess).not.toHaveBeenCalled(); + }); }); - it("does not move the thumb icon when disabled", () => { + it("does not move the thumb icon when disabled", async () => { const { getByTestId } = render(); - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "layout", { - nativeEvent: { - layout: { - width: 300, // Set a realistic width for the button - height: 50, + let button; + await waitFor(async () => { + button = screen.getByTestId("SwipeButton"); + }); + await act(async () => { + fireEvent(button, "layout", { + nativeEvent: { + layout: { + width: 300, // Set a realistic width for the button + height: 50, + }, }, - }, + }); }); - - const thumb = getByTestId("SwipeThumb"); - - fireEvent(thumb, "onPanResponderMove", { - nativeEvent: { touches: [{ clientX: 50 }] }, + let thumb; + await waitFor(() => { + thumb = getByTestId("SwipeThumb"); + }); + act(() => { + fireEvent(thumb, "onPanResponderMove", { + nativeEvent: { touches: [{ clientX: 50 }] }, + }); + expect(thumb).toHaveStyle({ width: 50 }); // Should not change }); - expect(thumb).toHaveStyle({ width: 50 }); // Should not change }); - it("is accessible to screen readers", () => { + it("is accessible to screen readers", async () => { const { getByLabelText } = render(); - expect(getByLabelText("Swipe to submit")).toBeTruthy(); + await waitFor(async () => { + expect(getByLabelText("Swipe to submit")).toBeTruthy(); + }); }); - it("moves thumb icon in reverse direction when enableReverseSwipe is true", () => { + it("moves thumb icon in reverse direction when enableReverseSwipe is true", async () => { const { getByTestId } = render(); // Simulate the onLayout event to set the layoutWidth - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "layout", { - nativeEvent: { - layout: { - width: 300, // Set a realistic width for the button - height: 50, - }, - }, + let button; + await waitFor(async () => { + button = screen.getByTestId("SwipeButton"); }); - - // Get the thumb element - const thumb = getByTestId("SwipeThumb"); - - // Simulate the movement during the gesture - fireEvent(thumb, "responderMove", { - touchHistory: { - mostRecentTimeStamp: "1", - touchBank: [ - { - touchActive: true, - currentTimeStamp: 1, - currentPageX: -100, - previousPageX: 0, + await act(async () => { + fireEvent(button, "layout", { + nativeEvent: { + layout: { + width: 300, // Set a realistic width for the button + height: 50, }, - ], - numberActiveTouches: 1, - indexOfSingleActiveTouch: 0, - }, + }, + }); + }); + let thumb; + await waitFor(async () => { + // Get the thumb element + thumb = getByTestId("SwipeThumb"); }); + act(() => { + // Simulate the movement during the gesture + fireEvent(thumb, "responderMove", { + touchHistory: { + mostRecentTimeStamp: "1", + touchBank: [ + { + touchActive: true, + currentTimeStamp: 1, + currentPageX: -100, + previousPageX: 0, + }, + ], + numberActiveTouches: 1, + indexOfSingleActiveTouch: 0, + }, + }); - expect(thumb).toHaveStyle({ width: 150 }); + expect(thumb).toHaveStyle({ width: 50 }); + }); }); it("should call onSwipeSuccess upon a tap when screen reader enabled", async () => { @@ -328,14 +433,22 @@ describe("Component: SwipeButton Functionality", () => { screenReaderEnabled />, ); - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); - // Execute - fireEvent(button, "onPress"); + let button; + await waitFor(async () => { + button = screen.getByTestId("SwipeButton"); + }); + await act(async () => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); - // Assert - expect(onSwipeSuccess).toHaveBeenCalledTimes(1); + // Execute + fireEvent(button, "onPress"); + + // Assert + expect(onSwipeSuccess).toHaveBeenCalledTimes(1); + }); }); it("should call screen reader toggle on focus change", async () => { @@ -344,20 +457,30 @@ describe("Component: SwipeButton Functionality", () => { AccessibilityInfo.addEventListener = jest.fn(); // Mock the event listener render(); - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); - fireEvent(button, "onPress"); - expect(onSwipeSuccess).not.toHaveBeenCalledTimes(1); - - // Execute - AccessibilityInfo.isScreenReaderEnabled = jest.fn().mockResolvedValue(true); - fireEvent(button, "onFocus"); - - await new Promise((resolve) => setTimeout(resolve, 0)); // Allow the effect to run - fireEvent(button, "onPress"); - - // Assert - expect(onSwipeSuccess).toHaveBeenCalledTimes(1); + let button; + await waitFor(async () => { + button = screen.getByTestId("SwipeButton"); + }); + await act(async () => { + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + fireEvent(button, "onPress"); + expect(onSwipeSuccess).not.toHaveBeenCalledTimes(1); + + // Execute + AccessibilityInfo.isScreenReaderEnabled = jest + .fn() + .mockResolvedValue(true); + await waitFor(() => { + fireEvent(button, "onFocus"); + }); + // await new Promise((resolve) => setTimeout(resolve, 0)); // Allow the effect to run + fireEvent(button, "onPress"); + + // Assert + expect(onSwipeSuccess).toHaveBeenCalledTimes(1); + }); }); it("press should invoke on success callback when the screen reader enabled internally", async () => { @@ -366,13 +489,18 @@ describe("Component: SwipeButton Functionality", () => { AccessibilityInfo.isScreenReaderEnabled = jest.fn().mockResolvedValue(true); AccessibilityInfo.addEventListener = jest.fn(); // Mock the event listener render(); - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); - - await new Promise((resolve) => setTimeout(resolve, 0)); // Allow the effect to run - - fireEvent(button, "onPress"); - expect(onSwipeSuccess).toHaveBeenCalledTimes(1); + await waitFor(() => { + const button = screen.getByTestId("SwipeButton"); + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); + + // await new Promise((resolve) => setTimeout(resolve, 0)); // Allow the effect to run + act(() => { + fireEvent(button, "onPress"); + }); + expect(onSwipeSuccess).toHaveBeenCalledTimes(1); + }); }); it("screen reader internally should not override the prop value", async () => { @@ -386,13 +514,17 @@ describe("Component: SwipeButton Functionality", () => { screenReaderEnabled={false} />, ); - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + await waitFor(() => { + const button = screen.getByTestId("SwipeButton"); + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); - await new Promise((resolve) => setTimeout(resolve, 0)); // Allow the effect to run + // await new Promise((resolve) => setTimeout(resolve, 0)); // Allow the effect to run - fireEvent(button, "onPress"); - expect(onSwipeSuccess).not.toHaveBeenCalledTimes(1); + fireEvent(button, "onPress"); + expect(onSwipeSuccess).not.toHaveBeenCalledTimes(1); + }); }); it("press should not invoke on success callback when the screen reader enabled internally and button disabled", async () => { @@ -401,12 +533,16 @@ describe("Component: SwipeButton Functionality", () => { AccessibilityInfo.isScreenReaderEnabled = jest.fn().mockResolvedValue(true); AccessibilityInfo.addEventListener = jest.fn(); // Mock the event listener render(); - const button = screen.getByTestId("SwipeButton"); - fireEvent(button, "onLayout", { nativeEvent: { layout: { width: 100 } } }); + await waitFor(() => { + const button = screen.getByTestId("SwipeButton"); + fireEvent(button, "onLayout", { + nativeEvent: { layout: { width: 100 } }, + }); - await new Promise((resolve) => setTimeout(resolve, 0)); // Allow the effect to run + // await new Promise((resolve) => setTimeout(resolve, 0)); // Allow the effect to run - fireEvent(button, "onPress"); - expect(onSwipeSuccess).not.toHaveBeenCalledTimes(1); + fireEvent(button, "onPress"); + expect(onSwipeSuccess).not.toHaveBeenCalledTimes(1); + }); }); }); diff --git a/src/components/SwipeButton/index.tsx b/src/components/SwipeButton/index.tsx index 694bc51..4e714bf 100644 --- a/src/components/SwipeButton/index.tsx +++ b/src/components/SwipeButton/index.tsx @@ -67,9 +67,9 @@ interface SwipeButtonProps extends TouchableOpacityProps { thumbIconImageSource?: ImageSourcePropType; thumbIconStyles?: ViewStyle; thumbIconWidth?: number; - titleComponent?: () => ReactElement; title?: string; titleColor?: string; + titleComponent?: () => ReactElement; titleFontSize?: number; titleMaxFontScale?: number; titleMaxLines?: number; @@ -78,7 +78,9 @@ interface SwipeButtonProps extends TouchableOpacityProps { } /** - * - Height of the RNSwipeButton will be determines by the height of the inner ThumbIcon which we interact with to swipe. + * A swipe to submit button + * + * - Height of the RNSwipeButton will be determined by the height of the inner ThumbIcon which we interact with to swipe. * * @param {*} param0 * @returns @@ -113,9 +115,9 @@ const SwipeButton: React.FC = ({ thumbIconImageSource, thumbIconStyles = {}, thumbIconWidth, - titleComponent: TitleComponent, title = DEFAULT_TITLE, titleColor = TITLE_COLOR, + titleComponent: TitleComponent, titleFontSize = DEFAULT_TITLE_FONT_SIZE, titleMaxFontScale, titleMaxLines = DEFAULT_TITLE_MAX_LINES, @@ -238,25 +240,25 @@ const SwipeButton: React.FC = ({ {...rest} > {TitleComponent ? ( - - - + + + ) : ( - - {title} - + + {title} + )} {layoutWidth > 0 && ( ; thumbIconHeight?: number; thumbIconWidth?: number; - titleComponent?: () => ReactElement; title?: string; titleColor?: string; + /** + * Use a component as title + */ + titleComponent?: () => ReactElement; /** * Default value is 20 */