|
1 | 1 | import { expect, test } from "bun:test" |
2 | 2 | import { useSpring } from "@opencode/ui/motion-spring" |
3 | | -import { createRoot, createSignal } from "solid-js" |
| 3 | +import { createEffect, createRoot, createSignal } from "solid-js" |
| 4 | + |
| 5 | +// Records every value the spring emits and resolves once it reaches `settled`. |
| 6 | +function watch(progress: () => number, settled: number) { |
| 7 | + const seen: number[] = [] |
| 8 | + const { promise, resolve } = Promise.withResolvers<void>() |
| 9 | + |
| 10 | + createEffect(() => { |
| 11 | + const value = progress() |
| 12 | + |
| 13 | + seen.push(value) |
| 14 | + |
| 15 | + if (value === settled) resolve() |
| 16 | + }) |
| 17 | + |
| 18 | + return { seen, settled: promise } |
| 19 | +} |
4 | 20 |
|
5 | 21 | test("springs progress to its target", async () => { |
6 | 22 | const state = createRoot((dispose) => { |
7 | 23 | const [target, setTarget] = createSignal(0) |
8 | | - const progress = useSpring(target, { visualDuration: 0.1, bounce: 0 }) |
| 24 | + const progress = useSpring(target, { visualDuration: 0.3, bounce: 0 }) |
9 | 25 |
|
10 | | - return { dispose, progress, setTarget } |
| 26 | + return { dispose, progress, setTarget, ...watch(progress, 1) } |
11 | 27 | }) |
12 | 28 |
|
13 | | - const seen: number[] = [] |
14 | | - |
15 | 29 | state.setTarget(1) |
| 30 | + await Promise.race([state.settled, Bun.sleep(2000)]) |
16 | 31 |
|
17 | | - const deadline = Date.now() + 1000 |
| 32 | + expect(state.progress()).toBe(1) |
| 33 | + expect(state.seen.some((value) => value > 0 && value < 1)).toBe(true) |
| 34 | + expect(state.seen.every((value) => value <= 1)).toBe(true) |
| 35 | + state.dispose() |
| 36 | +}) |
| 37 | + |
| 38 | +test("new spring options replace the running spring", async () => { |
| 39 | + const state = createRoot((dispose) => { |
| 40 | + const [target, setTarget] = createSignal(0) |
| 41 | + const [duration, setDuration] = createSignal(4) |
| 42 | + const progress = useSpring(target, () => ({ visualDuration: duration(), bounce: 0 })) |
| 43 | + |
| 44 | + return { dispose, progress, setTarget, setDuration, ...watch(progress, 1) } |
| 45 | + }) |
| 46 | + |
| 47 | + state.setTarget(1) |
| 48 | + await Bun.sleep(100) |
| 49 | + expect(state.progress()).toBeLessThan(0.5) |
18 | 50 |
|
19 | | - while (state.progress() !== 1 && Date.now() < deadline) { |
20 | | - await new Promise((resolve) => setTimeout(resolve, 16)) |
21 | | - seen.push(state.progress()) |
22 | | - } |
| 51 | + // The old four-second spring would still be far from its target after a second. |
| 52 | + state.setDuration(0.1) |
| 53 | + await Promise.race([state.settled, Bun.sleep(1000)]) |
23 | 54 |
|
24 | 55 | expect(state.progress()).toBe(1) |
25 | | - expect(seen.some((value) => value > 0 && value < 1)).toBe(true) |
26 | | - expect(seen.every((value) => value <= 1)).toBe(true) |
27 | 56 | state.dispose() |
28 | 57 | }) |
29 | 58 |
|
|
0 commit comments