-
-
Notifications
You must be signed in to change notification settings - Fork 905
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding unit support to useSpring (#3046)
* Adding unit support to useSpring * Fixing tests * Fixes * Removing string coercison
- Loading branch information
1 parent
d100739
commit 85bd99f
Showing
4 changed files
with
247 additions
and
143 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
309 changes: 190 additions & 119 deletions
309
packages/framer-motion/src/value/__tests__/use-spring.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,156 +1,227 @@ | ||
import { render } from "../../../jest.setup" | ||
import { useEffect } from "react"; | ||
import { useSpring } from "../use-spring" | ||
import { useMotionValue } from "../use-motion-value" | ||
import { useEffect } from "react" | ||
import { motionValue, MotionValue } from ".." | ||
import { motion } from "../../" | ||
import { render } from "../../../jest.setup" | ||
import { syncDriver } from "../../animation/animators/__tests__/utils" | ||
import { useMotionValue } from "../use-motion-value" | ||
import { useSpring } from "../use-spring" | ||
|
||
describe("useSpring", () => { | ||
describe("useSpring types", () => { | ||
test("can create a motion value from a number", async () => { | ||
const promise = new Promise((resolve) => { | ||
const Component = () => { | ||
const x = useSpring(0) | ||
const Component = () => { | ||
const x = useSpring(0) | ||
expect(x.get()).toBe(0) | ||
return null | ||
} | ||
render(<Component />) | ||
}) | ||
|
||
useEffect(() => { | ||
x.on("change", (v) => resolve(v)) | ||
x.set(100) | ||
}) | ||
test("can create a motion value from a string with a unit", async () => { | ||
const Component = () => { | ||
const x = useSpring("0%") | ||
expect(x.get()).toBe("0%") | ||
return null | ||
} | ||
render(<Component />) | ||
}) | ||
|
||
return null | ||
} | ||
test("can create a motion value from a number motion value", async () => { | ||
const Component = () => { | ||
const source = motionValue(0) | ||
const x = useSpring(source) | ||
expect(x.get()).toBe(0) | ||
return null | ||
} | ||
render(<Component />) | ||
}) | ||
|
||
const { rerender } = render(<Component />) | ||
rerender(<Component />) | ||
}) | ||
test("can create a motion value from a string motion value with a unit", async () => { | ||
const Component = () => { | ||
const source = motionValue("0%") | ||
const x = useSpring(source) | ||
expect(x.get()).toBe("0%") | ||
return null | ||
} | ||
render(<Component />) | ||
}) | ||
}) | ||
|
||
const resolved = await promise | ||
const runSpringTests = (unit?: string | undefined) => { | ||
const createValue = (num: number) => { | ||
if (unit) { | ||
return `${num}${unit}` as unknown as number | ||
} | ||
return num as number | ||
} | ||
|
||
expect(resolved).not.toBe(0) | ||
expect(resolved).not.toBe(100) | ||
}) | ||
const parseTestValue = (val: string | number): number => | ||
typeof val === "string" ? parseFloat(val) : val | ||
|
||
test("can create a MotionValue that responds to changes from another MotionValue", async () => { | ||
const promise = new Promise((resolve) => { | ||
const Component = () => { | ||
const x = useMotionValue(0) | ||
const y = useSpring(x) | ||
const formatOutput = (num: number) => { | ||
if (unit) { | ||
return `${Math.round(num)}${unit}` | ||
} | ||
return Math.round(num) | ||
} | ||
|
||
describe(`useSpring ${unit ? `with ${unit}` : "with numbers"}`, () => { | ||
test("can create a motion value from a number", async () => { | ||
const promise = new Promise((resolve) => { | ||
const Component = () => { | ||
const x = useMotionValue(createValue(0)) | ||
const spring = useSpring(x) | ||
|
||
useEffect(() => { | ||
spring.on("change", (v) => resolve(v)) | ||
x.set(createValue(100)) | ||
}) | ||
|
||
useEffect(() => { | ||
y.on("change", (v) => resolve(v)) | ||
x.set(100) | ||
}) | ||
return null | ||
} | ||
|
||
return null | ||
} | ||
const { rerender } = render(<Component />) | ||
rerender(<Component />) | ||
}) | ||
|
||
const { rerender } = render(<Component />) | ||
rerender(<Component />) | ||
}) | ||
const resolved = await promise | ||
|
||
const resolved = await promise | ||
expect(resolved).not.toBe(createValue(0)) | ||
expect(resolved).not.toBe(createValue(100)) | ||
}) | ||
|
||
expect(resolved).not.toBe(0) | ||
expect(resolved).not.toBe(100) | ||
}) | ||
test("can create a MotionValue that responds to changes from another MotionValue", async () => { | ||
const promise = new Promise((resolve) => { | ||
const Component = () => { | ||
const x = useMotionValue(createValue(0)) | ||
const y = useSpring(x) | ||
|
||
test("creates a spring that animates to the subscribed motion value", async () => { | ||
const promise = new Promise<number[]>((resolve) => { | ||
const output: number[] = [] | ||
const Component = () => { | ||
const x = useMotionValue(0) | ||
const y = useSpring(x, { | ||
driver: syncDriver(10), | ||
} as any) | ||
|
||
useEffect(() => { | ||
return y.on("change", (v) => { | ||
if (output.length >= 10) { | ||
resolve(output) | ||
} else { | ||
output.push(Math.round(v)) | ||
} | ||
useEffect(() => { | ||
y.on("change", (v) => resolve(v)) | ||
x.set(createValue(100)) | ||
}) | ||
}) | ||
|
||
useEffect(() => { | ||
x.set(100) | ||
}, []) | ||
return null | ||
} | ||
|
||
return null | ||
} | ||
const { rerender } = render(<Component />) | ||
rerender(<Component />) | ||
}) | ||
|
||
const resolved = await promise | ||
|
||
const { rerender } = render(<Component />) | ||
rerender(<Component />) | ||
expect(resolved).not.toBe(createValue(0)) | ||
expect(resolved).not.toBe(createValue(100)) | ||
}) | ||
|
||
const resolved = await promise | ||
test("creates a spring that animates to the subscribed motion value", async () => { | ||
const promise = new Promise<Array<string | number>>((resolve) => { | ||
const output: Array<string | number> = [] | ||
const Component = () => { | ||
const x = useMotionValue(createValue(0)) | ||
const y = useSpring(x, { | ||
driver: syncDriver(10), | ||
} as any) | ||
|
||
useEffect(() => { | ||
return y.on("change", (v) => { | ||
if (output.length >= 10) { | ||
resolve(output) | ||
} else { | ||
output.push(formatOutput(parseTestValue(v))) | ||
} | ||
}) | ||
}) | ||
|
||
const testNear = (value: number, expected: number, deviation = 2) => { | ||
expect( | ||
value >= expected - deviation && value <= expected + deviation | ||
).toBe(true) | ||
} | ||
useEffect(() => { | ||
x.set(createValue(100)) | ||
}, []) | ||
|
||
return null | ||
} | ||
|
||
const { rerender } = render(<Component />) | ||
rerender(<Component />) | ||
}) | ||
|
||
const resolved = await promise | ||
|
||
const testNear = ( | ||
value: string | number, | ||
expected: number, | ||
deviation = 2 | ||
) => { | ||
const numValue = parseTestValue(value) | ||
expect( | ||
numValue >= expected - deviation && | ||
numValue <= expected + deviation | ||
).toBe(true) | ||
} | ||
|
||
testNear(resolved[0], 0) | ||
testNear(resolved[4], 10) | ||
testNear(resolved[8], 30) | ||
}) | ||
testNear(resolved[0], 0) | ||
testNear(resolved[4], 10) | ||
testNear(resolved[8], 30) | ||
}) | ||
|
||
test("will not animate if immediate=true", async () => { | ||
const promise = new Promise((resolve) => { | ||
const output: number[] = [] | ||
const Component = () => { | ||
const y = useSpring(0, { | ||
driver: syncDriver(10), | ||
} as any) | ||
|
||
useEffect(() => { | ||
return y.on("change", (v) => { | ||
if (output.length >= 10) { | ||
} else { | ||
output.push(Math.round(v)) | ||
} | ||
test("will not animate if immediate=true", async () => { | ||
const promise = new Promise((resolve) => { | ||
const output: Array<string | number> = [] | ||
const Component = () => { | ||
const x = useMotionValue(createValue(0)) | ||
const y = useSpring(x, { | ||
driver: syncDriver(10), | ||
} as any) | ||
|
||
useEffect(() => { | ||
return y.on("change", (v) => { | ||
if (output.length >= 10) { | ||
} else { | ||
output.push(formatOutput(parseTestValue(v))) | ||
} | ||
}) | ||
}) | ||
}) | ||
|
||
useEffect(() => { | ||
y.jump(100) | ||
useEffect(() => { | ||
y.jump(createValue(100)) | ||
|
||
setTimeout(() => { | ||
resolve(output) | ||
}, 100) | ||
}, []) | ||
setTimeout(() => { | ||
resolve(output) | ||
}, 100) | ||
}, []) | ||
|
||
return null | ||
} | ||
return null | ||
} | ||
|
||
const { rerender } = render(<Component />) | ||
rerender(<Component />) | ||
}) | ||
const { rerender } = render(<Component />) | ||
rerender(<Component />) | ||
}) | ||
|
||
const resolved = await promise | ||
const resolved = await promise | ||
|
||
expect(resolved).toEqual([100]) | ||
}) | ||
expect(resolved).toEqual([createValue(100)]) | ||
}) | ||
|
||
test("unsubscribes when attached to a new value", () => { | ||
const a = motionValue(0) | ||
const b = motionValue(0) | ||
let y: MotionValue<number> | ||
const Component = ({ target }: { target: MotionValue<number> }) => { | ||
y = useSpring(target) | ||
return <motion.div style={{ y }} /> | ||
} | ||
test("unsubscribes when attached to a new value", () => { | ||
const a = motionValue(createValue(0)) | ||
const b = motionValue(createValue(0)) | ||
let y: MotionValue<number> | ||
const Component = ({ target }: { target: MotionValue<number> }) => { | ||
y = useSpring(target) | ||
return <motion.div style={{ y }} /> | ||
} | ||
|
||
const { rerender } = render(<Component target={a} />) | ||
rerender(<Component target={b} />) | ||
rerender(<Component target={a} />) | ||
rerender(<Component target={b} />) | ||
rerender(<Component target={a} />) | ||
rerender(<Component target={a} />) | ||
const { rerender } = render(<Component target={a} />) | ||
rerender(<Component target={b} />) | ||
rerender(<Component target={a} />) | ||
rerender(<Component target={b} />) | ||
rerender(<Component target={a} />) | ||
rerender(<Component target={a} />) | ||
|
||
// Cast to any here as `.events` is private API | ||
expect((a as any).events.change.getSize()).toBe(1) | ||
// Cast to any here as `.events` is private API | ||
expect((a as any).events.change.getSize()).toBe(1) | ||
}) | ||
}) | ||
}) | ||
} | ||
|
||
// Run tests for both number values and percentage values | ||
runSpringTests() | ||
runSpringTests("%") |
Oops, something went wrong.