From 81d864d872d4667c09969b947fc553bf969b1e0c Mon Sep 17 00:00:00 2001 From: Maciej Pyrc Date: Sat, 22 Jun 2024 15:17:45 +0200 Subject: [PATCH] =?UTF-8?q?test:=20=F0=9F=92=8D=20prepared=20test=20utils?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pan-touch/pan-touch.base.spec.tsx | 7 +-- .../pan-touch/pan-touch.velocity.spec.tsx | 39 ++++++++++++++ .../features/pinch/pinch.callbacks.spec.tsx | 37 ++++++++++++++ .../features/zoom/zoom.callbacks.spec.tsx | 40 +++++++++++++++ __tests__/utils/render-app.tsx | 51 +++++++++++++------ package.json | 4 +- src/core/instance.core.ts | 17 ++++--- src/models/context.model.ts | 12 ++--- src/stories/docs/props.tsx | 6 +-- yarn.lock | 24 ++++----- 10 files changed, 187 insertions(+), 50 deletions(-) create mode 100644 __tests__/features/pan-touch/pan-touch.velocity.spec.tsx create mode 100644 __tests__/features/pinch/pinch.callbacks.spec.tsx create mode 100644 __tests__/features/zoom/zoom.callbacks.spec.tsx diff --git a/__tests__/features/pan-touch/pan-touch.base.spec.tsx b/__tests__/features/pan-touch/pan-touch.base.spec.tsx index 127aad1..659e078 100644 --- a/__tests__/features/pan-touch/pan-touch.base.spec.tsx +++ b/__tests__/features/pan-touch/pan-touch.base.spec.tsx @@ -13,10 +13,11 @@ describe("Pan Touch [Base]", () => { expect(content.style.transform).toBe("translate(0px, 0px) scale(1)"); }); it("should return to position with padding enabled", async () => { - const { content, touchPan, pinch } = renderApp({ - disablePadding: false, + const { content, touchPan } = renderApp({ + alignmentAnimation: { + disabled: false, + }, }); - pinch({ value: 1.2 }); expect(content.style.transform).toBe("translate(0px, 0px) scale(1)"); touchPan({ x: 100, y: 100 }); expect(content.style.transform).toBe("translate(100px, 100px) scale(1)"); diff --git a/__tests__/features/pan-touch/pan-touch.velocity.spec.tsx b/__tests__/features/pan-touch/pan-touch.velocity.spec.tsx new file mode 100644 index 0000000..0f7a5fc --- /dev/null +++ b/__tests__/features/pan-touch/pan-touch.velocity.spec.tsx @@ -0,0 +1,39 @@ +import { waitFor } from "@testing-library/react"; + +import { renderApp, sleep } from "../../utils"; + +describe("Pan Touch [Velocity]", () => { + describe("When panning to coords", () => { + it("should trigger velocity", async () => { + const { content, touchPan, pinch } = renderApp(); + pinch({ value: 1.5 }); + expect(content.style.transform).toBe("translate(0px, 0px) scale(1.5)"); + await sleep(10); + touchPan({ x: 20, y: 20 }); + expect(content.style.transform).toBe("translate(20px, 20px) scale(1.5)"); + }); + // it("should not trigger disabled velocity", async () => { + // const { content, touchPan, pinch } = renderApp({ + // disablePadding: false, + // }); + // pinch({ value: 1.2 }); + // expect(content.style.transform).toBe("translate(0px, 0px) scale(1)"); + // touchPan({ x: 100, y: 100 }); + // expect(content.style.transform).toBe("translate(100px, 100px) scale(1)"); + // await waitFor(() => { + // expect(content.style.transform).toBe("translate(0px, 0px) scale(1)"); + // }); + // }); + // it("should accelerate to certain point", async () => { + // const { content, touchPan, zoom } = renderApp(); + // expect(content.style.transform).toBe("translate(0px, 0px) scale(1)"); + // zoom({ value: 1.5 }); + // expect(content.style.transform).toBe("translate(0px, 0px) scale(1.5)"); + // touchPan({ x: 100, y: 100 }); + // await sleep(10); + // expect(content.style.transform).toBe( + // "translate(100px, 100px) scale(1.5)", + // ); + // }); + }); +}); diff --git a/__tests__/features/pinch/pinch.callbacks.spec.tsx b/__tests__/features/pinch/pinch.callbacks.spec.tsx new file mode 100644 index 0000000..2df9aaa --- /dev/null +++ b/__tests__/features/pinch/pinch.callbacks.spec.tsx @@ -0,0 +1,37 @@ +import { renderApp } from "../../utils"; + +describe("Pinch [Callbacks]", () => { + describe("When pinch zooming", () => { + it("should trigger onPinch callbacks", async () => { + const spy1 = jest.fn(); + const spy2 = jest.fn(); + const spy3 = jest.fn(); + const { content, pinch } = renderApp({ + onPinchStart: spy1, + onPinch: spy2, + onPinchStop: spy3, + }); + expect(content.style.transform).toBe("translate(0px, 0px) scale(1)"); + pinch({ value: 2 }); + expect(spy1).toBeCalledTimes(1); + expect(spy2).toBeCalled(); + expect(spy3).toBeCalledTimes(1); + }); + + it("should not trigger onZoom callbacks", async () => { + const spy1 = jest.fn(); + const spy2 = jest.fn(); + const spy3 = jest.fn(); + const { content, pinch } = renderApp({ + onZoomStart: spy1, + onZoom: spy2, + onZoomStop: spy3, + }); + expect(content.style.transform).toBe("translate(0px, 0px) scale(1)"); + pinch({ value: 2 }); + expect(spy1).toBeCalledTimes(0); + expect(spy2).toBeCalledTimes(0); + expect(spy3).toBeCalledTimes(0); + }); + }); +}); diff --git a/__tests__/features/zoom/zoom.callbacks.spec.tsx b/__tests__/features/zoom/zoom.callbacks.spec.tsx new file mode 100644 index 0000000..8b465ce --- /dev/null +++ b/__tests__/features/zoom/zoom.callbacks.spec.tsx @@ -0,0 +1,40 @@ +import { waitFor } from "@testing-library/dom"; +import { renderApp } from "../../utils"; + +describe("Zoom [Callbacks]", () => { + describe("When wheel zooming", () => { + it("should trigger onZoom callbacks", async () => { + const spy1 = jest.fn(); + const spy2 = jest.fn(); + const spy3 = jest.fn(); + const { content, zoom } = renderApp({ + onZoomStart: spy1, + onZoom: spy2, + onZoomStop: spy3, + }); + expect(content.style.transform).toBe("translate(0px, 0px) scale(1)"); + zoom({ value: 2 }); + await waitFor(() => { + expect(spy1).toBeCalledTimes(1); + expect(spy2).toBeCalled(); + expect(spy3).toBeCalledTimes(1); + }); + }); + + it("should not trigger onPinch callbacks", async () => { + const spy1 = jest.fn(); + const spy2 = jest.fn(); + const spy3 = jest.fn(); + const { content, zoom } = renderApp({ + onPinchStart: spy1, + onPinch: spy2, + onPinchStop: spy3, + }); + expect(content.style.transform).toBe("translate(0px, 0px) scale(1)"); + zoom({ value: 2 }); + expect(spy1).toBeCalledTimes(0); + expect(spy2).toBeCalledTimes(0); + expect(spy3).toBeCalledTimes(0); + }); + }); +}); diff --git a/__tests__/utils/render-app.tsx b/__tests__/utils/render-app.tsx index f2621a3..a73d968 100644 --- a/__tests__/utils/render-app.tsx +++ b/__tests__/utils/render-app.tsx @@ -24,6 +24,12 @@ interface RenderApp { pinch: (options: { value: number; center?: [number, number] }) => void; } +const waitForPreviousActionToEnd = () => { + // Synchronous await for 10ms to wait for the previous event to finish (pinching or touching) + const startTime = Date.now(); + while (Date.now() - startTime < 10) {} +}; + function getPinchTouches( content: HTMLElement, center: [number, number], @@ -95,7 +101,7 @@ export const renderApp = ({ {...{ contentHeight, contentWidth, wrapperHeight, wrapperWidth }} />, ); - // // controls buttons + // controls buttons const zoomInBtn = screen.getByTestId("zoom-in"); const zoomOutBtn = screen.getByTestId("zoom-out"); const resetBtn = screen.getByTestId("reset"); @@ -147,6 +153,8 @@ export const renderApp = ({ const { value, center = [0, 0] } = options; if (!ref.current) throw new Error("ref.current is null"); + waitForPreviousActionToEnd(); + const isZoomIn = ref.current.instance.state.scale < value; const from = isZoomIn ? 1 : 2; const step = 0.1; @@ -169,9 +177,9 @@ export const renderApp = ({ const scaleDifference = Math.abs( ref.current.instance.state.scale - value, ); - const isNearScale = scaleDifference < 0.5; + const isNearScale = scaleDifference < 0.05; - const newStep = isNearScale ? step / 2 : step; + const newStep = isNearScale ? step / 6 : step; pinchValue = pinchValue + newStep; touches = getPinchTouches(content, center, pinchValue, from); @@ -184,7 +192,9 @@ export const renderApp = ({ } } - fireEvent.touchEnd(content); + fireEvent.touchEnd(content, { + touches, + }); }; const pan: RenderApp["pan"] = ({ x, y }) => { @@ -196,17 +206,18 @@ export const renderApp = ({ }; const touchPan: RenderApp["touchPan"] = ({ x, y }) => { - const touches = [ - { - pageX: 0, - pageY: 0, - clientX: 0, - clientY: 0, - target: content, - }, - ]; + waitForPreviousActionToEnd(); + fireEvent.touchStart(content, { - touches, + touches: [ + { + pageX: 0, + pageY: 0, + clientX: 0, + clientY: 0, + target: content, + }, + ], }); fireEvent.touchMove(content, { touches: [ @@ -219,7 +230,17 @@ export const renderApp = ({ }, ], }); - fireEvent.touchEnd(content); + fireEvent.touchEnd(content, { + touches: [ + { + pageX: x, + pageY: y, + clientX: x, + clientY: y, + target: content, + }, + ], + }); }; return { diff --git a/package.json b/package.json index 79eba69..5bb6e3e 100644 --- a/package.json +++ b/package.json @@ -55,8 +55,8 @@ "@storybook/react": "7.6.9", "@storybook/react-vite": "^7.6.9", "@storybook/test": "^7.6.9", - "@testing-library/dom": "^8.20.0", - "@testing-library/react": "^13.4.0", + "@testing-library/dom": "^10.1.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", "@types/jest": "^26.0.22", "@types/node": "^15.6.1", diff --git a/src/core/instance.core.ts b/src/core/instance.core.ts index 840dba4..15ecccc 100644 --- a/src/core/instance.core.ts +++ b/src/core/instance.core.ts @@ -327,7 +327,7 @@ export class ZoomPanPinch { onPinchStart = (event: TouchEvent): void => { const { disabled } = this.setup; - const { onPinchingStart, onZoomStart } = this.props; + const { onPinchStart, onZoomStart } = this.props; if (disabled) return; @@ -336,13 +336,13 @@ export class ZoomPanPinch { handlePinchStart(this, event); handleCancelAnimation(this); - handleCallback(getContext(this), event, onPinchingStart); + handleCallback(getContext(this), event, onPinchStart); handleCallback(getContext(this), event, onZoomStart); }; onPinch = (event: TouchEvent): void => { const { disabled } = this.setup; - const { onPinching, onZoom } = this.props; + const { onPinch, onZoom } = this.props; if (disabled) return; @@ -353,16 +353,16 @@ export class ZoomPanPinch { event.stopPropagation(); handlePinchZoom(this, event); - handleCallback(getContext(this), event, onPinching); + handleCallback(getContext(this), event, onPinch); handleCallback(getContext(this), event, onZoom); }; onPinchStop = (event: TouchEvent): void => { - const { onPinchingStop, onZoomStop } = this.props; + const { onPinchStop, onZoomStop } = this.props; if (this.pinchStartScale) { handlePinchStop(this); - handleCallback(getContext(this), event, onPinchingStop); + handleCallback(getContext(this), event, onPinchStop); handleCallback(getContext(this), event, onZoomStop); } }; @@ -372,14 +372,15 @@ export class ZoomPanPinch { /// /////// onTouchPanningStart = (event: TouchEvent): void => { - const { disabled } = this.setup; + const { disabled, doubleClick } = this.setup; const { onPanningStart } = this.props; if (disabled) return; + const isDoubleTapAllowed = !doubleClick?.disabled; const isDoubleTap = this.lastTouch && +new Date() - this.lastTouch < 200; - if (isDoubleTap && event.touches.length === 1) { + if (isDoubleTapAllowed && isDoubleTap && event.touches.length === 1) { this.onDoubleClick(event); } else { this.lastTouch = +new Date(); diff --git a/src/models/context.model.ts b/src/models/context.model.ts index 474b6f4..0233e19 100644 --- a/src/models/context.model.ts +++ b/src/models/context.model.ts @@ -142,9 +142,9 @@ export type ReactZoomPanPinchProps = { ref: ReactZoomPanPinchRef, event: TouchEvent | MouseEvent, ) => void; - onPinchingStart?: (ref: ReactZoomPanPinchRef, event: TouchEvent) => void; - onPinching?: (ref: ReactZoomPanPinchRef, event: TouchEvent) => void; - onPinchingStop?: (ref: ReactZoomPanPinchRef, event: TouchEvent) => void; + onPinchStart?: (ref: ReactZoomPanPinchRef, event: TouchEvent) => void; + onPinch?: (ref: ReactZoomPanPinchRef, event: TouchEvent) => void; + onPinchStop?: (ref: ReactZoomPanPinchRef, event: TouchEvent) => void; onZoomStart?: ( ref: ReactZoomPanPinchRef, event: TouchEvent | MouseEvent, @@ -192,9 +192,9 @@ export type LibrarySetup = Pick< | "onPanningStart" | "onPanning" | "onPanningStop" - | "onPinchingStart" - | "onPinching" - | "onPinchingStop" + | "onPinchStart" + | "onPinch" + | "onPinchStop" | "onZoomStart" | "onZoom" | "onZoomStop" diff --git a/src/stories/docs/props.tsx b/src/stories/docs/props.tsx index a71bcc3..41df927 100644 --- a/src/stories/docs/props.tsx +++ b/src/stories/docs/props.tsx @@ -495,17 +495,17 @@ export const wrapperPropsTable: ComponentProps = { defaultValue: "undefined", description: "Callback fired when panning event has finished", }, - onPinchingStart: { + onPinchStart: { type: ["(ref: ReactZoomPanPinchRef, event) => void"], defaultValue: "undefined", description: "Callback fired when pinch event has started", }, - onPinching: { + onPinch: { type: ["(ref: ReactZoomPanPinchRef, event) => void"], defaultValue: "undefined", description: "Callback fired when pinch event is ongoing", }, - onPinchingStop: { + onPinchStop: { type: ["(ref: ReactZoomPanPinchRef, event) => void"], defaultValue: "undefined", description: "Callback fired when pinch event has finished", diff --git a/yarn.lock b/yarn.lock index a409b38..b08544a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4494,15 +4494,15 @@ dependencies: defer-to-connect "^1.0.1" -"@testing-library/dom@^8.20.0", "@testing-library/dom@^8.5.0": - version "8.20.1" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.20.1.tgz#2e52a32e46fc88369eef7eef634ac2a192decd9f" - integrity sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g== +"@testing-library/dom@^10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.1.0.tgz#2d073e49771ad614da999ca48f199919e5176fb6" + integrity sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" "@types/aria-query" "^5.0.1" - aria-query "5.1.3" + aria-query "5.3.0" chalk "^4.1.0" dom-accessibility-api "^0.5.9" lz-string "^1.5.0" @@ -4536,14 +4536,12 @@ lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react@^13.4.0": - version "13.4.0" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.4.0.tgz#6a31e3bf5951615593ad984e96b9e5e2d9380966" - integrity sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw== +"@testing-library/react@^16.0.0": + version "16.0.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.0.0.tgz#0a1e0c7a3de25841c3591b8cb7fb0cf0c0a27321" + integrity sha512-guuxUKRWQ+FgNX0h0NS0FIq3Q3uLtWVpBzcLOggmfMoUpgBnzBzvLLd4fbm6yS8ydJd94cIfY4yP9qUQjM2KwQ== dependencies: "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^8.5.0" - "@types/react-dom" "^18.0.0" "@testing-library/user-event@14.3.0": version "14.3.0" @@ -4931,7 +4929,7 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/react-dom@^18.0.0", "@types/react-dom@^18.2.18": +"@types/react-dom@^18.2.18": version "18.2.18" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd" integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw== @@ -5704,7 +5702,7 @@ aria-query@5.1.3: dependencies: deep-equal "^2.0.5" -aria-query@^5.0.0, aria-query@^5.3.0: +aria-query@5.3.0, aria-query@^5.0.0, aria-query@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==