react-native-screen-transitions 3.3.0-beta.2 → 3.3.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -31
- package/lib/commonjs/shared/animation/snap-to.js +17 -10
- package/lib/commonjs/shared/animation/snap-to.js.map +1 -1
- package/lib/commonjs/shared/components/create-transition-aware-component.js +20 -18
- package/lib/commonjs/shared/components/create-transition-aware-component.js.map +1 -1
- package/lib/commonjs/shared/components/screen-container.js +68 -9
- package/lib/commonjs/shared/components/screen-container.js.map +1 -1
- package/lib/commonjs/shared/constants.js +8 -1
- package/lib/commonjs/shared/constants.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +49 -39
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js +110 -61
- package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js +67 -70
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/commonjs/shared/providers/gestures.provider.js +113 -25
- package/lib/commonjs/shared/providers/gestures.provider.js.map +1 -1
- package/lib/commonjs/shared/types/ownership.types.js +71 -0
- package/lib/commonjs/shared/types/ownership.types.js.map +1 -0
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js +72 -128
- package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/commonjs/shared/utils/gesture/compute-claimed-directions.js +81 -0
- package/lib/commonjs/shared/utils/gesture/compute-claimed-directions.js.map +1 -0
- package/lib/commonjs/shared/utils/gesture/determine-snap-target.js +1 -1
- package/lib/commonjs/shared/utils/gesture/determine-snap-target.js.map +1 -1
- package/lib/commonjs/shared/utils/gesture/find-collapse-target.js +48 -0
- package/lib/commonjs/shared/utils/gesture/find-collapse-target.js.map +1 -0
- package/lib/commonjs/shared/utils/gesture/resolve-ownership.js +87 -0
- package/lib/commonjs/shared/utils/gesture/resolve-ownership.js.map +1 -0
- package/lib/commonjs/shared/utils/gesture/velocity.js +16 -5
- package/lib/commonjs/shared/utils/gesture/velocity.js.map +1 -1
- package/lib/module/shared/animation/snap-to.js +16 -10
- package/lib/module/shared/animation/snap-to.js.map +1 -1
- package/lib/module/shared/components/create-transition-aware-component.js +20 -18
- package/lib/module/shared/components/create-transition-aware-component.js.map +1 -1
- package/lib/module/shared/components/screen-container.js +68 -10
- package/lib/module/shared/components/screen-container.js.map +1 -1
- package/lib/module/shared/constants.js +7 -0
- package/lib/module/shared/constants.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-build-gestures.js +49 -39
- package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js +112 -63
- package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js +68 -70
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/module/shared/providers/gestures.provider.js +113 -25
- package/lib/module/shared/providers/gestures.provider.js.map +1 -1
- package/lib/module/shared/types/ownership.types.js +67 -0
- package/lib/module/shared/types/ownership.types.js.map +1 -0
- package/lib/module/shared/utils/gesture/check-gesture-activation.js +70 -126
- package/lib/module/shared/utils/gesture/check-gesture-activation.js.map +1 -1
- package/lib/module/shared/utils/gesture/compute-claimed-directions.js +77 -0
- package/lib/module/shared/utils/gesture/compute-claimed-directions.js.map +1 -0
- package/lib/module/shared/utils/gesture/determine-snap-target.js +1 -1
- package/lib/module/shared/utils/gesture/determine-snap-target.js.map +1 -1
- package/lib/module/shared/utils/gesture/find-collapse-target.js +44 -0
- package/lib/module/shared/utils/gesture/find-collapse-target.js.map +1 -0
- package/lib/module/shared/utils/gesture/resolve-ownership.js +83 -0
- package/lib/module/shared/utils/gesture/resolve-ownership.js.map +1 -0
- package/lib/module/shared/utils/gesture/velocity.js +16 -5
- package/lib/module/shared/utils/gesture/velocity.js.map +1 -1
- package/lib/typescript/shared/animation/snap-to.d.ts.map +1 -1
- package/lib/typescript/shared/components/create-transition-aware-component.d.ts.map +1 -1
- package/lib/typescript/shared/components/screen-container.d.ts.map +1 -1
- package/lib/typescript/shared/constants.d.ts +6 -0
- package/lib/typescript/shared/constants.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts +15 -3
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts +52 -2
- package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts +11 -6
- package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/use-backdrop-pointer-events.d.ts +1 -1
- package/lib/typescript/shared/hooks/use-backdrop-pointer-events.d.ts.map +1 -1
- package/lib/typescript/shared/providers/gestures.provider.d.ts +28 -3
- package/lib/typescript/shared/providers/gestures.provider.d.ts.map +1 -1
- package/lib/typescript/shared/types/ownership.types.d.ts +52 -0
- package/lib/typescript/shared/types/ownership.types.d.ts.map +1 -0
- package/lib/typescript/shared/types/screen.types.d.ts +22 -1
- package/lib/typescript/shared/types/screen.types.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts +23 -19
- package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/compute-claimed-directions.d.ts +23 -0
- package/lib/typescript/shared/utils/gesture/compute-claimed-directions.d.ts.map +1 -0
- package/lib/typescript/shared/utils/gesture/determine-snap-target.d.ts +5 -1
- package/lib/typescript/shared/utils/gesture/determine-snap-target.d.ts.map +1 -1
- package/lib/typescript/shared/utils/gesture/find-collapse-target.d.ts +17 -0
- package/lib/typescript/shared/utils/gesture/find-collapse-target.d.ts.map +1 -0
- package/lib/typescript/shared/utils/gesture/resolve-ownership.d.ts +36 -0
- package/lib/typescript/shared/utils/gesture/resolve-ownership.d.ts.map +1 -0
- package/lib/typescript/shared/utils/gesture/velocity.d.ts.map +1 -1
- package/package.json +121 -120
- package/src/shared/animation/snap-to.ts +17 -11
- package/src/shared/components/create-transition-aware-component.tsx +28 -25
- package/src/shared/components/screen-container.tsx +79 -12
- package/src/shared/constants.ts +7 -0
- package/src/shared/hooks/gestures/use-build-gestures.tsx +80 -44
- package/src/shared/hooks/gestures/use-screen-gesture-handlers.ts +147 -71
- package/src/shared/hooks/gestures/use-scroll-registry.tsx +94 -86
- package/src/shared/hooks/use-backdrop-pointer-events.ts +1 -1
- package/src/shared/providers/gestures.provider.tsx +168 -25
- package/src/shared/types/ownership.types.ts +77 -0
- package/src/shared/types/screen.types.ts +24 -1
- package/src/shared/utils/gesture/check-gesture-activation.ts +82 -116
- package/src/shared/utils/gesture/compute-claimed-directions.ts +93 -0
- package/src/shared/utils/gesture/determine-snap-target.ts +6 -2
- package/src/shared/utils/gesture/find-collapse-target.ts +42 -0
- package/src/shared/utils/gesture/resolve-ownership.ts +110 -0
- package/src/shared/utils/gesture/velocity.ts +16 -6
- package/src/shared/__tests__/bounds.store.test.ts +0 -394
- package/src/shared/__tests__/derivations.test.ts +0 -156
- package/src/shared/__tests__/determine-dismissal.test.ts +0 -111
- package/src/shared/__tests__/determine-snap-target.test.ts +0 -268
- package/src/shared/__tests__/geometry.test.ts +0 -130
- package/src/shared/__tests__/gesture-activation.test.ts +0 -471
- package/src/shared/__tests__/gesture.velocity.test.ts +0 -131
- package/src/shared/__tests__/history.store.test.ts +0 -550
- package/src/shared/__tests__/sync-routes-with-removed.test.ts +0 -137
- package/src/shared/__tests__/validate-snap-points.test.ts +0 -125
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import { BoundStore, type Snapshot } from "../stores/bounds.store";
|
|
3
|
-
|
|
4
|
-
// Helper to create mock bounds
|
|
5
|
-
const createBounds = (
|
|
6
|
-
x = 0,
|
|
7
|
-
y = 0,
|
|
8
|
-
width = 100,
|
|
9
|
-
height = 100,
|
|
10
|
-
): Snapshot["bounds"] => ({
|
|
11
|
-
x,
|
|
12
|
-
y,
|
|
13
|
-
pageX: x,
|
|
14
|
-
pageY: y,
|
|
15
|
-
width,
|
|
16
|
-
height,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
// Reset registry before each test
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
globalThis.resetMutableRegistry();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// =============================================================================
|
|
25
|
-
// Unit Tests - registerSnapshot
|
|
26
|
-
// =============================================================================
|
|
27
|
-
|
|
28
|
-
describe("BoundStore.registerSnapshot", () => {
|
|
29
|
-
it("registers new tag with bounds and styles", () => {
|
|
30
|
-
const bounds = createBounds(10, 20, 200, 300);
|
|
31
|
-
const styles = { backgroundColor: "red" };
|
|
32
|
-
|
|
33
|
-
BoundStore.registerSnapshot("card", "screen-a", bounds, styles);
|
|
34
|
-
|
|
35
|
-
const snapshot = BoundStore.getSnapshot("card", "screen-a");
|
|
36
|
-
expect(snapshot).not.toBeNull();
|
|
37
|
-
expect(snapshot?.bounds).toEqual(bounds);
|
|
38
|
-
expect(snapshot?.styles).toEqual(styles);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("adds snapshot to existing tag", () => {
|
|
42
|
-
const boundsA = createBounds(0, 0, 100, 100);
|
|
43
|
-
const boundsB = createBounds(50, 50, 150, 150);
|
|
44
|
-
|
|
45
|
-
BoundStore.registerSnapshot("card", "screen-a", boundsA);
|
|
46
|
-
BoundStore.registerSnapshot("card", "screen-b", boundsB);
|
|
47
|
-
|
|
48
|
-
expect(BoundStore.getSnapshot("card", "screen-a")?.bounds).toEqual(boundsA);
|
|
49
|
-
expect(BoundStore.getSnapshot("card", "screen-b")?.bounds).toEqual(boundsB);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("stores ancestorKeys correctly", () => {
|
|
53
|
-
const bounds = createBounds();
|
|
54
|
-
const ancestors = ["stack-a", "tab-nav"];
|
|
55
|
-
|
|
56
|
-
BoundStore.registerSnapshot("card", "screen-a", bounds, {}, ancestors);
|
|
57
|
-
|
|
58
|
-
// Verify ancestor matching works
|
|
59
|
-
const viaAncestor = BoundStore.getSnapshot("card", "stack-a");
|
|
60
|
-
expect(viaAncestor).not.toBeNull();
|
|
61
|
-
expect(viaAncestor?.bounds).toEqual(bounds);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("updates existing snapshot on re-measurement", () => {
|
|
65
|
-
const initialBounds = createBounds(0, 0, 100, 100);
|
|
66
|
-
const updatedBounds = createBounds(10, 10, 200, 200);
|
|
67
|
-
|
|
68
|
-
BoundStore.registerSnapshot("card", "screen-a", initialBounds);
|
|
69
|
-
BoundStore.registerSnapshot("card", "screen-a", updatedBounds);
|
|
70
|
-
|
|
71
|
-
const snapshot = BoundStore.getSnapshot("card", "screen-a");
|
|
72
|
-
expect(snapshot?.bounds).toEqual(updatedBounds);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// =============================================================================
|
|
77
|
-
// Unit Tests - setLinkSource / setLinkDestination
|
|
78
|
-
// =============================================================================
|
|
79
|
-
|
|
80
|
-
describe("BoundStore.setLinkSource", () => {
|
|
81
|
-
it("creates new tag if it does not exist", () => {
|
|
82
|
-
const bounds = createBounds();
|
|
83
|
-
|
|
84
|
-
BoundStore.setLinkSource("card", "screen-a", bounds);
|
|
85
|
-
|
|
86
|
-
const link = BoundStore.getActiveLink("card");
|
|
87
|
-
expect(link).not.toBeNull();
|
|
88
|
-
expect(link?.source.screenKey).toBe("screen-a");
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("pushes link with source and null destination", () => {
|
|
92
|
-
const bounds = createBounds();
|
|
93
|
-
|
|
94
|
-
BoundStore.setLinkSource("card", "screen-a", bounds);
|
|
95
|
-
|
|
96
|
-
const link = BoundStore.getActiveLink("card");
|
|
97
|
-
expect(link?.source.screenKey).toBe("screen-a");
|
|
98
|
-
expect(link?.destination).toBeNull();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("multiple sources create multiple links", () => {
|
|
102
|
-
const boundsA = createBounds(0, 0);
|
|
103
|
-
const boundsB = createBounds(100, 100);
|
|
104
|
-
|
|
105
|
-
BoundStore.setLinkSource("card", "screen-a", boundsA);
|
|
106
|
-
BoundStore.setLinkSource("card", "screen-b", boundsB);
|
|
107
|
-
|
|
108
|
-
// Most recent link should be from screen-b
|
|
109
|
-
const link = BoundStore.getActiveLink("card");
|
|
110
|
-
expect(link?.source.screenKey).toBe("screen-b");
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe("BoundStore.setLinkDestination", () => {
|
|
115
|
-
it("fills topmost link with null destination", () => {
|
|
116
|
-
const srcBounds = createBounds(0, 0);
|
|
117
|
-
const dstBounds = createBounds(100, 100);
|
|
118
|
-
|
|
119
|
-
BoundStore.setLinkSource("card", "screen-a", srcBounds);
|
|
120
|
-
BoundStore.setLinkDestination("card", "screen-b", dstBounds);
|
|
121
|
-
|
|
122
|
-
const link = BoundStore.getActiveLink("card");
|
|
123
|
-
expect(link?.source.screenKey).toBe("screen-a");
|
|
124
|
-
expect(link?.destination?.screenKey).toBe("screen-b");
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("ignores if no pending links", () => {
|
|
128
|
-
const bounds = createBounds();
|
|
129
|
-
|
|
130
|
-
// No source set, destination should be ignored
|
|
131
|
-
BoundStore.setLinkDestination("card", "screen-b", bounds);
|
|
132
|
-
|
|
133
|
-
const link = BoundStore.getActiveLink("card");
|
|
134
|
-
expect(link).toBeNull();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it("fills correct link when multiple pending", () => {
|
|
138
|
-
const boundsA = createBounds(0, 0);
|
|
139
|
-
const boundsB = createBounds(50, 50);
|
|
140
|
-
const boundsC = createBounds(100, 100);
|
|
141
|
-
|
|
142
|
-
// Two sources, one destination
|
|
143
|
-
BoundStore.setLinkSource("card", "screen-a", boundsA);
|
|
144
|
-
BoundStore.setLinkSource("card", "screen-b", boundsB);
|
|
145
|
-
BoundStore.setLinkDestination("card", "screen-c", boundsC);
|
|
146
|
-
|
|
147
|
-
// Most recent link (from screen-b) should have destination
|
|
148
|
-
const link = BoundStore.getActiveLink("card");
|
|
149
|
-
expect(link?.source.screenKey).toBe("screen-b");
|
|
150
|
-
expect(link?.destination?.screenKey).toBe("screen-c");
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// =============================================================================
|
|
155
|
-
// Unit Tests - getSnapshot
|
|
156
|
-
// =============================================================================
|
|
157
|
-
|
|
158
|
-
describe("BoundStore.getSnapshot", () => {
|
|
159
|
-
it("returns null for unknown tag", () => {
|
|
160
|
-
const result = BoundStore.getSnapshot("unknown", "screen-a");
|
|
161
|
-
expect(result).toBeNull();
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("returns bounds and styles for direct key match", () => {
|
|
165
|
-
const bounds = createBounds(10, 20, 300, 400);
|
|
166
|
-
const styles = { borderRadius: 8 };
|
|
167
|
-
|
|
168
|
-
BoundStore.registerSnapshot("card", "screen-a", bounds, styles);
|
|
169
|
-
|
|
170
|
-
const result = BoundStore.getSnapshot("card", "screen-a");
|
|
171
|
-
expect(result).toEqual({ bounds, styles });
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it("returns bounds via ancestor match", () => {
|
|
175
|
-
const bounds = createBounds();
|
|
176
|
-
const ancestors = ["stack-a", "root"];
|
|
177
|
-
|
|
178
|
-
BoundStore.registerSnapshot("card", "screen-a", bounds, {}, ancestors);
|
|
179
|
-
|
|
180
|
-
// Query by ancestor key
|
|
181
|
-
const result = BoundStore.getSnapshot("card", "stack-a");
|
|
182
|
-
expect(result).not.toBeNull();
|
|
183
|
-
expect(result?.bounds).toEqual(bounds);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it("prefers direct match over ancestor match", () => {
|
|
187
|
-
const directBounds = createBounds(0, 0, 100, 100);
|
|
188
|
-
const ancestorBounds = createBounds(200, 200, 50, 50);
|
|
189
|
-
|
|
190
|
-
// Register with ancestor that matches another screen's key
|
|
191
|
-
BoundStore.registerSnapshot("card", "screen-a", ancestorBounds, {}, [
|
|
192
|
-
"stack-a",
|
|
193
|
-
]);
|
|
194
|
-
BoundStore.registerSnapshot("card", "stack-a", directBounds);
|
|
195
|
-
|
|
196
|
-
// Direct match should win
|
|
197
|
-
const result = BoundStore.getSnapshot("card", "stack-a");
|
|
198
|
-
expect(result?.bounds).toEqual(directBounds);
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// =============================================================================
|
|
203
|
-
// Unit Tests - getActiveLink
|
|
204
|
-
// =============================================================================
|
|
205
|
-
|
|
206
|
-
describe("BoundStore.getActiveLink", () => {
|
|
207
|
-
it("returns null for unknown tag", () => {
|
|
208
|
-
const result = BoundStore.getActiveLink("unknown");
|
|
209
|
-
expect(result).toBeNull();
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("returns null for empty linkStack", () => {
|
|
213
|
-
// Register snapshot but no links
|
|
214
|
-
BoundStore.registerSnapshot("card", "screen-a", createBounds());
|
|
215
|
-
|
|
216
|
-
const result = BoundStore.getActiveLink("card");
|
|
217
|
-
expect(result).toBeNull();
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it("returns most recent link when no screenKey provided", () => {
|
|
221
|
-
BoundStore.setLinkSource("card", "screen-a", createBounds());
|
|
222
|
-
BoundStore.setLinkDestination("card", "screen-b", createBounds());
|
|
223
|
-
BoundStore.setLinkSource("card", "screen-b", createBounds());
|
|
224
|
-
BoundStore.setLinkDestination("card", "screen-c", createBounds());
|
|
225
|
-
|
|
226
|
-
const link = BoundStore.getActiveLink("card");
|
|
227
|
-
expect(link?.source.screenKey).toBe("screen-b");
|
|
228
|
-
expect(link?.destination?.screenKey).toBe("screen-c");
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("infers isClosing when screenKey matches source", () => {
|
|
232
|
-
BoundStore.setLinkSource("card", "screen-a", createBounds());
|
|
233
|
-
BoundStore.setLinkDestination("card", "screen-b", createBounds());
|
|
234
|
-
|
|
235
|
-
// Query from source screen = closing (going back)
|
|
236
|
-
const linkFromSource = BoundStore.getActiveLink("card", "screen-a");
|
|
237
|
-
expect(linkFromSource?.source.screenKey).toBe("screen-a");
|
|
238
|
-
|
|
239
|
-
// Query from destination screen = opening
|
|
240
|
-
const linkFromDest = BoundStore.getActiveLink("card", "screen-b");
|
|
241
|
-
expect(linkFromDest?.destination?.screenKey).toBe("screen-b");
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it("ancestor matching works in link lookup", () => {
|
|
245
|
-
const ancestors = ["stack-a"];
|
|
246
|
-
|
|
247
|
-
BoundStore.setLinkSource("card", "screen-a", createBounds(), {}, ancestors);
|
|
248
|
-
BoundStore.setLinkDestination("card", "screen-b", createBounds());
|
|
249
|
-
|
|
250
|
-
// Query by ancestor key (matches source)
|
|
251
|
-
const link = BoundStore.getActiveLink("card", "stack-a");
|
|
252
|
-
expect(link).not.toBeNull();
|
|
253
|
-
expect(link?.source.screenKey).toBe("screen-a");
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it("returns null when screenKey does not match any link", () => {
|
|
257
|
-
BoundStore.setLinkSource("card", "screen-a", createBounds());
|
|
258
|
-
BoundStore.setLinkDestination("card", "screen-b", createBounds());
|
|
259
|
-
|
|
260
|
-
const link = BoundStore.getActiveLink("card", "screen-x");
|
|
261
|
-
expect(link).toBeNull();
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
// =============================================================================
|
|
266
|
-
// Scenario Tests - Navigation Flows
|
|
267
|
-
// =============================================================================
|
|
268
|
-
|
|
269
|
-
describe("Scenario: Simple push/pop navigation", () => {
|
|
270
|
-
it("captures source on press, destination on layout, reverses on pop", () => {
|
|
271
|
-
const srcBounds = createBounds(50, 100, 200, 200);
|
|
272
|
-
const dstBounds = createBounds(0, 0, 400, 400);
|
|
273
|
-
|
|
274
|
-
// 1. User presses card on Screen A (source captured)
|
|
275
|
-
BoundStore.setLinkSource("card", "screen-a", srcBounds);
|
|
276
|
-
|
|
277
|
-
// 2. Screen B mounts, measures card (destination captured)
|
|
278
|
-
BoundStore.setLinkDestination("card", "screen-b", dstBounds);
|
|
279
|
-
|
|
280
|
-
// Verify link is complete - query from destination (opening)
|
|
281
|
-
const openingLink = BoundStore.getActiveLink("card", "screen-b");
|
|
282
|
-
expect(openingLink?.source.bounds).toEqual(srcBounds);
|
|
283
|
-
expect(openingLink?.destination?.bounds).toEqual(dstBounds);
|
|
284
|
-
|
|
285
|
-
// 3. Query from source (closing - going back)
|
|
286
|
-
const closingLink = BoundStore.getActiveLink("card", "screen-a");
|
|
287
|
-
expect(closingLink?.source.screenKey).toBe("screen-a");
|
|
288
|
-
expect(closingLink?.destination?.screenKey).toBe("screen-b");
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
describe("Scenario: Multiple bounds, only one matches", () => {
|
|
293
|
-
it("establishes link only for matching bound", () => {
|
|
294
|
-
// Screen A has header, card, footer
|
|
295
|
-
BoundStore.registerSnapshot("header", "screen-a", createBounds(0, 0));
|
|
296
|
-
BoundStore.registerSnapshot("card", "screen-a", createBounds(0, 100));
|
|
297
|
-
BoundStore.registerSnapshot("footer", "screen-a", createBounds(0, 500));
|
|
298
|
-
|
|
299
|
-
// Only card triggers navigation
|
|
300
|
-
BoundStore.setLinkSource("card", "screen-a", createBounds(0, 100));
|
|
301
|
-
|
|
302
|
-
// Screen B only has card
|
|
303
|
-
BoundStore.setLinkDestination("card", "screen-b", createBounds(0, 0));
|
|
304
|
-
|
|
305
|
-
// Card link exists
|
|
306
|
-
expect(BoundStore.getActiveLink("card")).not.toBeNull();
|
|
307
|
-
|
|
308
|
-
// Header and footer have no links (only snapshots)
|
|
309
|
-
expect(BoundStore.getActiveLink("header")).toBeNull();
|
|
310
|
-
expect(BoundStore.getActiveLink("footer")).toBeNull();
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
describe("Scenario: Nested navigator with ancestor keys", () => {
|
|
315
|
-
it("supports cross-stack bounds via ancestor matching", () => {
|
|
316
|
-
// Tab Navigator structure:
|
|
317
|
-
// - Stack A (key: "stack-a") -> Screen A1 (key: "a1", ancestors: ["stack-a"])
|
|
318
|
-
// - Stack B (key: "stack-b") -> Screen B1 (key: "b1", ancestors: ["stack-b"])
|
|
319
|
-
|
|
320
|
-
const boundsA = createBounds(10, 10, 80, 80);
|
|
321
|
-
const boundsB = createBounds(20, 20, 100, 100);
|
|
322
|
-
|
|
323
|
-
// Register snapshot in Stack A
|
|
324
|
-
BoundStore.registerSnapshot("profile", "a1", boundsA, {}, ["stack-a"]);
|
|
325
|
-
|
|
326
|
-
// Register snapshot in Stack B
|
|
327
|
-
BoundStore.registerSnapshot("profile", "b1", boundsB, {}, ["stack-b"]);
|
|
328
|
-
|
|
329
|
-
// Query by stack key should return correct bounds
|
|
330
|
-
const fromStackA = BoundStore.getSnapshot("profile", "stack-a");
|
|
331
|
-
expect(fromStackA?.bounds).toEqual(boundsA);
|
|
332
|
-
|
|
333
|
-
const fromStackB = BoundStore.getSnapshot("profile", "stack-b");
|
|
334
|
-
expect(fromStackB?.bounds).toEqual(boundsB);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it("getActiveLink respects ancestor chain", () => {
|
|
338
|
-
// Navigation from Stack A to detail screen
|
|
339
|
-
BoundStore.setLinkSource("profile", "a1", createBounds(10, 10), {}, [
|
|
340
|
-
"stack-a",
|
|
341
|
-
]);
|
|
342
|
-
BoundStore.setLinkDestination("profile", "detail", createBounds(0, 0));
|
|
343
|
-
|
|
344
|
-
// Query by ancestor should find the link
|
|
345
|
-
const link = BoundStore.getActiveLink("profile", "stack-a");
|
|
346
|
-
expect(link?.source.screenKey).toBe("a1");
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
describe("Scenario: Rapid navigation A → B → C → pop → pop", () => {
|
|
351
|
-
it("link stack grows and getActiveLink finds correct link for each screen", () => {
|
|
352
|
-
// A → B
|
|
353
|
-
BoundStore.setLinkSource("card", "screen-a", createBounds(0, 0));
|
|
354
|
-
BoundStore.setLinkDestination("card", "screen-b", createBounds(100, 100));
|
|
355
|
-
|
|
356
|
-
// B → C
|
|
357
|
-
BoundStore.setLinkSource("card", "screen-b", createBounds(100, 100));
|
|
358
|
-
BoundStore.setLinkDestination("card", "screen-c", createBounds(200, 200));
|
|
359
|
-
|
|
360
|
-
// Most recent link is B → C
|
|
361
|
-
const latest = BoundStore.getActiveLink("card");
|
|
362
|
-
expect(latest?.source.screenKey).toBe("screen-b");
|
|
363
|
-
expect(latest?.destination?.screenKey).toBe("screen-c");
|
|
364
|
-
|
|
365
|
-
// Query from C (destination of B→C) = opening
|
|
366
|
-
const fromC = BoundStore.getActiveLink("card", "screen-c");
|
|
367
|
-
expect(fromC?.destination?.screenKey).toBe("screen-c");
|
|
368
|
-
|
|
369
|
-
// Query from B - B is source of B→C link, so isClosing=true
|
|
370
|
-
const fromB = BoundStore.getActiveLink("card", "screen-b");
|
|
371
|
-
expect(fromB?.source.screenKey).toBe("screen-b");
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
describe("Scenario: Global bounds (fullscreen target)", () => {
|
|
376
|
-
it("getActiveLink with no screenKey returns most recent for fullscreen", () => {
|
|
377
|
-
// Source exists, destination will be fullscreen (no specific screenKey needed)
|
|
378
|
-
BoundStore.setLinkSource(
|
|
379
|
-
"image",
|
|
380
|
-
"gallery",
|
|
381
|
-
createBounds(50, 50, 100, 100),
|
|
382
|
-
);
|
|
383
|
-
BoundStore.setLinkDestination(
|
|
384
|
-
"image",
|
|
385
|
-
"fullscreen-viewer",
|
|
386
|
-
createBounds(0, 0, 400, 800),
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
// Fullscreen target can get link without knowing screenKey
|
|
390
|
-
const link = BoundStore.getActiveLink("image");
|
|
391
|
-
expect(link).not.toBeNull();
|
|
392
|
-
expect(link?.destination?.screenKey).toBe("fullscreen-viewer");
|
|
393
|
-
});
|
|
394
|
-
});
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import type { ScreenTransitionState } from "../types/animation.types";
|
|
3
|
-
import { derivations } from "../utils/animation/derivations";
|
|
4
|
-
|
|
5
|
-
const createMockState = (
|
|
6
|
-
overrides: Partial<ScreenTransitionState> = {},
|
|
7
|
-
): ScreenTransitionState => ({
|
|
8
|
-
progress: 1,
|
|
9
|
-
closing: 0,
|
|
10
|
-
animating: 0,
|
|
11
|
-
entering: 1,
|
|
12
|
-
gesture: {
|
|
13
|
-
isDragging: 0,
|
|
14
|
-
x: 0,
|
|
15
|
-
y: 0,
|
|
16
|
-
normalizedX: 0,
|
|
17
|
-
normalizedY: 0,
|
|
18
|
-
isDismissing: 0,
|
|
19
|
-
direction: null,
|
|
20
|
-
},
|
|
21
|
-
route: { key: "test-route", name: "TestScreen" } as any,
|
|
22
|
-
...overrides,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe("derivations", () => {
|
|
26
|
-
describe("progress", () => {
|
|
27
|
-
it("returns current progress when no next screen", () => {
|
|
28
|
-
const result = derivations({
|
|
29
|
-
current: createMockState({ progress: 0.5 }),
|
|
30
|
-
});
|
|
31
|
-
expect(result.progress).toBe(0.5);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("combines current + next progress (0-2 range)", () => {
|
|
35
|
-
const result = derivations({
|
|
36
|
-
current: createMockState({ progress: 1 }),
|
|
37
|
-
next: createMockState({ progress: 0.5 }),
|
|
38
|
-
});
|
|
39
|
-
expect(result.progress).toBe(1.5);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("returns 2 when both screens fully transitioned", () => {
|
|
43
|
-
const result = derivations({
|
|
44
|
-
current: createMockState({ progress: 1 }),
|
|
45
|
-
next: createMockState({ progress: 1 }),
|
|
46
|
-
});
|
|
47
|
-
expect(result.progress).toBe(2);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe("focused", () => {
|
|
52
|
-
it("returns true when no next screen", () => {
|
|
53
|
-
const result = derivations({
|
|
54
|
-
current: createMockState(),
|
|
55
|
-
});
|
|
56
|
-
expect(result.focused).toBe(true);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("returns false when next screen exists", () => {
|
|
60
|
-
const result = derivations({
|
|
61
|
-
current: createMockState(),
|
|
62
|
-
next: createMockState(),
|
|
63
|
-
});
|
|
64
|
-
expect(result.focused).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe("active", () => {
|
|
69
|
-
it("returns current when focused (no next)", () => {
|
|
70
|
-
const current = createMockState({ progress: 0.3 });
|
|
71
|
-
const result = derivations({ current });
|
|
72
|
-
expect(result.active).toBe(current);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("returns next when not focused", () => {
|
|
76
|
-
const current = createMockState({ progress: 1 });
|
|
77
|
-
const next = createMockState({ progress: 0.5 });
|
|
78
|
-
const result = derivations({ current, next });
|
|
79
|
-
expect(result.active).toBe(next);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe("isActiveTransitioning", () => {
|
|
84
|
-
it("returns true when active screen is dragging", () => {
|
|
85
|
-
const result = derivations({
|
|
86
|
-
current: createMockState({
|
|
87
|
-
gesture: {
|
|
88
|
-
isDragging: 1,
|
|
89
|
-
x: 0,
|
|
90
|
-
y: 0,
|
|
91
|
-
normalizedX: 0,
|
|
92
|
-
normalizedY: 0,
|
|
93
|
-
isDismissing: 0,
|
|
94
|
-
direction: null,
|
|
95
|
-
},
|
|
96
|
-
}),
|
|
97
|
-
});
|
|
98
|
-
expect(result.isActiveTransitioning).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("returns true when active screen is animating", () => {
|
|
102
|
-
const result = derivations({
|
|
103
|
-
current: createMockState({ animating: 1 }),
|
|
104
|
-
});
|
|
105
|
-
expect(result.isActiveTransitioning).toBe(true);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("returns false when not dragging or animating", () => {
|
|
109
|
-
const result = derivations({
|
|
110
|
-
current: createMockState({ animating: 0 }),
|
|
111
|
-
});
|
|
112
|
-
expect(result.isActiveTransitioning).toBe(false);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe("isDismissing", () => {
|
|
117
|
-
it("returns true when gesture isDismissing", () => {
|
|
118
|
-
const result = derivations({
|
|
119
|
-
current: createMockState({
|
|
120
|
-
gesture: {
|
|
121
|
-
isDragging: 0,
|
|
122
|
-
x: 0,
|
|
123
|
-
y: 0,
|
|
124
|
-
normalizedX: 0,
|
|
125
|
-
normalizedY: 0,
|
|
126
|
-
isDismissing: 1,
|
|
127
|
-
direction: null,
|
|
128
|
-
},
|
|
129
|
-
}),
|
|
130
|
-
});
|
|
131
|
-
expect(result.isDismissing).toBe(true);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("returns true when closing flag is set", () => {
|
|
135
|
-
const result = derivations({
|
|
136
|
-
current: createMockState({ closing: 1 }),
|
|
137
|
-
});
|
|
138
|
-
expect(result.isDismissing).toBe(true);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("returns false when not dismissing or closing", () => {
|
|
142
|
-
const result = derivations({
|
|
143
|
-
current: createMockState(),
|
|
144
|
-
});
|
|
145
|
-
expect(result.isDismissing).toBe(false);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("checks active screen (next) for dismissing state", () => {
|
|
149
|
-
const result = derivations({
|
|
150
|
-
current: createMockState(),
|
|
151
|
-
next: createMockState({ closing: 1 }),
|
|
152
|
-
});
|
|
153
|
-
expect(result.isDismissing).toBe(true);
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
});
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import { determineDismissal } from "../utils/gesture/determine-dismissal";
|
|
3
|
-
|
|
4
|
-
describe("determineDismissal", () => {
|
|
5
|
-
const dimensions = { width: 320, height: 640 };
|
|
6
|
-
|
|
7
|
-
it("dismisses when horizontal translation exceeds the threshold", () => {
|
|
8
|
-
const { shouldDismiss } = determineDismissal({
|
|
9
|
-
event: {
|
|
10
|
-
translationX: 170,
|
|
11
|
-
translationY: 0,
|
|
12
|
-
velocityX: 0,
|
|
13
|
-
velocityY: 0,
|
|
14
|
-
},
|
|
15
|
-
directions: {
|
|
16
|
-
vertical: false,
|
|
17
|
-
verticalInverted: false,
|
|
18
|
-
horizontal: true,
|
|
19
|
-
horizontalInverted: false,
|
|
20
|
-
},
|
|
21
|
-
dimensions,
|
|
22
|
-
gestureVelocityImpact: 0.3,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
expect(shouldDismiss).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("ignores movement in disallowed directions", () => {
|
|
29
|
-
const { shouldDismiss } = determineDismissal({
|
|
30
|
-
event: {
|
|
31
|
-
translationX: 200,
|
|
32
|
-
translationY: 0,
|
|
33
|
-
velocityX: 0,
|
|
34
|
-
velocityY: 0,
|
|
35
|
-
},
|
|
36
|
-
directions: {
|
|
37
|
-
vertical: true,
|
|
38
|
-
verticalInverted: false,
|
|
39
|
-
horizontal: false,
|
|
40
|
-
horizontalInverted: false,
|
|
41
|
-
},
|
|
42
|
-
dimensions,
|
|
43
|
-
gestureVelocityImpact: 0.3,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
expect(shouldDismiss).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("dismisses vertical gestures when velocity pushes the projection past the threshold", () => {
|
|
50
|
-
const { shouldDismiss } = determineDismissal({
|
|
51
|
-
event: {
|
|
52
|
-
translationX: 0,
|
|
53
|
-
translationY: 40,
|
|
54
|
-
velocityX: 0,
|
|
55
|
-
velocityY: 1800,
|
|
56
|
-
},
|
|
57
|
-
directions: {
|
|
58
|
-
vertical: true,
|
|
59
|
-
verticalInverted: false,
|
|
60
|
-
horizontal: false,
|
|
61
|
-
horizontalInverted: false,
|
|
62
|
-
},
|
|
63
|
-
dimensions,
|
|
64
|
-
gestureVelocityImpact: 0.3,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
expect(shouldDismiss).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("respects inverted horizontal directions", () => {
|
|
71
|
-
const { shouldDismiss } = determineDismissal({
|
|
72
|
-
event: {
|
|
73
|
-
translationX: -160,
|
|
74
|
-
translationY: 0,
|
|
75
|
-
velocityX: -700,
|
|
76
|
-
velocityY: 0,
|
|
77
|
-
},
|
|
78
|
-
directions: {
|
|
79
|
-
vertical: false,
|
|
80
|
-
verticalInverted: false,
|
|
81
|
-
horizontal: false,
|
|
82
|
-
horizontalInverted: true,
|
|
83
|
-
},
|
|
84
|
-
dimensions,
|
|
85
|
-
gestureVelocityImpact: 0.25,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
expect(shouldDismiss).toBe(true);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("returns false when movement never exceeds the composite threshold", () => {
|
|
92
|
-
const { shouldDismiss } = determineDismissal({
|
|
93
|
-
event: {
|
|
94
|
-
translationX: 30,
|
|
95
|
-
translationY: 0,
|
|
96
|
-
velocityX: 100,
|
|
97
|
-
velocityY: 0,
|
|
98
|
-
},
|
|
99
|
-
directions: {
|
|
100
|
-
vertical: false,
|
|
101
|
-
verticalInverted: false,
|
|
102
|
-
horizontal: true,
|
|
103
|
-
horizontalInverted: false,
|
|
104
|
-
},
|
|
105
|
-
dimensions,
|
|
106
|
-
gestureVelocityImpact: 0.2,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
expect(shouldDismiss).toBe(false);
|
|
110
|
-
});
|
|
111
|
-
});
|