one
Version:
One is a new React Framework that makes Vite serve both native and web.
151 lines (150 loc) • 9.41 kB
JavaScript
import { describe, expect, it, beforeEach } from "vitest";
import { createPrefetchIntent } from "./prefetchIntent";
const rect = (x, y, w = 100, h = 40) => ({
left: x,
top: y,
right: x + w,
bottom: y + h,
width: w,
height: h,
x,
y,
toJSON: () => ({})
});
describe("prefetchIntent", () => {
let prefetched, intent;
beforeEach(() => {
prefetched = [], intent = createPrefetchIntent({
onPrefetch: (href) => prefetched.push(href)
});
}), describe("basic targeting", () => {
it("prefetches when moving directly toward a link", () => {
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"]);
}), it("does not prefetch when moving away from a link", () => {
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([]);
}), it("does not prefetch when moving perpendicular to a link", () => {
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([]);
}), it("prefetches link at long distance with good aim", () => {
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"]);
}), it("does not prefetch when aim is slightly off at long distance", () => {
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([]);
});
}), describe("prevents over-fetching", () => {
it("only prefetches each href once", () => {
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"]);
}), it("removes prefetched links from future consideration", () => {
intent.setRects([
{ r: rect(400, 300), h: "/first" },
{ r: rect(600, 300), h: "/second" }
]), 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"]);
});
}), describe("winner-takes-all with clustered links", () => {
it("only prefetches the best target when multiple links are in path", () => {
intent.setRects([
{ r: rect(500, 280), h: "/top" },
{ r: rect(500, 320), h: "/middle" },
{ r: rect(500, 360), h: "/bottom" }
]), intent.move(100, 340, 0, 0), intent.move(150, 340, 50, 0), expect(prefetched.length).toBe(1), expect(prefetched[0]).toBe("/middle");
}), it("handles dense nav with 20 links", () => {
const links = Array.from({ length: 20 }, (_, i) => ({
r: rect(100 + i * 60, 50, 50, 30),
h: `/nav-${i}`
}));
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");
}), it("picks closer link when two are roughly aligned", () => {
intent.setRects([
{ r: rect(300, 300), h: "/near" },
{ r: rect(600, 300), h: "/far" }
]), intent.move(100, 320, 0, 0), intent.move(150, 320, 50, 0), expect(prefetched).toEqual(["/near"]);
}), it("picks better-aimed link over closer link", () => {
intent.setRects([
{ r: rect(200, 400), h: "/close-but-off" },
// closer but significantly off-axis
{ r: rect(400, 300), h: "/far-but-aimed" }
// farther but dead-on
]), intent.move(100, 300, 0, 0), intent.move(150, 300, 50, 0), expect(prefetched).toEqual(["/far-but-aimed"]);
});
}), describe("velocity and smoothing", () => {
it("does not prefetch when mouse is stationary", () => {
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([]);
}), it("does not prefetch when mouse is moving slowly", () => {
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([]);
}), it("smooths velocity to avoid jitter false positives", () => {
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"]);
});
}), describe("diagonal movement", () => {
it("prefetches with diagonal approach", () => {
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"]);
}), it("handles angled approach to horizontal nav", () => {
intent.setRects([
{ r: rect(400, 50), h: "/link1" },
{ r: rect(500, 50), h: "/link2" },
{ r: rect(600, 50), h: "/link3" }
]), intent.move(300, 300, 0, 0), intent.move(340, 260, 40, -40), intent.move(390, 210, 50, -50), expect(prefetched.length).toBe(1);
});
}), describe("edge cases", () => {
it("handles empty rect list", () => {
intent.setRects([]), intent.move(100, 100, 0, 0), intent.move(150, 100, 50, 0), expect(prefetched).toEqual([]);
}), it("handles link at cursor position", () => {
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);
}), it("cleans up when observe returns cleanup function", () => {
intent.observe(
{ getBoundingClientRect: () => rect(500, 300) },
"/test"
)(), intent.move(100, 300, 0, 0), intent.move(150, 300, 50, 0), expect(prefetched).toEqual([]);
}), it("re-enables prefetch for href after cleanup", () => {
const el = { getBoundingClientRect: () => rect(500, 300) }, cleanup = 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"]), 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"]);
});
}), describe("reach configuration", () => {
it("respects maxReach option", () => {
const shortReach = createPrefetchIntent({
onPrefetch: (href) => prefetched.push(href),
maxReach: 200
});
shortReach.setRects([{ r: rect(500, 300), h: "/far" }]), shortReach.move(100, 300, 0, 0), shortReach.move(150, 300, 50, 0), expect(prefetched).toEqual([]);
}), it("respects perpWeight option for aim strictness", () => {
const strictAim = createPrefetchIntent({
onPrefetch: (href) => prefetched.push(href),
perpWeight: 10
// very strict
});
strictAim.setRects([{ r: rect(400, 350), h: "/off" }]), strictAim.move(100, 300, 0, 0), strictAim.move(150, 300, 50, 0), expect(prefetched).toEqual([]);
});
}), describe("memory and performance", () => {
it("does not leak elements after cleanup", () => {
const cleanups = [];
for (let i = 0; i < 100; i++) {
const el = {};
cleanups.push(intent.observe(el, `/page-${i}`));
}
expect(intent.nodes.size).toBe(100), cleanups.forEach((c) => c()), expect(intent.nodes.size).toBe(0), expect(intent.done.size).toBe(0);
}), it("handles rapid observe/unobserve cycles", () => {
const el = {};
for (let i = 0; i < 100; i++)
intent.observe(el, "/test")();
expect(intent.nodes.size).toBe(0);
}), it("processes 100 links efficiently", () => {
const links = [];
for (let row = 0; row < 10; row++)
for (let col = 0; col < 10; col++)
links.push({
r: rect(100 + col * 80, 100 + row * 50, 60, 30),
h: `/link-${row}-${col}`
});
intent.setRects(links);
const start = performance.now();
for (let i = 0; i < 100; i++)
intent.move(50 + i * 5, 300, 5, 0);
const elapsed = performance.now() - start;
expect(elapsed).toBeLessThan(50);
}), it("removes prefetched links from rects to speed up future checks", () => {
const links = Array.from({ length: 10 }, (_, i) => ({
r: rect(200 + i * 100, 300),
h: `/link-${i}`
}));
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]);
});
});
});
//# sourceMappingURL=prefetchIntent.test.js.map