mirror of
https://github.com/github/awesome-copilot.git
synced 2026-10-13 15:24:25 +00:00
105 lines
5.5 KiB
JavaScript
105 lines
5.5 KiB
JavaScript
// Tests for label wrapping, arrow routes, pen strokes and hit testing. Run `node --test` in the extension folder.
|
|
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { arrowGeometry, elementBounds, fitStroke, hitTest, simplifyStroke, wrapText } from "./geometry.mjs";
|
|
import { MAX_PEN_POINTS, normalizeElement } from "./model.mjs";
|
|
|
|
// One unit of width per character, so the expected lines are easy to read.
|
|
const chars = (s) => [...s].length;
|
|
|
|
test("labels wrap at spaces, long words are cut, and extra spaces are kept", () => {
|
|
assert.deepEqual(wrapText("the quick brown fox jumps", 11, 14, 400, chars), ["the quick", "brown fox", "jumps"]);
|
|
assert.deepEqual(wrapText("abcdefghij", 4, 14, 400, chars), ["abcd", "efgh", "ij"]);
|
|
assert.deepEqual(wrapText("ab cdefghij k", 4, 14, 400, chars), ["ab", "cdef", "ghij", "k"]);
|
|
assert.deepEqual(wrapText(" abcdefgh", 4, 14, 400, chars), ["abcd", "efgh"]);
|
|
assert.deepEqual(wrapText("a b\nc", 40, 14, 400, chars), ["a b", "c"]);
|
|
assert.deepEqual(wrapText("😀😀😀😀😀", 2, 14, 400, chars), ["😀😀", "😀😀", "😀"]);
|
|
});
|
|
|
|
test("a long label is wrapped without measuring each line over and over", () => {
|
|
const repeat = (n, line) => Array.from({ length: n }, () => line);
|
|
for (const [text, want] of [
|
|
["x".repeat(4000), [...repeat(13, "x".repeat(300)), "x".repeat(100)]],
|
|
["i ".repeat(2000), [...repeat(13, "i ".repeat(150).trimEnd()), "i ".repeat(50)]],
|
|
]) {
|
|
let measured = 0;
|
|
const lines = wrapText(text, 300, 14, 400, (s) => {
|
|
measured += s.length;
|
|
return chars(s);
|
|
});
|
|
assert.deepEqual(lines, want);
|
|
// Adding one character (or word) at a time measures about 75 to 150 times the text here.
|
|
assert.ok(measured < 10 * text.length, `measured ${measured} characters to wrap ${text.length}`);
|
|
}
|
|
});
|
|
|
|
test("an arrow attached to nothing follows its elbow or curve route", () => {
|
|
const free = (route, x2 = 200, y2 = 100) =>
|
|
arrowGeometry(normalizeElement({ id: "a", type: "arrow", x1: 0, y1: 0, x2, y2, route }), new Map());
|
|
assert.deepEqual(free("elbow").points.map((p) => [p.x, p.y]), [[0, 0], [100, 0], [100, 100], [200, 100]]);
|
|
assert.match(free("curve").d, / C/);
|
|
assert.equal(free("straight").points.length, 2);
|
|
// Ends this close stay straight, as they do between shapes that nearly touch.
|
|
assert.equal(free("elbow", 10, 8).points.length, 2);
|
|
});
|
|
|
|
test("a simplified stroke keeps its ends and corners and drops points on a straight line", () => {
|
|
const line = Array.from({ length: 500 }, (_, i) => [i, 0]);
|
|
assert.deepEqual(simplifyStroke(line, 0.5), [[0, 0], [499, 0]]);
|
|
const corner = [...line.slice(0, 250), ...Array.from({ length: 250 }, (_, i) => [249, i + 1])];
|
|
assert.deepEqual(simplifyStroke(corner, 0.5), [[0, 0], [249, 0], [249, 250]]);
|
|
assert.deepEqual(simplifyStroke([[0, 0], [5, 5]], 0.5), [[0, 0], [5, 5]]);
|
|
// A long stroke is done in pieces, and the points where they meet stay: one per 512 points.
|
|
const long = Array.from({ length: 5000 }, (_, i) => [i, 0]);
|
|
assert.deepEqual(simplifyStroke(long, 0.5).map((p) => p[0]), [0, 512, 1024, 1536, 2048, 2560, 3072, 3584, 4096, 4608, 4999]);
|
|
});
|
|
|
|
test("simplifying a long jagged stroke takes time in step with its length", () => {
|
|
// A zigzag that gets wider as it goes makes plain RDP split one point off at a time, so it
|
|
// would check about n * n / 2 distances: over a billion here.
|
|
const zigzag = Array.from({ length: 50000 }, (_, i) => [i, i % 2 ? -i : i]);
|
|
const start = performance.now();
|
|
const out = simplifyStroke(zigzag, 0.5);
|
|
assert.ok(performance.now() - start < 1500, `took ${Math.round(performance.now() - start)} ms`);
|
|
// No point is on a line, so every one stays.
|
|
assert.equal(out.length, zigzag.length);
|
|
});
|
|
|
|
test("a fitted stroke is short enough to keep and ends where the pointer did", () => {
|
|
const zigzag = Array.from({ length: 30000 }, (_, i) => [i, i % 2 ? -i : i]);
|
|
const out = fitStroke(zigzag, 0.5);
|
|
assert.equal(out.length, MAX_PEN_POINTS);
|
|
assert.deepEqual(out[0], zigzag[0]);
|
|
assert.deepEqual(out.at(-1), zigzag.at(-1));
|
|
assert.equal(normalizeElement({ type: "pen", points: out }).points.length, MAX_PEN_POINTS);
|
|
});
|
|
|
|
test("pointing finds a pen stroke without reading the points of strokes out of reach", () => {
|
|
const near = normalizeElement({ id: "near", type: "pen", points: [[0, 0], [100, 0], [100, 100]], width: 4 });
|
|
const dot = normalizeElement({ id: "dot", type: "pen", points: [[300, 300]], width: 4 });
|
|
const far = normalizeElement({ id: "far", type: "pen", points: Array.from({ length: MAX_PEN_POINTS }, (_, i) => [1000 + i, 1000]), width: 4 });
|
|
let reads = 0;
|
|
const points = new Proxy(far.points, {
|
|
get(target, key, receiver) {
|
|
if (typeof key === "string" && /^\d+$/.test(key)) reads++;
|
|
return Reflect.get(target, key, receiver);
|
|
},
|
|
});
|
|
const elements = [near, dot, { ...far, points }];
|
|
|
|
// Within half the width plus the tolerance plus 2 (8 here) of a line between two points.
|
|
assert.equal(hitTest(elements, 50, 5)?.id, "near");
|
|
reads = 0;
|
|
assert.equal(hitTest(elements, 50, 9), null);
|
|
assert.equal(hitTest(elements, 50, 5, 0), null);
|
|
assert.equal(hitTest(elements, 105, 50)?.id, "near");
|
|
assert.equal(hitTest(elements, 303, 303)?.id, "dot");
|
|
assert.equal(hitTest(elements, 306, 306), null);
|
|
assert.equal(reads, 0);
|
|
assert.equal(hitTest(elements, 1500, 1006)?.id, "far");
|
|
|
|
assert.deepEqual(elementBounds(near), { x: -2, y: -2, w: 104, h: 104 });
|
|
const moved = { ...near, points: near.points.map(([x, y]) => [x + 50, y]) };
|
|
assert.deepEqual(elementBounds(moved), { x: 48, y: -2, w: 104, h: 104 });
|
|
});
|