/** @jsxImportSource test */ import { View } from "react-native"; import { getAnimatedStyle } from "react-native-reanimated"; import { fireEvent, registerCSS, render, screen, setupAllComponents, } from "test"; const testID = "react-native-css-interop"; setupAllComponents(); jest.useFakeTimers(); test("basic animation", () => { registerCSS(` .my-class { animation-duration: 3000ms; animation-name: slidein; animation-timing-function: linear; } @keyframes slidein { from { margin-left: 100%; } to { margin-left: 0%; } } `); render(); const component = screen.getByTestId(testID); expect(component).toHaveAnimatedStyle({ marginLeft: "100%", }); jest.advanceTimersByTime(1500); expect(getAnimatedStyle(component)).toEqual({ marginLeft: "50%", }); jest.advanceTimersByTime(1500); expect(component).toHaveAnimatedStyle({ marginLeft: "0%", }); }); test("single frame", () => { registerCSS(` .my-class { animation-duration: 3s; animation-name: spin; animation-timing-function: linear; } @keyframes spin { to { transform: rotate(360deg); } } `); const component = render( , ).getByTestId(testID); expect(component).toHaveAnimatedStyle({ transform: [{ rotate: "0deg" }], }); jest.advanceTimersByTime(1500); expect(component).toHaveAnimatedStyle({ transform: [{ rotate: "180deg" }], }); jest.advanceTimersByTime(1501); expect(component).toHaveAnimatedStyle({ transform: [{ rotate: "360deg" }], }); }); test("transform - starting", () => { registerCSS(` .my-class { animation-duration: 3s; animation-name: spin; animation-timing-function: linear; transform: rotate(180deg); } @keyframes spin { to { transform: rotate(360deg); } } `); const component = render( , ).getByTestId(testID); expect(component).toHaveAnimatedStyle({ transform: [{ rotate: "180deg" }], }); jest.advanceTimersByTime(1500); expect(component).toHaveAnimatedStyle({ transform: [{ rotate: "270deg" }], }); jest.advanceTimersByTime(1500); expect(component).toHaveAnimatedStyle({ transform: [{ rotate: "360deg" }], }); }); test("timing functions per frame", () => { registerCSS(` .my-class { animation: bounce 1s infinite; } @keyframes bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1); } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1); } } `); const component = render( , ).getByTestId(testID); // Initial frame is incorrect due to missing layout expect(component).toHaveAnimatedStyle({ transform: [ { translateY: "-25%" }, { perspective: 1 }, { translateX: 0 }, { rotate: "0deg" }, { rotateX: "0deg" }, { rotateY: "0deg" }, { rotateZ: "0deg" }, { scale: 1 }, { scaleX: 1 }, { scaleY: 1 }, { skewX: "0deg" }, { skewY: "0deg" }, ], }); jest.advanceTimersByTime(250); expect(component).toHaveAnimatedStyle({ transform: [ { translateY: "-20.981126534630565%" }, { perspective: 1 }, { translateX: 0 }, { rotate: "0deg" }, { rotateX: "0deg" }, { rotateY: "0deg" }, { rotateZ: "0deg" }, { scale: 1 }, { scaleX: 1 }, { scaleY: 1 }, { skewX: "0deg" }, { skewY: "0deg" }, ], }); jest.advanceTimersByTime(250); expect(component).toHaveAnimatedStyle({ transform: [ { translateY: "0%" }, { perspective: 1 }, { translateX: 0 }, { rotate: "0deg" }, { rotateX: "0deg" }, { rotateY: "0deg" }, { rotateZ: "0deg" }, { scale: 1 }, { scaleX: 1 }, { scaleY: 1 }, { skewX: "0deg" }, { skewY: "0deg" }, ], }); jest.advanceTimersByTime(250); expect(component).toHaveAnimatedStyle({ transform: [ { translateY: "-20.944655241363616%" }, { perspective: 1 }, { translateX: 0 }, { rotate: "0deg" }, { rotateX: "0deg" }, { rotateY: "0deg" }, { rotateZ: "0deg" }, { scale: 1 }, { scaleX: 1 }, { scaleY: 1 }, { skewX: "0deg" }, { skewY: "0deg" }, ], }); jest.advanceTimersByTime(251); expect(component).toHaveAnimatedStyle({ transform: [ { translateY: "-25%" }, { perspective: 1 }, { translateX: 0 }, { rotate: "0deg" }, { rotateX: "0deg" }, { rotateY: "0deg" }, { rotateZ: "0deg" }, { scale: 1 }, { scaleX: 1 }, { scaleY: 1 }, { skewX: "0deg" }, { skewY: "0deg" }, ], }); }); test("per-frame timing function", () => { registerCSS(` .my-class { animation: spin 1s infinite linear } @keyframes spin { 100% { transform: rotate(360deg); } 0% { transform: rotate(0deg); } 50% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0,0,0.2,1); } } `); const component = render( , ).getByTestId(testID); fireEvent(component, "layout", { nativeEvent: { layout: { width: 200, height: 100 } }, }); jest.advanceTimersByTime(250); expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "90deg" }], }); jest.advanceTimersByTime(250); expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "180deg" }], }); jest.advanceTimersByTime(250); expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "330.801517737818deg" }], }); jest.advanceTimersByTime(251); expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "360deg" }], }); }); test("work with active styles", () => { registerCSS(` .my-class:active { animation-duration: 3s; animation-name: spin; animation-timing-function: linear; } @keyframes spin { to { transform: rotate(360deg); } } `); render(); const component = screen.getByTestId(testID); expect(getAnimatedStyle(component)).toStrictEqual({}); fireEvent(component, "onPressIn"); // Half way though the animation jest.advanceTimersByTime(1500); expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "180deg" }], }); fireEvent(component, "onPressOut"); jest.advanceTimersToNextTimer(); expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "0deg" }], }); }); test("stop animating when the animation is removed", () => { registerCSS(` .my-class { animation-duration: 3s; animation-name: spin; animation-timing-function: linear; } @keyframes spin { to { transform: rotate(360deg); } } .bg-white { background-color: white; } `); render(); const component = screen.getByTestId(testID); // Half way though the animation jest.advanceTimersByTime(1500); expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "180deg" }], }); // Remove the animation screen.rerender(); jest.advanceTimersToNextTimer(); // The animation should have reset // Reanimated is keeping the transform prop expect(getAnimatedStyle(component)).toStrictEqual({ backgroundColor: "#ffffff", transform: [ { rotate: "0deg", }, ], }); }); test("stop animating when the animation-none is added", () => { registerCSS(` .my-class { animation-duration: 3s; animation-name: spin; animation-timing-function: linear; } .animate-none { animation-name: none; } @keyframes spin { to { transform: rotate(360deg); } } `); render(); const component = screen.getByTestId(testID); jest.advanceTimersByTime(1500); expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "180deg" }], }); screen.rerender(); jest.advanceTimersToNextTimer(); expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "0deg" }], }); jest.advanceTimersByTime(1500); expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "0deg" }], }); }); test("can change the duration mid animation", () => { registerCSS(` .my-class { animation-duration: 3s; animation-name: spin; animation-timing-function: linear; } @keyframes spin { to { transform: rotate(360deg); } } .new-duration { animation-duration: 1s; } `); render(); const component = screen.getByTestId(testID); // Half way though the animation jest.advanceTimersByTime(1500); expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "180deg" }], }); // Change the duration screen.rerender(); // The animation should have reset expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "0deg" }], }); // Now the half-way point is at 500ms jest.advanceTimersByTime(500); expect(getAnimatedStyle(component)).toStrictEqual({ transform: [{ rotate: "180deg" }], }); });