one 1.2.79 → 1.2.81

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.
Files changed (178) hide show
  1. package/dist/cjs/Root.cjs +1 -1
  2. package/dist/cjs/Root.js +1 -1
  3. package/dist/cjs/Root.js.map +1 -1
  4. package/dist/cjs/Root.native.js +1 -1
  5. package/dist/cjs/Root.native.js.map +1 -1
  6. package/dist/cjs/drawer.cjs +27 -0
  7. package/dist/cjs/drawer.js +22 -0
  8. package/dist/cjs/drawer.js.map +6 -0
  9. package/dist/cjs/drawer.native.js +30 -0
  10. package/dist/cjs/drawer.native.js.map +1 -0
  11. package/dist/cjs/hooks.cjs +25 -2
  12. package/dist/cjs/hooks.js +23 -2
  13. package/dist/cjs/hooks.js.map +1 -1
  14. package/dist/cjs/hooks.native.js +147 -14
  15. package/dist/cjs/hooks.native.js.map +1 -1
  16. package/dist/cjs/hooks.test.cjs +73 -0
  17. package/dist/cjs/hooks.test.js +98 -0
  18. package/dist/cjs/hooks.test.js.map +6 -0
  19. package/dist/cjs/hooks.test.native.js +194 -0
  20. package/dist/cjs/hooks.test.native.js.map +1 -0
  21. package/dist/cjs/index.cjs +1 -0
  22. package/dist/cjs/index.js +1 -0
  23. package/dist/cjs/index.js.map +1 -1
  24. package/dist/cjs/index.native.js +1 -0
  25. package/dist/cjs/index.native.js.map +1 -1
  26. package/dist/cjs/link/prefetchIntent.cjs +122 -0
  27. package/dist/cjs/link/prefetchIntent.js +85 -0
  28. package/dist/cjs/link/prefetchIntent.js.map +6 -0
  29. package/dist/cjs/link/prefetchIntent.native.js +155 -0
  30. package/dist/cjs/link/prefetchIntent.native.js.map +1 -0
  31. package/dist/cjs/link/prefetchIntent.test.cjs +217 -0
  32. package/dist/cjs/link/prefetchIntent.test.js +149 -0
  33. package/dist/cjs/link/prefetchIntent.test.js.map +6 -0
  34. package/dist/cjs/link/prefetchIntent.test.native.js +239 -0
  35. package/dist/cjs/link/prefetchIntent.test.native.js.map +1 -0
  36. package/dist/cjs/link/prefetchViewport.cjs +67 -0
  37. package/dist/cjs/link/prefetchViewport.js +55 -0
  38. package/dist/cjs/link/prefetchViewport.js.map +6 -0
  39. package/dist/cjs/link/prefetchViewport.native.js +83 -0
  40. package/dist/cjs/link/prefetchViewport.native.js.map +1 -0
  41. package/dist/cjs/link/prefetchViewport.test.cjs +57 -0
  42. package/dist/cjs/link/prefetchViewport.test.js +59 -0
  43. package/dist/cjs/link/prefetchViewport.test.js.map +6 -0
  44. package/dist/cjs/link/prefetchViewport.test.native.js +85 -0
  45. package/dist/cjs/link/prefetchViewport.test.native.js.map +1 -0
  46. package/dist/cjs/router/findRouteNode.cjs +26 -0
  47. package/dist/cjs/router/findRouteNode.js +28 -0
  48. package/dist/cjs/router/findRouteNode.js.map +1 -1
  49. package/dist/cjs/router/findRouteNode.native.js +31 -0
  50. package/dist/cjs/router/findRouteNode.native.js.map +1 -1
  51. package/dist/cjs/router/router.cjs +11 -13
  52. package/dist/cjs/router/router.js +8 -10
  53. package/dist/cjs/router/router.js.map +2 -2
  54. package/dist/cjs/router/router.native.js +38 -122
  55. package/dist/cjs/router/router.native.js.map +1 -1
  56. package/dist/cjs/views/PreloadLinks.cjs +102 -18
  57. package/dist/cjs/views/PreloadLinks.js +95 -19
  58. package/dist/cjs/views/PreloadLinks.js.map +1 -1
  59. package/dist/cjs/vite/one.cjs +3 -0
  60. package/dist/cjs/vite/one.js +3 -0
  61. package/dist/cjs/vite/one.js.map +1 -1
  62. package/dist/cjs/vite/one.native.js +4 -0
  63. package/dist/cjs/vite/one.native.js.map +1 -1
  64. package/dist/esm/Root.js +1 -1
  65. package/dist/esm/Root.js.map +1 -1
  66. package/dist/esm/Root.mjs +1 -1
  67. package/dist/esm/Root.mjs.map +1 -1
  68. package/dist/esm/Root.native.js +1 -1
  69. package/dist/esm/Root.native.js.map +1 -1
  70. package/dist/esm/drawer.js +6 -0
  71. package/dist/esm/drawer.js.map +6 -0
  72. package/dist/esm/drawer.mjs +3 -0
  73. package/dist/esm/drawer.mjs.map +1 -0
  74. package/dist/esm/drawer.native.js +3 -0
  75. package/dist/esm/drawer.native.js.map +1 -0
  76. package/dist/esm/hooks.js +23 -2
  77. package/dist/esm/hooks.js.map +1 -1
  78. package/dist/esm/hooks.mjs +25 -3
  79. package/dist/esm/hooks.mjs.map +1 -1
  80. package/dist/esm/hooks.native.js +147 -15
  81. package/dist/esm/hooks.native.js.map +1 -1
  82. package/dist/esm/hooks.test.js +98 -0
  83. package/dist/esm/hooks.test.js.map +6 -0
  84. package/dist/esm/hooks.test.mjs +74 -0
  85. package/dist/esm/hooks.test.mjs.map +1 -0
  86. package/dist/esm/hooks.test.native.js +192 -0
  87. package/dist/esm/hooks.test.native.js.map +1 -0
  88. package/dist/esm/index.js +2 -0
  89. package/dist/esm/index.js.map +1 -1
  90. package/dist/esm/index.mjs +2 -2
  91. package/dist/esm/index.mjs.map +1 -1
  92. package/dist/esm/index.native.js +2 -2
  93. package/dist/esm/index.native.js.map +1 -1
  94. package/dist/esm/link/prefetchIntent.js +69 -0
  95. package/dist/esm/link/prefetchIntent.js.map +6 -0
  96. package/dist/esm/link/prefetchIntent.mjs +97 -0
  97. package/dist/esm/link/prefetchIntent.mjs.map +1 -0
  98. package/dist/esm/link/prefetchIntent.native.js +127 -0
  99. package/dist/esm/link/prefetchIntent.native.js.map +1 -0
  100. package/dist/esm/link/prefetchIntent.test.js +150 -0
  101. package/dist/esm/link/prefetchIntent.test.js.map +6 -0
  102. package/dist/esm/link/prefetchIntent.test.mjs +218 -0
  103. package/dist/esm/link/prefetchIntent.test.mjs.map +1 -0
  104. package/dist/esm/link/prefetchIntent.test.native.js +237 -0
  105. package/dist/esm/link/prefetchIntent.test.native.js.map +1 -0
  106. package/dist/esm/link/prefetchViewport.js +39 -0
  107. package/dist/esm/link/prefetchViewport.js.map +6 -0
  108. package/dist/esm/link/prefetchViewport.mjs +42 -0
  109. package/dist/esm/link/prefetchViewport.mjs.map +1 -0
  110. package/dist/esm/link/prefetchViewport.native.js +55 -0
  111. package/dist/esm/link/prefetchViewport.native.js.map +1 -0
  112. package/dist/esm/link/prefetchViewport.test.js +60 -0
  113. package/dist/esm/link/prefetchViewport.test.js.map +6 -0
  114. package/dist/esm/link/prefetchViewport.test.mjs +58 -0
  115. package/dist/esm/link/prefetchViewport.test.mjs.map +1 -0
  116. package/dist/esm/link/prefetchViewport.test.native.js +83 -0
  117. package/dist/esm/link/prefetchViewport.test.native.js.map +1 -0
  118. package/dist/esm/router/findRouteNode.js +28 -0
  119. package/dist/esm/router/findRouteNode.js.map +1 -1
  120. package/dist/esm/router/findRouteNode.mjs +26 -1
  121. package/dist/esm/router/findRouteNode.mjs.map +1 -1
  122. package/dist/esm/router/findRouteNode.native.js +31 -1
  123. package/dist/esm/router/findRouteNode.native.js.map +1 -1
  124. package/dist/esm/router/router.js +10 -11
  125. package/dist/esm/router/router.js.map +2 -2
  126. package/dist/esm/router/router.mjs +12 -14
  127. package/dist/esm/router/router.mjs.map +1 -1
  128. package/dist/esm/router/router.native.js +39 -123
  129. package/dist/esm/router/router.native.js.map +1 -1
  130. package/dist/esm/views/PreloadLinks.js +86 -17
  131. package/dist/esm/views/PreloadLinks.js.map +1 -1
  132. package/dist/esm/views/PreloadLinks.mjs +87 -14
  133. package/dist/esm/views/PreloadLinks.mjs.map +1 -1
  134. package/dist/esm/vite/one.js +3 -0
  135. package/dist/esm/vite/one.js.map +1 -1
  136. package/dist/esm/vite/one.mjs +3 -0
  137. package/dist/esm/vite/one.mjs.map +1 -1
  138. package/dist/esm/vite/one.native.js +4 -0
  139. package/dist/esm/vite/one.native.js.map +1 -1
  140. package/package.json +33 -12
  141. package/src/Root.tsx +1 -1
  142. package/src/drawer.ts +1 -0
  143. package/src/hooks.test.ts +157 -0
  144. package/src/hooks.tsx +79 -23
  145. package/src/index.ts +1 -0
  146. package/src/link/prefetchIntent.test.ts +416 -0
  147. package/src/link/prefetchIntent.ts +174 -0
  148. package/src/link/prefetchViewport.test.ts +120 -0
  149. package/src/link/prefetchViewport.ts +62 -0
  150. package/src/router/findRouteNode.ts +67 -0
  151. package/src/router/router.ts +68 -41
  152. package/src/views/PreloadLinks.tsx +156 -20
  153. package/src/vite/one.ts +4 -0
  154. package/src/vite/types.ts +12 -0
  155. package/types/drawer.d.ts +2 -0
  156. package/types/drawer.d.ts.map +1 -0
  157. package/types/hooks.d.ts +22 -0
  158. package/types/hooks.d.ts.map +1 -1
  159. package/types/hooks.test.d.ts +2 -0
  160. package/types/hooks.test.d.ts.map +1 -0
  161. package/types/index.d.ts +1 -1
  162. package/types/index.d.ts.map +1 -1
  163. package/types/link/prefetchIntent.d.ts +43 -0
  164. package/types/link/prefetchIntent.d.ts.map +1 -0
  165. package/types/link/prefetchIntent.test.d.ts +2 -0
  166. package/types/link/prefetchIntent.test.d.ts.map +1 -0
  167. package/types/link/prefetchViewport.d.ts +16 -0
  168. package/types/link/prefetchViewport.d.ts.map +1 -0
  169. package/types/link/prefetchViewport.test.d.ts +2 -0
  170. package/types/link/prefetchViewport.test.d.ts.map +1 -0
  171. package/types/router/findRouteNode.d.ts +11 -0
  172. package/types/router/findRouteNode.d.ts.map +1 -1
  173. package/types/router/router.d.ts.map +1 -1
  174. package/types/views/PreloadLinks.d.ts +9 -0
  175. package/types/views/PreloadLinks.d.ts.map +1 -1
  176. package/types/vite/one.d.ts.map +1 -1
  177. package/types/vite/types.d.ts +11 -0
  178. package/types/vite/types.d.ts.map +1 -1
