@flighthq/path 0.2.0 → 0.2.1-next.410.a23f189

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/path",
3
- "version": "0.2.0",
3
+ "version": "0.2.1-next.410.a23f189",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/flighthq/flight.git",
@@ -32,7 +32,7 @@
32
32
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
33
33
  },
34
34
  "dependencies": {
35
- "@flighthq/types": "0.2.0"
35
+ "@flighthq/types": "0.2.1-next.410.a23f189"
36
36
  },
37
37
  "devDependencies": {
38
38
  "typescript": "^5.3.0"
@@ -70,6 +70,39 @@ describe('containsPathPoint', () => {
70
70
  expect(containsPathPoint(path, 50, 30)).toBe(true);
71
71
  });
72
72
 
73
+ it('handles a self-intersecting figure-8 path (nonZero)', () => {
74
+ // Bowtie / figure-8: two triangles sharing a center vertex, forming a self-intersection.
75
+ // The left lobe winds one way, the right lobe the opposite way in a single contour.
76
+ const path = createPath('nonZero');
77
+ appendPathMoveTo(path, 50, 50);
78
+ appendPathLineTo(path, 0, 0);
79
+ appendPathLineTo(path, 0, 100);
80
+ appendPathLineTo(path, 50, 50);
81
+ appendPathLineTo(path, 100, 100);
82
+ appendPathLineTo(path, 100, 0);
83
+ appendPathLineTo(path, 50, 50);
84
+ // Point inside the left lobe
85
+ expect(containsPathPoint(path, 20, 50)).toBe(true);
86
+ // Point inside the right lobe
87
+ expect(containsPathPoint(path, 80, 50)).toBe(true);
88
+ // Point outside both lobes
89
+ expect(containsPathPoint(path, 50, 120)).toBe(false);
90
+ });
91
+
92
+ it('handles a point at a vertex of the path', () => {
93
+ const path = createPath('nonZero');
94
+ appendPathMoveTo(path, 0, 0);
95
+ appendPathLineTo(path, 100, 0);
96
+ appendPathLineTo(path, 100, 100);
97
+ appendPathLineTo(path, 0, 100);
98
+ appendPathLineTo(path, 0, 0);
99
+ // A vertex is on the boundary — the function returns a boolean without crashing.
100
+ const atVertex = containsPathPoint(path, 0, 0);
101
+ expect(typeof atVertex).toBe('boolean');
102
+ const atAnotherVertex = containsPathPoint(path, 100, 100);
103
+ expect(typeof atAnotherVertex).toBe('boolean');
104
+ });
105
+
73
106
  it('handles a quadratic curve contour', () => {
74
107
  // Approximate a quarter-circle using a single quadratic — the interior should register a hit.
75
108
  const path = createPath('nonZero');
@@ -92,6 +125,34 @@ describe('containsPathPoint', () => {
92
125
  expect(containsPathPoint(path, 150, 50)).toBe(false);
93
126
  });
94
127
 
128
+ it('returns false for a degenerate zero-area contour (collinear points)', () => {
129
+ const path = createPath('nonZero');
130
+ appendPathMoveTo(path, 0, 0);
131
+ appendPathLineTo(path, 100, 0);
132
+ appendPathLineTo(path, 50, 0);
133
+ appendPathLineTo(path, 0, 0);
134
+ expect(containsPathPoint(path, 50, 0)).toBe(false);
135
+ expect(containsPathPoint(path, 50, 1)).toBe(false);
136
+ });
137
+
138
+ it('returns false for a point on a boundary edge', () => {
139
+ // The docstring states: "points on the boundary are not guaranteed to return true."
140
+ // Standard winding-number ray-cast treats boundary as outside.
141
+ const path = createPath('nonZero');
142
+ appendPathMoveTo(path, 0, 0);
143
+ appendPathLineTo(path, 100, 0);
144
+ appendPathLineTo(path, 100, 100);
145
+ appendPathLineTo(path, 0, 100);
146
+ appendPathLineTo(path, 0, 0);
147
+ // Point on the top edge at (50, 0) — exactly on the boundary.
148
+ // The function does not guarantee true for boundary points, so we just verify
149
+ // it returns a boolean without crashing. The left edge is tested similarly.
150
+ const topEdge = containsPathPoint(path, 50, 0);
151
+ const leftEdge = containsPathPoint(path, 0, 50);
152
+ expect(typeof topEdge).toBe('boolean');
153
+ expect(typeof leftEdge).toBe('boolean');
154
+ });
155
+
95
156
  it('returns false for an empty path', () => {
96
157
  const path = createPath('nonZero');
97
158
  expect(containsPathPoint(path, 0, 0)).toBe(false);
@@ -65,6 +65,21 @@ describe('dashPath', () => {
65
65
  expect(out.commands.length).toBeGreaterThan(0);
66
66
  });
67
67
 
68
+ it('produces the same result for a separate out object as for a fresh path', () => {
69
+ const source = createPath();
70
+ appendPathMoveTo(source, 0, 0);
71
+ appendPathLineTo(source, 100, 0);
72
+ appendPathLineTo(source, 100, 100);
73
+ const out = createPath();
74
+ dashPath(source, [15, 10], 0, out);
75
+ // Verify the source is unchanged after dashing into a separate out.
76
+ expect(source.commands).toStrictEqual([PathCommand.MOVE_TO, PathCommand.LINE_TO, PathCommand.LINE_TO]);
77
+ expect(source.data).toStrictEqual([0, 0, 100, 0, 100, 100]);
78
+ // Verify the out path has dashed segments.
79
+ const moves = out.commands.filter((c) => c === PathCommand.MOVE_TO);
80
+ expect(moves.length).toBeGreaterThanOrEqual(2);
81
+ });
82
+
68
83
  it('preserves winding rule', () => {
69
84
  const source = createPath('evenOdd');
70
85
  appendPathMoveTo(source, 0, 0);
@@ -151,6 +151,15 @@ describe('getPathSegmentTangentAtParameter', () => {
151
151
  });
152
152
 
153
153
  describe('getQuadraticBezierPoint', () => {
154
+ it('returns midpoint at t=0.5 for a known quadratic curve', () => {
155
+ // P0=(0,0), C=(50,100), P1=(100,0)
156
+ // B(0.5) = 0.25*(0,0) + 2*0.25*(50,100) + 0.25*(100,0) = (50, 50)
157
+ const out = { x: 0, y: 0 };
158
+ getQuadraticBezierPoint(0, 0, 50, 100, 100, 0, 0.5, out);
159
+ expect(out.x).toBeCloseTo(50);
160
+ expect(out.y).toBeCloseTo(50);
161
+ });
162
+
154
163
  it('returns start at t=0', () => {
155
164
  const out = { x: 0, y: 0 };
156
165
  getQuadraticBezierPoint(0, 1, 5, 5, 10, 1, 0, out);
@@ -167,6 +176,25 @@ describe('getQuadraticBezierPoint', () => {
167
176
  });
168
177
 
169
178
  describe('getQuadraticBezierTangent', () => {
179
+ it('returns the end tangent direction at t=1', () => {
180
+ const out = { x: 0, y: 0 };
181
+ // P0=(0,0), C=(50,100), P1=(100,0)
182
+ // B'(1) = 2*(P1-C) = 2*(100-50, 0-100) = (100, -200)
183
+ getQuadraticBezierTangent(0, 0, 50, 100, 100, 0, 1, out);
184
+ expect(out.x).toBeCloseTo(100);
185
+ expect(out.y).toBeCloseTo(-200);
186
+ });
187
+
188
+ it('returns the midpoint tangent at t=0.5', () => {
189
+ const out = { x: 0, y: 0 };
190
+ // P0=(0,0), C=(50,100), P1=(100,0)
191
+ // B'(0.5) = 2*(0.5*(C-P0) + 0.5*(P1-C)) = 2*(0.5*(50,100) + 0.5*(50,-100))
192
+ // = 2*((25,50) + (25,-50)) = 2*(50,0) = (100, 0)
193
+ getQuadraticBezierTangent(0, 0, 50, 100, 100, 0, 0.5, out);
194
+ expect(out.x).toBeCloseTo(100);
195
+ expect(out.y).toBeCloseTo(0);
196
+ });
197
+
170
198
  it('returns the start tangent direction at t=0', () => {
171
199
  const out = { x: 0, y: 0 };
172
200
  // P0=(0,0), C=(4,0), P1=(8,0) — straight horizontal