@@ -0,0 +1,150 @@
1
+ import { describe, expect, it, beforeEach } from "vitest";
2
+ import { createPrefetchIntent } from "./prefetchIntent";
3
+ const rect = (x, y, w = 100, h = 40) => ({
4
+ left: x,
5
+ top: y,
6
+ right: x + w,
7
+ bottom: y + h,
8
+ width: w,
9
+ height: h,
10
+ x,
11
+ y,
12
+ toJSON: () => ({})
13
+ });
14
+ describe("prefetchIntent", () => {
15
+ let prefetched, intent;
16
+ beforeEach(() => {
17
+ prefetched = [], intent = createPrefetchIntent({
18
+ onPrefetch: (href) => prefetched.push(href)
19
+ });
20
+ }), describe("basic targeting", () => {
21
+ it("prefetches when moving directly toward a link", () => {
22
+ intent.setRects([{ r: rect(500, 300), h: "/about" }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/about"]);
23
+ }), it("does not prefetch when moving away from a link", () => {
24
+ intent.setRects([{ r: rect(500, 300), h: "/about" }]), intent.move(400, 300, 0, 0), intent.move(380, 300, -20, 0), intent.move(350, 300, -30, 0), expect(prefetched).toEqual([]);
25
+ }), it("does not prefetch when moving perpendicular to a link", () => {
26
+ intent.setRects([{ r: rect(500, 300), h: "/about" }]), intent.move(200, 300, 0, 0), intent.move(200, 330, 0, 30), intent.move(200, 370, 0, 40), expect(prefetched).toEqual([]);
27
+ }), it("prefetches link at long distance with good aim", () => {
28
+ intent.setRects([{ r: rect(500, 280), h: "/far" }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/far"]);
29
+ }), it("does not prefetch when aim is slightly off at long distance", () => {
30
+ intent.setRects([{ r: rect(600, 300), h: "/far" }]), intent.move(100, 300, 0, 0), intent.move(140, 320, 40, 20), intent.move(200, 360, 60, 40), expect(prefetched).toEqual([]);
31
+ });
32
+ }), describe("prevents over-fetching", () => {
33
+ it("only prefetches each href once", () => {
34
+ intent.setRects([{ r: rect(500, 300), h: "/about" }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/about"]), intent.move(100, 300, -100, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/about"]);
35
+ }), it("removes prefetched links from future consideration", () => {
36
+ intent.setRects([
37
+ { r: rect(400, 300), h: "/first" },
38
+ { r: rect(600, 300), h: "/second" }
39
+ ]), intent.move(100, 300, 0, 0), intent.move(150, 300, 50, 0), expect(prefetched).toEqual(["/first"]), intent.move(250, 300, 100, 0), intent.move(350, 300, 100, 0), expect(prefetched).toEqual(["/first", "/second"]);
40
+ });
41
+ }), describe("winner-takes-all with clustered links", () => {
42
+ it("only prefetches the best target when multiple links are in path", () => {
43
+ intent.setRects([
44
+ { r: rect(500, 280), h: "/top" },
45
+ { r: rect(500, 320), h: "/middle" },
46
+ { r: rect(500, 360), h: "/bottom" }
47
+ ]), intent.move(100, 340, 0, 0), intent.move(150, 340, 50, 0), expect(prefetched.length).toBe(1), expect(prefetched[0]).toBe("/middle");
48
+ }), it("handles dense nav with 20 links", () => {
49
+ const links = Array.from({ length: 20 }, (_, i) => ({
50
+ r: rect(100 + i * 60, 50, 50, 30),
51
+ h: `/nav-${i}`
52
+ }));
53
+ intent.setRects(links), intent.move(700, 200, 0, 0), intent.move(700, 150, 0, -50), intent.move(700, 100, 0, -50), expect(prefetched.length).toBe(1), expect(prefetched[0]).toBe("/nav-10");
54
+ }), it("picks closer link when two are roughly aligned", () => {
55
+ intent.setRects([
56
+ { r: rect(300, 300), h: "/near" },
57
+ { r: rect(600, 300), h: "/far" }
58
+ ]), intent.move(100, 320, 0, 0), intent.move(150, 320, 50, 0), expect(prefetched).toEqual(["/near"]);
59
+ }), it("picks better-aimed link over closer link", () => {
60
+ intent.setRects([
61
+ { r: rect(200, 400), h: "/close-but-off" },
62
+ // closer but significantly off-axis
63
+ { r: rect(400, 300), h: "/far-but-aimed" }
64
+ // farther but dead-on
65
+ ]), intent.move(100, 300, 0, 0), intent.move(150, 300, 50, 0), expect(prefetched).toEqual(["/far-but-aimed"]);
66
+ });
67
+ }), describe("velocity and smoothing", () => {
68
+ it("does not prefetch when mouse is stationary", () => {
69
+ intent.setRects([{ r: rect(500, 300), h: "/about" }]), intent.move(400, 300, 0, 0), intent.move(400, 300, 0, 0), intent.move(400, 300, 0, 0), expect(prefetched).toEqual([]);
70
+ }), it("does not prefetch when mouse is moving slowly", () => {
71
+ intent.setRects([{ r: rect(500, 300), h: "/about" }]), intent.move(400, 300, 0, 0), intent.move(401, 300, 1, 0), intent.move(402, 300, 1, 0), expect(prefetched).toEqual([]);
72
+ }), it("smooths velocity to avoid jitter false positives", () => {
73
+ intent.setRects([{ r: rect(500, 300), h: "/about" }]), intent.move(100, 300, 0, 0), intent.move(130, 305, 30, 5), intent.move(160, 298, 30, -7), intent.move(195, 303, 35, 5), expect(prefetched).toEqual(["/about"]);
74
+ });
75
+ }), describe("diagonal movement", () => {
76
+ it("prefetches with diagonal approach", () => {
77
+ intent.setRects([{ r: rect(500, 500), h: "/corner" }]), intent.move(200, 200, 0, 0), intent.move(240, 240, 40, 40), intent.move(290, 290, 50, 50), expect(prefetched).toEqual(["/corner"]);
78
+ }), it("handles angled approach to horizontal nav", () => {
79
+ intent.setRects([
80
+ { r: rect(400, 50), h: "/link1" },
81
+ { r: rect(500, 50), h: "/link2" },
82
+ { r: rect(600, 50), h: "/link3" }
83
+ ]), intent.move(300, 300, 0, 0), intent.move(340, 260, 40, -40), intent.move(390, 210, 50, -50), expect(prefetched.length).toBe(1);
84
+ });
85
+ }), describe("edge cases", () => {
86
+ it("handles empty rect list", () => {
87
+ intent.setRects([]), intent.move(100, 100, 0, 0), intent.move(150, 100, 50, 0), expect(prefetched).toEqual([]);
88
+ }), it("handles link at cursor position", () => {
89
+ intent.setRects([{ r: rect(100, 100), h: "/here" }]), intent.move(150, 120, 0, 0), intent.move(160, 120, 10, 0), expect(prefetched.length).toBeLessThanOrEqual(1);
90
+ }), it("cleans up when observe returns cleanup function", () => {
91
+ intent.observe(
92
+ { getBoundingClientRect: () => rect(500, 300) },
93
+ "/test"
94
+ )(), intent.move(100, 300, 0, 0), intent.move(150, 300, 50, 0), expect(prefetched).toEqual([]);
95
+ }), it("re-enables prefetch for href after cleanup", () => {
96
+ const el = { getBoundingClientRect: () => rect(500, 300) }, cleanup = intent.observe(el, "/test");
97
+ intent.setRects([{ r: rect(500, 300), h: "/test" }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/test"]), cleanup(), prefetched.length = 0, intent.observe(el, "/test"), intent.setRects([{ r: rect(500, 300), h: "/test" }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/test"]);
98
+ });
99
+ }), describe("reach configuration", () => {
100
+ it("respects maxReach option", () => {
101
+ const shortReach = createPrefetchIntent({
102
+ onPrefetch: (href) => prefetched.push(href),
103
+ maxReach: 200
104
+ });
105
+ shortReach.setRects([{ r: rect(500, 300), h: "/far" }]), shortReach.move(100, 300, 0, 0), shortReach.move(150, 300, 50, 0), expect(prefetched).toEqual([]);
106
+ }), it("respects perpWeight option for aim strictness", () => {
107
+ const strictAim = createPrefetchIntent({
108
+ onPrefetch: (href) => prefetched.push(href),
109
+ perpWeight: 10
110
+ // very strict
111
+ });
112
+ strictAim.setRects([{ r: rect(400, 350), h: "/off" }]), strictAim.move(100, 300, 0, 0), strictAim.move(150, 300, 50, 0), expect(prefetched).toEqual([]);
113
+ });
114
+ }), describe("memory and performance", () => {
115
+ it("does not leak elements after cleanup", () => {
116
+ const cleanups = [];
117
+ for (let i = 0; i < 100; i++) {
118
+ const el = {};
119
+ cleanups.push(intent.observe(el, `/page-${i}`));
120
+ }
121
+ expect(intent.nodes.size).toBe(100), cleanups.forEach((c) => c()), expect(intent.nodes.size).toBe(0), expect(intent.done.size).toBe(0);
122
+ }), it("handles rapid observe/unobserve cycles", () => {
123
+ const el = {};
124
+ for (let i = 0; i < 100; i++)
125
+ intent.observe(el, "/test")();
126
+ expect(intent.nodes.size).toBe(0);
127
+ }), it("processes 100 links efficiently", () => {
128
+ const links = [];
129
+ for (let row = 0; row < 10; row++)
130
+ for (let col = 0; col < 10; col++)
131
+ links.push({
132
+ r: rect(100 + col * 80, 100 + row * 50, 60, 30),
133
+ h: `/link-${row}-${col}`
134
+ });
135
+ intent.setRects(links);
136
+ const start = performance.now();
137
+ for (let i = 0; i < 100; i++)
138
+ intent.move(50 + i * 5, 300, 5, 0);
139
+ const elapsed = performance.now() - start;
140
+ expect(elapsed).toBeLessThan(50);
141
+ }), it("removes prefetched links from rects to speed up future checks", () => {
142
+ const links = Array.from({ length: 10 }, (_, i) => ({
143
+ r: rect(200 + i * 100, 300),
144
+ h: `/link-${i}`
145
+ }));
146
+ intent.setRects(links), intent.move(100, 320, 0, 0), intent.move(150, 320, 50, 0), expect(prefetched.length).toBe(1), intent.move(100, 320, -50, 0), intent.move(150, 320, 50, 0), intent.move(200, 320, 50, 0), expect(prefetched.length).toBe(2), expect(prefetched[0]).not.toBe(prefetched[1]);
147
+ });
148
+ });
149
+ });
150
+ //# sourceMappingURL=prefetchIntent.test.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/link/prefetchIntent.test.ts"],
4
+ "mappings": "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",
5
+ "names": []
6
+ }
@@ -0,0 +1,218 @@
1
+ import { describe, expect, it, beforeEach } from "vitest";
2
+ import { createPrefetchIntent } from "./prefetchIntent.mjs";
3
+ const rect = (x, y, w = 100, h = 40) => ({
4
+ left: x,
5
+ top: y,
6
+ right: x + w,
7
+ bottom: y + h,
8
+ width: w,
9
+ height: h,
10
+ x,
11
+ y,
12
+ toJSON: () => ({})
13
+ });
14
+ describe("prefetchIntent", () => {
15
+ let prefetched, intent;
16
+ beforeEach(() => {
17
+ prefetched = [], intent = createPrefetchIntent({
18
+ onPrefetch: href => prefetched.push(href)
19
+ });
20
+ }), describe("basic targeting", () => {
21
+ it("prefetches when moving directly toward a link", () => {
22
+ intent.setRects([{
23
+ r: rect(500, 300),
24
+ h: "/about"
25
+ }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/about"]);
26
+ }), it("does not prefetch when moving away from a link", () => {
27
+ intent.setRects([{
28
+ r: rect(500, 300),
29
+ h: "/about"
30
+ }]), intent.move(400, 300, 0, 0), intent.move(380, 300, -20, 0), intent.move(350, 300, -30, 0), expect(prefetched).toEqual([]);
31
+ }), it("does not prefetch when moving perpendicular to a link", () => {
32
+ intent.setRects([{
33
+ r: rect(500, 300),
34
+ h: "/about"
35
+ }]), intent.move(200, 300, 0, 0), intent.move(200, 330, 0, 30), intent.move(200, 370, 0, 40), expect(prefetched).toEqual([]);
36
+ }), it("prefetches link at long distance with good aim", () => {
37
+ intent.setRects([{
38
+ r: rect(500, 280),
39
+ h: "/far"
40
+ }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/far"]);
41
+ }), it("does not prefetch when aim is slightly off at long distance", () => {
42
+ intent.setRects([{
43
+ r: rect(600, 300),
44
+ h: "/far"
45
+ }]), intent.move(100, 300, 0, 0), intent.move(140, 320, 40, 20), intent.move(200, 360, 60, 40), expect(prefetched).toEqual([]);
46
+ });
47
+ }), describe("prevents over-fetching", () => {
48
+ it("only prefetches each href once", () => {
49
+ intent.setRects([{
50
+ r: rect(500, 300),
51
+ h: "/about"
52
+ }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/about"]), intent.move(100, 300, -100, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/about"]);
53
+ }), it("removes prefetched links from future consideration", () => {
54
+ intent.setRects([{
55
+ r: rect(400, 300),
56
+ h: "/first"
57
+ }, {
58
+ r: rect(600, 300),
59
+ h: "/second"
60
+ }]), intent.move(100, 300, 0, 0), intent.move(150, 300, 50, 0), expect(prefetched).toEqual(["/first"]), intent.move(250, 300, 100, 0), intent.move(350, 300, 100, 0), expect(prefetched).toEqual(["/first", "/second"]);
61
+ });
62
+ }), describe("winner-takes-all with clustered links", () => {
63
+ it("only prefetches the best target when multiple links are in path", () => {
64
+ intent.setRects([{
65
+ r: rect(500, 280),
66
+ h: "/top"
67
+ }, {
68
+ r: rect(500, 320),
69
+ h: "/middle"
70
+ }, {
71
+ r: rect(500, 360),
72
+ h: "/bottom"
73
+ }]), intent.move(100, 340, 0, 0), intent.move(150, 340, 50, 0), expect(prefetched.length).toBe(1), expect(prefetched[0]).toBe("/middle");
74
+ }), it("handles dense nav with 20 links", () => {
75
+ const links = Array.from({
76
+ length: 20
77
+ }, (_, i) => ({
78
+ r: rect(100 + i * 60, 50, 50, 30),
79
+ h: `/nav-${i}`
80
+ }));
81
+ intent.setRects(links), intent.move(700, 200, 0, 0), intent.move(700, 150, 0, -50), intent.move(700, 100, 0, -50), expect(prefetched.length).toBe(1), expect(prefetched[0]).toBe("/nav-10");
82
+ }), it("picks closer link when two are roughly aligned", () => {
83
+ intent.setRects([{
84
+ r: rect(300, 300),
85
+ h: "/near"
86
+ }, {
87
+ r: rect(600, 300),
88
+ h: "/far"
89
+ }]), intent.move(100, 320, 0, 0), intent.move(150, 320, 50, 0), expect(prefetched).toEqual(["/near"]);
90
+ }), it("picks better-aimed link over closer link", () => {
91
+ intent.setRects([{
92
+ r: rect(200, 400),
93
+ h: "/close-but-off"
94
+ },
95
+ // closer but significantly off-axis
96
+ {
97
+ r: rect(400, 300),
98
+ h: "/far-but-aimed"
99
+ }
100
+ // farther but dead-on
101
+ ]), intent.move(100, 300, 0, 0), intent.move(150, 300, 50, 0), expect(prefetched).toEqual(["/far-but-aimed"]);
102
+ });
103
+ }), describe("velocity and smoothing", () => {
104
+ it("does not prefetch when mouse is stationary", () => {
105
+ intent.setRects([{
106
+ r: rect(500, 300),
107
+ h: "/about"
108
+ }]), intent.move(400, 300, 0, 0), intent.move(400, 300, 0, 0), intent.move(400, 300, 0, 0), expect(prefetched).toEqual([]);
109
+ }), it("does not prefetch when mouse is moving slowly", () => {
110
+ intent.setRects([{
111
+ r: rect(500, 300),
112
+ h: "/about"
113
+ }]), intent.move(400, 300, 0, 0), intent.move(401, 300, 1, 0), intent.move(402, 300, 1, 0), expect(prefetched).toEqual([]);
114
+ }), it("smooths velocity to avoid jitter false positives", () => {
115
+ intent.setRects([{
116
+ r: rect(500, 300),
117
+ h: "/about"
118
+ }]), intent.move(100, 300, 0, 0), intent.move(130, 305, 30, 5), intent.move(160, 298, 30, -7), intent.move(195, 303, 35, 5), expect(prefetched).toEqual(["/about"]);
119
+ });
120
+ }), describe("diagonal movement", () => {
121
+ it("prefetches with diagonal approach", () => {
122
+ intent.setRects([{
123
+ r: rect(500, 500),
124
+ h: "/corner"
125
+ }]), intent.move(200, 200, 0, 0), intent.move(240, 240, 40, 40), intent.move(290, 290, 50, 50), expect(prefetched).toEqual(["/corner"]);
126
+ }), it("handles angled approach to horizontal nav", () => {
127
+ intent.setRects([{
128
+ r: rect(400, 50),
129
+ h: "/link1"
130
+ }, {
131
+ r: rect(500, 50),
132
+ h: "/link2"
133
+ }, {
134
+ r: rect(600, 50),
135
+ h: "/link3"
136
+ }]), intent.move(300, 300, 0, 0), intent.move(340, 260, 40, -40), intent.move(390, 210, 50, -50), expect(prefetched.length).toBe(1);
137
+ });
138
+ }), describe("edge cases", () => {
139
+ it("handles empty rect list", () => {
140
+ intent.setRects([]), intent.move(100, 100, 0, 0), intent.move(150, 100, 50, 0), expect(prefetched).toEqual([]);
141
+ }), it("handles link at cursor position", () => {
142
+ intent.setRects([{
143
+ r: rect(100, 100),
144
+ h: "/here"
145
+ }]), intent.move(150, 120, 0, 0), intent.move(160, 120, 10, 0), expect(prefetched.length).toBeLessThanOrEqual(1);
146
+ }), it("cleans up when observe returns cleanup function", () => {
147
+ intent.observe({
148
+ getBoundingClientRect: () => rect(500, 300)
149
+ }, "/test")(), intent.move(100, 300, 0, 0), intent.move(150, 300, 50, 0), expect(prefetched).toEqual([]);
150
+ }), it("re-enables prefetch for href after cleanup", () => {
151
+ const el = {
152
+ getBoundingClientRect: () => rect(500, 300)
153
+ },
154
+ cleanup = intent.observe(el, "/test");
155
+ intent.setRects([{
156
+ r: rect(500, 300),
157
+ h: "/test"
158
+ }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/test"]), cleanup(), prefetched.length = 0, intent.observe(el, "/test"), intent.setRects([{
159
+ r: rect(500, 300),
160
+ h: "/test"
161
+ }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/test"]);
162
+ });
163
+ }), describe("reach configuration", () => {
164
+ it("respects maxReach option", () => {
165
+ const shortReach = createPrefetchIntent({
166
+ onPrefetch: href => prefetched.push(href),
167
+ maxReach: 200
168
+ });
169
+ shortReach.setRects([{
170
+ r: rect(500, 300),
171
+ h: "/far"
172
+ }]), shortReach.move(100, 300, 0, 0), shortReach.move(150, 300, 50, 0), expect(prefetched).toEqual([]);
173
+ }), it("respects perpWeight option for aim strictness", () => {
174
+ const strictAim = createPrefetchIntent({
175
+ onPrefetch: href => prefetched.push(href),
176
+ perpWeight: 10
177
+ // very strict
178
+ });
179
+ strictAim.setRects([{
180
+ r: rect(400, 350),
181
+ h: "/off"
182
+ }]), strictAim.move(100, 300, 0, 0), strictAim.move(150, 300, 50, 0), expect(prefetched).toEqual([]);
183
+ });
184
+ }), describe("memory and performance", () => {
185
+ it("does not leak elements after cleanup", () => {
186
+ const cleanups = [];
187
+ for (let i = 0; i < 100; i++) {
188
+ const el = {};
189
+ cleanups.push(intent.observe(el, `/page-${i}`));
190
+ }
191
+ expect(intent.nodes.size).toBe(100), cleanups.forEach(c => c()), expect(intent.nodes.size).toBe(0), expect(intent.done.size).toBe(0);
192
+ }), it("handles rapid observe/unobserve cycles", () => {
193
+ const el = {};
194
+ for (let i = 0; i < 100; i++) intent.observe(el, "/test")();
195
+ expect(intent.nodes.size).toBe(0);
196
+ }), it("processes 100 links efficiently", () => {
197
+ const links = [];
198
+ for (let row = 0; row < 10; row++) for (let col = 0; col < 10; col++) links.push({
199
+ r: rect(100 + col * 80, 100 + row * 50, 60, 30),
200
+ h: `/link-${row}-${col}`
201
+ });
202
+ intent.setRects(links);
203
+ const start = performance.now();
204
+ for (let i = 0; i < 100; i++) intent.move(50 + i * 5, 300, 5, 0);
205
+ const elapsed = performance.now() - start;
206
+ expect(elapsed).toBeLessThan(50);
207
+ }), it("removes prefetched links from rects to speed up future checks", () => {
208
+ const links = Array.from({
209
+ length: 10
210
+ }, (_, i) => ({
211
+ r: rect(200 + i * 100, 300),
212
+ h: `/link-${i}`
213
+ }));
214
+ intent.setRects(links), intent.move(100, 320, 0, 0), intent.move(150, 320, 50, 0), expect(prefetched.length).toBe(1), intent.move(100, 320, -50, 0), intent.move(150, 320, 50, 0), intent.move(200, 320, 50, 0), expect(prefetched.length).toBe(2), expect(prefetched[0]).not.toBe(prefetched[1]);
215
+ });
216
+ });
217
+ });
218
+ //# sourceMappingURL=prefetchIntent.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["describe","expect","it","beforeEach","createPrefetchIntent","rect","x","y","w","h","left","top","right","bottom","width","height","toJSON","prefetched","intent","onPrefetch","href","push","setRects","r","move","toEqual","length","toBe","links","Array","from","_","i","toBeLessThanOrEqual","observe","getBoundingClientRect","el","cleanup","shortReach","maxReach","strictAim","perpWeight","cleanups","nodes","size","forEach","c","done","row","col","start","performance","now","elapsed","toBeLessThan","not"],"sources":["../../../src/link/prefetchIntent.test.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,QAAA,EAAUC,MAAA,EAAQC,EAAA,EAAIC,UAAA,QAAsB;AACrD,SAASC,oBAAA,QAAwD;AAGjE,MAAMC,IAAA,GAAOA,CAACC,CAAA,EAAWC,CAAA,EAAWC,CAAA,GAAI,KAAKC,CAAA,GAAI,QAAyB;EACxEC,IAAA,EAAMJ,CAAA;EACNK,GAAA,EAAKJ,CAAA;EACLK,KAAA,EAAON,CAAA,GAAIE,CAAA;EACXK,MAAA,EAAQN,CAAA,GAAIE,CAAA;EACZK,KAAA,EAAON,CAAA;EACPO,MAAA,EAAQN,CAAA;EACRH,CAAA;EACAC,CAAA;EACAS,MAAA,EAAQA,CAAA,MAAO,CAAC;AAClB;AAEAhB,QAAA,CAAS,kBAAkB,MAAM;EAC/B,IAAIiB,UAAA,EACAC,MAAA;EAEJf,UAAA,CAAW,MAAM;IACfc,UAAA,GAAa,EAAC,EACdC,MAAA,GAASd,oBAAA,CAAqB;MAC5Be,UAAA,EAAaC,IAAA,IAASH,UAAA,CAAWI,IAAA,CAAKD,IAAI;IAC5C,CAAC;EACH,CAAC,GAEDpB,QAAA,CAAS,mBAAmB,MAAM;IAChCE,EAAA,CAAG,iDAAiD,MAAM;MACxDgB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAS,CAAC,CAAC,GAIpDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAE3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,CAAC,QAAQ,CAAC;IACvC,CAAC,GAEDvB,EAAA,CAAG,kDAAkD,MAAM;MACzDgB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAS,CAAC,CAAC,GAGpDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,KAAK,CAAC,GAC5BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,KAAK,CAAC,GAE5BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,EAAE;IAC/B,CAAC,GAEDvB,EAAA,CAAG,yDAAyD,MAAM;MAChEgB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAS,CAAC,CAAC,GAGpDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,EAAE,GAC3BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,EAAE,GAE3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,EAAE;IAC/B,CAAC,GAEDvB,EAAA,CAAG,kDAAkD,MAAM;MAGzDgB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAO,CAAC,CAAC,GAGlDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAE3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,CAAC,MAAM,CAAC;IACrC,CAAC,GAEDvB,EAAA,CAAG,+DAA+D,MAAM;MACtEgB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAO,CAAC,CAAC,GAGlDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,EAAE,GAC5BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,EAAE,GAE5BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,EAAE;IAC/B,CAAC;EACH,CAAC,GAEDzB,QAAA,CAAS,0BAA0B,MAAM;IACvCE,EAAA,CAAG,kCAAkC,MAAM;MACzCgB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAS,CAAC,CAAC,GAGpDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAE3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,CAAC,QAAQ,CAAC,GAGrCP,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,MAAM,CAAC,GAC7BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAG3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,CAAC,QAAQ,CAAC;IACvC,CAAC,GAEDvB,EAAA,CAAG,sDAAsD,MAAM;MAC7DgB,MAAA,CAAOI,QAAA,CAAS,CACd;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAS,GACjC;QAAEc,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAU,EACnC,GAGDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,CAAC,QAAQ,CAAC,GAGrCP,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,KAAK,CAAC,GAC5BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,KAAK,CAAC,GAC5BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,CAAC,UAAU,SAAS,CAAC;IAClD,CAAC;EACH,CAAC,GAEDzB,QAAA,CAAS,yCAAyC,MAAM;IACtDE,EAAA,CAAG,mEAAmE,MAAM;MAE1EgB,MAAA,CAAOI,QAAA,CAAS,CACd;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAO,GAC/B;QAAEc,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAU,GAClC;QAAEc,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAU,EACnC,GAGDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAG3BvB,MAAA,CAAOgB,UAAA,CAAWS,MAAM,EAAEC,IAAA,CAAK,CAAC,GAChC1B,MAAA,CAAOgB,UAAA,CAAW,CAAC,CAAC,EAAEU,IAAA,CAAK,SAAS;IACtC,CAAC,GAEDzB,EAAA,CAAG,mCAAmC,MAAM;MAE1C,MAAM0B,KAAA,GAAQC,KAAA,CAAMC,IAAA,CAAK;QAAEJ,MAAA,EAAQ;MAAG,GAAG,CAACK,CAAA,EAAGC,CAAA,MAAO;QAClDT,CAAA,EAAGlB,IAAA,CAAK,MAAM2B,CAAA,GAAI,IAAI,IAAI,IAAI,EAAE;QAChCvB,CAAA,EAAG,QAAQuB,CAAC;MACd,EAAE;MACFd,MAAA,CAAOI,QAAA,CAASM,KAAK,GAGrBV,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,GAAG,GAC5BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,GAAG,GAG5BvB,MAAA,CAAOgB,UAAA,CAAWS,MAAM,EAAEC,IAAA,CAAK,CAAC,GAChC1B,MAAA,CAAOgB,UAAA,CAAW,CAAC,CAAC,EAAEU,IAAA,CAAK,SAAS;IACtC,CAAC,GAEDzB,EAAA,CAAG,kDAAkD,MAAM;MACzDgB,MAAA,CAAOI,QAAA,CAAS,CACd;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAQ,GAChC;QAAEc,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAO,EAChC,GAGDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAE3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,CAAC,OAAO,CAAC;IACtC,CAAC,GAEDvB,EAAA,CAAG,4CAA4C,MAAM;MACnDgB,MAAA,CAAOI,QAAA,CAAS,CACd;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAiB;MAAA;MACzC;QAAEc,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAiB;MAAA;MAAA,CAC1C,GAGDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAG3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,CAAC,gBAAgB,CAAC;IAC/C,CAAC;EACH,CAAC,GAEDzB,QAAA,CAAS,0BAA0B,MAAM;IACvCE,EAAA,CAAG,8CAA8C,MAAM;MACrDgB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAS,CAAC,CAAC,GAEpDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAE1BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,EAAE;IAC/B,CAAC,GAEDvB,EAAA,CAAG,iDAAiD,MAAM;MACxDgB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAS,CAAC,CAAC,GAEpDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAE1BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,EAAE;IAC/B,CAAC,GAEDvB,EAAA,CAAG,oDAAoD,MAAM;MAC3DgB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAS,CAAC,CAAC,GAGpDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,EAAE,GAC5BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAG3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,CAAC,QAAQ,CAAC;IACvC,CAAC;EACH,CAAC,GAEDzB,QAAA,CAAS,qBAAqB,MAAM;IAClCE,EAAA,CAAG,qCAAqC,MAAM;MAC5CgB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAU,CAAC,CAAC,GAGrDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,EAAE,GAC5BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,EAAE,GAE5BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,CAAC,SAAS,CAAC;IACxC,CAAC,GAEDvB,EAAA,CAAG,6CAA6C,MAAM;MACpDgB,MAAA,CAAOI,QAAA,CAAS,CACd;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,EAAE;QAAGI,CAAA,EAAG;MAAS,GAChC;QAAEc,CAAA,EAAGlB,IAAA,CAAK,KAAK,EAAE;QAAGI,CAAA,EAAG;MAAS,GAChC;QAAEc,CAAA,EAAGlB,IAAA,CAAK,KAAK,EAAE;QAAGI,CAAA,EAAG;MAAS,EACjC,GAGDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,GAAG,GAC7BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,GAAG,GAG7BvB,MAAA,CAAOgB,UAAA,CAAWS,MAAM,EAAEC,IAAA,CAAK,CAAC;IAClC,CAAC;EACH,CAAC,GAED3B,QAAA,CAAS,cAAc,MAAM;IAC3BE,EAAA,CAAG,2BAA2B,MAAM;MAClCgB,MAAA,CAAOI,QAAA,CAAS,EAAE,GAClBJ,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,EAAE;IAC/B,CAAC,GAEDvB,EAAA,CAAG,mCAAmC,MAAM;MAC1CgB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAQ,CAAC,CAAC,GACnDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAE3BvB,MAAA,CAAOgB,UAAA,CAAWS,MAAM,EAAEO,mBAAA,CAAoB,CAAC;IACjD,CAAC,GAED/B,EAAA,CAAG,mDAAmD,MAAM;MAC1CgB,MAAA,CAAOgB,OAAA,CACrB;QAAEC,qBAAA,EAAuBA,CAAA,KAAM9B,IAAA,CAAK,KAAK,GAAG;MAAE,GAC9C,OACF,EACQ,GAERa,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,EAAE;IAC/B,CAAC,GAEDvB,EAAA,CAAG,8CAA8C,MAAM;MACrD,MAAMkC,EAAA,GAAK;UAAED,qBAAA,EAAuBA,CAAA,KAAM9B,IAAA,CAAK,KAAK,GAAG;QAAE;QACnDgC,OAAA,GAAUnB,MAAA,CAAOgB,OAAA,CAAQE,EAAA,EAAI,OAAO;MAG1ClB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAQ,CAAC,CAAC,GACnDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,CAAC,OAAO,CAAC,GAGpCY,OAAA,CAAQ,GACRpB,UAAA,CAAWS,MAAA,GAAS,GACpBR,MAAA,CAAOgB,OAAA,CAAQE,EAAA,EAAI,OAAO,GAC1BlB,MAAA,CAAOI,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAQ,CAAC,CAAC,GAEnDS,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,CAAC,OAAO,CAAC;IACtC,CAAC;EACH,CAAC,GAEDzB,QAAA,CAAS,uBAAuB,MAAM;IACpCE,EAAA,CAAG,4BAA4B,MAAM;MACnC,MAAMoC,UAAA,GAAalC,oBAAA,CAAqB;QACtCe,UAAA,EAAaC,IAAA,IAASH,UAAA,CAAWI,IAAA,CAAKD,IAAI;QAC1CmB,QAAA,EAAU;MACZ,CAAC;MACDD,UAAA,CAAWhB,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAO,CAAC,CAAC,GAGtD6B,UAAA,CAAWd,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC9Bc,UAAA,CAAWd,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAE/BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,EAAE;IAC/B,CAAC,GAEDvB,EAAA,CAAG,iDAAiD,MAAM;MACxD,MAAMsC,SAAA,GAAYpC,oBAAA,CAAqB;QACrCe,UAAA,EAAaC,IAAA,IAASH,UAAA,CAAWI,IAAA,CAAKD,IAAI;QAC1CqB,UAAA,EAAY;QAAA;MACd,CAAC;MACDD,SAAA,CAAUlB,QAAA,CAAS,CAAC;QAAEC,CAAA,EAAGlB,IAAA,CAAK,KAAK,GAAG;QAAGI,CAAA,EAAG;MAAO,CAAC,CAAC,GAGrD+B,SAAA,CAAUhB,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC7BgB,SAAA,CAAUhB,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAE9BvB,MAAA,CAAOgB,UAAU,EAAEQ,OAAA,CAAQ,EAAE;IAC/B,CAAC;EACH,CAAC,GAEDzB,QAAA,CAAS,0BAA0B,MAAM;IACvCE,EAAA,CAAG,wCAAwC,MAAM;MAC/C,MAAMwC,QAAA,GAA2B,EAAC;MAGlC,SAASV,CAAA,GAAI,GAAGA,CAAA,GAAI,KAAKA,CAAA,IAAK;QAC5B,MAAMI,EAAA,GAAK,CAAC;QACZM,QAAA,CAASrB,IAAA,CAAKH,MAAA,CAAOgB,OAAA,CAAQE,EAAA,EAAI,SAASJ,CAAC,EAAE,CAAC;MAChD;MAEA/B,MAAA,CAAOiB,MAAA,CAAOyB,KAAA,CAAMC,IAAI,EAAEjB,IAAA,CAAK,GAAG,GAGlCe,QAAA,CAASG,OAAA,CAASC,CAAA,IAAMA,CAAA,CAAE,CAAC,GAE3B7C,MAAA,CAAOiB,MAAA,CAAOyB,KAAA,CAAMC,IAAI,EAAEjB,IAAA,CAAK,CAAC,GAChC1B,MAAA,CAAOiB,MAAA,CAAO6B,IAAA,CAAKH,IAAI,EAAEjB,IAAA,CAAK,CAAC;IACjC,CAAC,GAEDzB,EAAA,CAAG,0CAA0C,MAAM;MACjD,MAAMkC,EAAA,GAAK,CAAC;MAEZ,SAASJ,CAAA,GAAI,GAAGA,CAAA,GAAI,KAAKA,CAAA,IACPd,MAAA,CAAOgB,OAAA,CAAQE,EAAA,EAAI,OAAO,EAClC;MAIVnC,MAAA,CAAOiB,MAAA,CAAOyB,KAAA,CAAMC,IAAI,EAAEjB,IAAA,CAAK,CAAC;IAClC,CAAC,GAEDzB,EAAA,CAAG,mCAAmC,MAAM;MAE1C,MAAM0B,KAAA,GAA6C,EAAC;MACpD,SAASoB,GAAA,GAAM,GAAGA,GAAA,GAAM,IAAIA,GAAA,IAC1B,SAASC,GAAA,GAAM,GAAGA,GAAA,GAAM,IAAIA,GAAA,IAC1BrB,KAAA,CAAMP,IAAA,CAAK;QACTE,CAAA,EAAGlB,IAAA,CAAK,MAAM4C,GAAA,GAAM,IAAI,MAAMD,GAAA,GAAM,IAAI,IAAI,EAAE;QAC9CvC,CAAA,EAAG,SAASuC,GAAG,IAAIC,GAAG;MACxB,CAAC;MAGL/B,MAAA,CAAOI,QAAA,CAASM,KAAK;MAErB,MAAMsB,KAAA,GAAQC,WAAA,CAAYC,GAAA,CAAI;MAE9B,SAASpB,CAAA,GAAI,GAAGA,CAAA,GAAI,KAAKA,CAAA,IACvBd,MAAA,CAAOM,IAAA,CAAK,KAAKQ,CAAA,GAAI,GAAG,KAAK,GAAG,CAAC;MAEnC,MAAMqB,OAAA,GAAUF,WAAA,CAAYC,GAAA,CAAI,IAAIF,KAAA;MAGpCjD,MAAA,CAAOoD,OAAO,EAAEC,YAAA,CAAa,EAAE;IACjC,CAAC,GAEDpD,EAAA,CAAG,iEAAiE,MAAM;MACxE,MAAM0B,KAAA,GAAQC,KAAA,CAAMC,IAAA,CAAK;QAAEJ,MAAA,EAAQ;MAAG,GAAG,CAACK,CAAA,EAAGC,CAAA,MAAO;QAClDT,CAAA,EAAGlB,IAAA,CAAK,MAAM2B,CAAA,GAAI,KAAK,GAAG;QAC1BvB,CAAA,EAAG,SAASuB,CAAC;MACf,EAAE;MACFd,MAAA,CAAOI,QAAA,CAASM,KAAK,GAGrBV,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,GAAG,CAAC,GAC1BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAE3BvB,MAAA,CAAOgB,UAAA,CAAWS,MAAM,EAAEC,IAAA,CAAK,CAAC,GAKhCT,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,KAAK,CAAC,GAC5BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAC3BN,MAAA,CAAOM,IAAA,CAAK,KAAK,KAAK,IAAI,CAAC,GAG3BvB,MAAA,CAAOgB,UAAA,CAAWS,MAAM,EAAEC,IAAA,CAAK,CAAC,GAChC1B,MAAA,CAAOgB,UAAA,CAAW,CAAC,CAAC,EAAEsC,GAAA,CAAI5B,IAAA,CAAKV,UAAA,CAAW,CAAC,CAAC;IAC9C,CAAC;EACH,CAAC;AACH,CAAC","ignoreList":[]}
@@ -0,0 +1,237 @@
1
+ import { describe, expect, it, beforeEach } from "vitest";
2
+ import { createPrefetchIntent } from "./prefetchIntent.native.js";
3
+ var rect = function (x, y) {
4
+ var w = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : 100,
5
+ h = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : 40;
6
+ return {
7
+ left: x,
8
+ top: y,
9
+ right: x + w,
10
+ bottom: y + h,
11
+ width: w,
12
+ height: h,
13
+ x,
14
+ y,
15
+ toJSON: function () {
16
+ return {};
17
+ }
18
+ };
19
+ };
20
+ describe("prefetchIntent", function () {
21
+ var prefetched, intent;
22
+ beforeEach(function () {
23
+ prefetched = [], intent = createPrefetchIntent({
24
+ onPrefetch: function (href) {
25
+ return prefetched.push(href);
26
+ }
27
+ });
28
+ }), describe("basic targeting", function () {
29
+ it("prefetches when moving directly toward a link", function () {
30
+ intent.setRects([{
31
+ r: rect(500, 300),
32
+ h: "/about"
33
+ }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/about"]);
34
+ }), it("does not prefetch when moving away from a link", function () {
35
+ intent.setRects([{
36
+ r: rect(500, 300),
37
+ h: "/about"
38
+ }]), intent.move(400, 300, 0, 0), intent.move(380, 300, -20, 0), intent.move(350, 300, -30, 0), expect(prefetched).toEqual([]);
39
+ }), it("does not prefetch when moving perpendicular to a link", function () {
40
+ intent.setRects([{
41
+ r: rect(500, 300),
42
+ h: "/about"
43
+ }]), intent.move(200, 300, 0, 0), intent.move(200, 330, 0, 30), intent.move(200, 370, 0, 40), expect(prefetched).toEqual([]);
44
+ }), it("prefetches link at long distance with good aim", function () {
45
+ intent.setRects([{
46
+ r: rect(500, 280),
47
+ h: "/far"
48
+ }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/far"]);
49
+ }), it("does not prefetch when aim is slightly off at long distance", function () {
50
+ intent.setRects([{
51
+ r: rect(600, 300),
52
+ h: "/far"
53
+ }]), intent.move(100, 300, 0, 0), intent.move(140, 320, 40, 20), intent.move(200, 360, 60, 40), expect(prefetched).toEqual([]);
54
+ });
55
+ }), describe("prevents over-fetching", function () {
56
+ it("only prefetches each href once", function () {
57
+ intent.setRects([{
58
+ r: rect(500, 300),
59
+ h: "/about"
60
+ }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/about"]), intent.move(100, 300, -100, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/about"]);
61
+ }), it("removes prefetched links from future consideration", function () {
62
+ intent.setRects([{
63
+ r: rect(400, 300),
64
+ h: "/first"
65
+ }, {
66
+ r: rect(600, 300),
67
+ h: "/second"
68
+ }]), intent.move(100, 300, 0, 0), intent.move(150, 300, 50, 0), expect(prefetched).toEqual(["/first"]), intent.move(250, 300, 100, 0), intent.move(350, 300, 100, 0), expect(prefetched).toEqual(["/first", "/second"]);
69
+ });
70
+ }), describe("winner-takes-all with clustered links", function () {
71
+ it("only prefetches the best target when multiple links are in path", function () {
72
+ intent.setRects([{
73
+ r: rect(500, 280),
74
+ h: "/top"
75
+ }, {
76
+ r: rect(500, 320),
77
+ h: "/middle"
78
+ }, {
79
+ r: rect(500, 360),
80
+ h: "/bottom"
81
+ }]), intent.move(100, 340, 0, 0), intent.move(150, 340, 50, 0), expect(prefetched.length).toBe(1), expect(prefetched[0]).toBe("/middle");
82
+ }), it("handles dense nav with 20 links", function () {
83
+ var links = Array.from({
84
+ length: 20
85
+ }, function (_, i) {
86
+ return {
87
+ r: rect(100 + i * 60, 50, 50, 30),
88
+ h: `/nav-${i}`
89
+ };
90
+ });
91
+ intent.setRects(links), intent.move(700, 200, 0, 0), intent.move(700, 150, 0, -50), intent.move(700, 100, 0, -50), expect(prefetched.length).toBe(1), expect(prefetched[0]).toBe("/nav-10");
92
+ }), it("picks closer link when two are roughly aligned", function () {
93
+ intent.setRects([{
94
+ r: rect(300, 300),
95
+ h: "/near"
96
+ }, {
97
+ r: rect(600, 300),
98
+ h: "/far"
99
+ }]), intent.move(100, 320, 0, 0), intent.move(150, 320, 50, 0), expect(prefetched).toEqual(["/near"]);
100
+ }), it("picks better-aimed link over closer link", function () {
101
+ intent.setRects([{
102
+ r: rect(200, 400),
103
+ h: "/close-but-off"
104
+ },
105
+ // closer but significantly off-axis
106
+ {
107
+ r: rect(400, 300),
108
+ h: "/far-but-aimed"
109
+ }]), intent.move(100, 300, 0, 0), intent.move(150, 300, 50, 0), expect(prefetched).toEqual(["/far-but-aimed"]);
110
+ });
111
+ }), describe("velocity and smoothing", function () {
112
+ it("does not prefetch when mouse is stationary", function () {
113
+ intent.setRects([{
114
+ r: rect(500, 300),
115
+ h: "/about"
116
+ }]), intent.move(400, 300, 0, 0), intent.move(400, 300, 0, 0), intent.move(400, 300, 0, 0), expect(prefetched).toEqual([]);
117
+ }), it("does not prefetch when mouse is moving slowly", function () {
118
+ intent.setRects([{
119
+ r: rect(500, 300),
120
+ h: "/about"
121
+ }]), intent.move(400, 300, 0, 0), intent.move(401, 300, 1, 0), intent.move(402, 300, 1, 0), expect(prefetched).toEqual([]);
122
+ }), it("smooths velocity to avoid jitter false positives", function () {
123
+ intent.setRects([{
124
+ r: rect(500, 300),
125
+ h: "/about"
126
+ }]), intent.move(100, 300, 0, 0), intent.move(130, 305, 30, 5), intent.move(160, 298, 30, -7), intent.move(195, 303, 35, 5), expect(prefetched).toEqual(["/about"]);
127
+ });
128
+ }), describe("diagonal movement", function () {
129
+ it("prefetches with diagonal approach", function () {
130
+ intent.setRects([{
131
+ r: rect(500, 500),
132
+ h: "/corner"
133
+ }]), intent.move(200, 200, 0, 0), intent.move(240, 240, 40, 40), intent.move(290, 290, 50, 50), expect(prefetched).toEqual(["/corner"]);
134
+ }), it("handles angled approach to horizontal nav", function () {
135
+ intent.setRects([{
136
+ r: rect(400, 50),
137
+ h: "/link1"
138
+ }, {
139
+ r: rect(500, 50),
140
+ h: "/link2"
141
+ }, {
142
+ r: rect(600, 50),
143
+ h: "/link3"
144
+ }]), intent.move(300, 300, 0, 0), intent.move(340, 260, 40, -40), intent.move(390, 210, 50, -50), expect(prefetched.length).toBe(1);
145
+ });
146
+ }), describe("edge cases", function () {
147
+ it("handles empty rect list", function () {
148
+ intent.setRects([]), intent.move(100, 100, 0, 0), intent.move(150, 100, 50, 0), expect(prefetched).toEqual([]);
149
+ }), it("handles link at cursor position", function () {
150
+ intent.setRects([{
151
+ r: rect(100, 100),
152
+ h: "/here"
153
+ }]), intent.move(150, 120, 0, 0), intent.move(160, 120, 10, 0), expect(prefetched.length).toBeLessThanOrEqual(1);
154
+ }), it("cleans up when observe returns cleanup function", function () {
155
+ var cleanup = intent.observe({
156
+ getBoundingClientRect: function () {
157
+ return rect(500, 300);
158
+ }
159
+ }, "/test");
160
+ cleanup(), intent.move(100, 300, 0, 0), intent.move(150, 300, 50, 0), expect(prefetched).toEqual([]);
161
+ }), it("re-enables prefetch for href after cleanup", function () {
162
+ var el = {
163
+ getBoundingClientRect: function () {
164
+ return rect(500, 300);
165
+ }
166
+ },
167
+ cleanup = intent.observe(el, "/test");
168
+ intent.setRects([{
169
+ r: rect(500, 300),
170
+ h: "/test"
171
+ }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/test"]), cleanup(), prefetched.length = 0, intent.observe(el, "/test"), intent.setRects([{
172
+ r: rect(500, 300),
173
+ h: "/test"
174
+ }]), intent.move(100, 300, 0, 0), intent.move(140, 300, 40, 0), intent.move(200, 300, 60, 0), expect(prefetched).toEqual(["/test"]);
175
+ });
176
+ }), describe("reach configuration", function () {
177
+ it("respects maxReach option", function () {
178
+ var shortReach = createPrefetchIntent({
179
+ onPrefetch: function (href) {
180
+ return prefetched.push(href);
181
+ },
182
+ maxReach: 200
183
+ });
184
+ shortReach.setRects([{
185
+ r: rect(500, 300),
186
+ h: "/far"
187
+ }]), shortReach.move(100, 300, 0, 0), shortReach.move(150, 300, 50, 0), expect(prefetched).toEqual([]);
188
+ }), it("respects perpWeight option for aim strictness", function () {
189
+ var strictAim = createPrefetchIntent({
190
+ onPrefetch: function (href) {
191
+ return prefetched.push(href);
192
+ },
193
+ perpWeight: 10
194
+ });
195
+ strictAim.setRects([{
196
+ r: rect(400, 350),
197
+ h: "/off"
198
+ }]), strictAim.move(100, 300, 0, 0), strictAim.move(150, 300, 50, 0), expect(prefetched).toEqual([]);
199
+ });
200
+ }), describe("memory and performance", function () {
201
+ it("does not leak elements after cleanup", function () {
202
+ for (var cleanups = [], i = 0; i < 100; i++) {
203
+ var el = {};
204
+ cleanups.push(intent.observe(el, `/page-${i}`));
205
+ }
206
+ expect(intent.nodes.size).toBe(100), cleanups.forEach(function (c) {
207
+ return c();
208
+ }), expect(intent.nodes.size).toBe(0), expect(intent.done.size).toBe(0);
209
+ }), it("handles rapid observe/unobserve cycles", function () {
210
+ for (var el = {}, i = 0; i < 100; i++) {
211
+ var cleanup = intent.observe(el, "/test");
212
+ cleanup();
213
+ }
214
+ expect(intent.nodes.size).toBe(0);
215
+ }), it("processes 100 links efficiently", function () {
216
+ for (var links = [], row = 0; row < 10; row++) for (var col = 0; col < 10; col++) links.push({
217
+ r: rect(100 + col * 80, 100 + row * 50, 60, 30),
218
+ h: `/link-${row}-${col}`
219
+ });
220
+ intent.setRects(links);
221
+ for (var start = performance.now(), i = 0; i < 100; i++) intent.move(50 + i * 5, 300, 5, 0);
222
+ var elapsed = performance.now() - start;
223
+ expect(elapsed).toBeLessThan(50);
224
+ }), it("removes prefetched links from rects to speed up future checks", function () {
225
+ var links = Array.from({
226
+ length: 10
227
+ }, function (_, i) {
228
+ return {
229
+ r: rect(200 + i * 100, 300),
230
+ h: `/link-${i}`
231
+ };
232
+ });
233
+ intent.setRects(links), intent.move(100, 320, 0, 0), intent.move(150, 320, 50, 0), expect(prefetched.length).toBe(1), intent.move(100, 320, -50, 0), intent.move(150, 320, 50, 0), intent.move(200, 320, 50, 0), expect(prefetched.length).toBe(2), expect(prefetched[0]).not.toBe(prefetched[1]);
234
+ });
235
+ });
236
+ });
237
+ //# sourceMappingURL=prefetchIntent.test.native.js.map