@flighthq/path 0.5.0-next.1944.cd6b6e7 → 0.5.1-edge.638.d4dbd4e

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 CHANGED
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/path`. The `@flighth
13
13
  This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
14
14
 
15
15
  - [Flight project](https://github.com/flighthq/flight)
16
- - [Source for @flighthq/path@0.5.0-next.1944.cd6b6e7](https://github.com/flighthq/flight/tree/cd6b6e71eb472b3a4249830a7743333f019fa7b7/packages/path)
17
- - [License](https://github.com/flighthq/flight/blob/cd6b6e71eb472b3a4249830a7743333f019fa7b7/LICENSE.md)
16
+ - [Source for @flighthq/path@0.5.1-edge.638.d4dbd4e](https://github.com/flighthq/flight/tree/d4dbd4e347dc8be10b180c85a766ab50333dcb51/packages/path)
17
+ - [License](https://github.com/flighthq/flight/blob/d4dbd4e347dc8be10b180c85a766ab50333dcb51/LICENSE.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/path",
3
- "version": "0.5.0-next.1944.cd6b6e7",
3
+ "version": "0.5.1-edge.638.d4dbd4e",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,8 +38,8 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/math": "0.5.0-next.1944.cd6b6e7",
42
- "@flighthq/types": "0.5.0-next.1944.cd6b6e7"
41
+ "@flighthq/math": "0.5.1-edge.638.d4dbd4e",
42
+ "@flighthq/types": "0.5.1-edge.638.d4dbd4e"
43
43
  },
44
44
  "devDependencies": {
45
45
  "typescript": "^5.3.0"
@@ -1,3 +1,5 @@
1
+ import type { StrokeStyle } from '@flighthq/types/contract';
2
+
1
3
  import { appendPathClose, appendPathLineTo, appendPathMoveTo, appendPathRectangle, createPath } from './path';
2
4
  import { tessellateStrokePath } from './tessellateStrokePath';
3
5
 
@@ -63,6 +65,81 @@ describe('tessellateStrokePath', () => {
63
65
  expect(tessellateStrokePath(path, { width: 0 })).toBeNull();
64
66
  expect(tessellateStrokePath(path, { width: Number.NaN })).toBeNull();
65
67
  });
68
+
69
+ // The sharing contract on StrokeTessellator, which exists because a derived render pipeline
70
+ // INHERITS the tessellator bound to its source — one function object serves every state in that
71
+ // lineage. Each probe below asserts a property nothing else in this suite covers.
72
+ //
73
+ // Every one of them is grounded in a mesh asserted NONEMPTY first. A degenerate path returns an
74
+ // empty mesh, and against an empty mesh all four probes pass while proving nothing: two empty
75
+ // results always match, always have distinct empty arrays, and never disturb each other.
76
+
77
+ it('returns a nonempty mesh for the fixtures the sharing probes rely on', () => {
78
+ expect(tessellateStrokePath(makeOpenPath(), OPEN_STYLE)!.vertices.length).toBeGreaterThan(0);
79
+ expect(tessellateStrokePath(makeClosedPath(), CLOSED_STYLE, 0.1)!.vertices.length).toBeGreaterThan(0);
80
+ });
81
+
82
+ it('returns identical geometry when calls for two paths are interleaved', () => {
83
+ const openBaseline = JSON.stringify(tessellateStrokePath(makeOpenPath(), OPEN_STYLE));
84
+ const closedBaseline = JSON.stringify(tessellateStrokePath(makeClosedPath(), CLOSED_STYLE, 0.1));
85
+ let drifted = 0;
86
+ for (let i = 0; i < 50; i++) {
87
+ if (JSON.stringify(tessellateStrokePath(makeOpenPath(), OPEN_STYLE)) !== openBaseline) drifted++;
88
+ if (JSON.stringify(tessellateStrokePath(makeClosedPath(), CLOSED_STYLE, 0.1)) !== closedBaseline) drifted++;
89
+ }
90
+ expect(drifted).toBe(0);
91
+ });
92
+
93
+ it('allocates fresh result arrays for every call', () => {
94
+ const first = tessellateStrokePath(makeOpenPath(), OPEN_STYLE)!;
95
+ const second = tessellateStrokePath(makeOpenPath(), OPEN_STYLE)!;
96
+ expect(first.vertices).not.toBe(second.vertices);
97
+ expect(first.indices).not.toBe(second.indices);
98
+ });
99
+
100
+ it('is unaffected by a caller mutating an earlier result', () => {
101
+ const baseline = JSON.stringify(tessellateStrokePath(makeOpenPath(), OPEN_STYLE));
102
+ const owned = tessellateStrokePath(makeOpenPath(), OPEN_STYLE)!;
103
+ owned.vertices.length = 0;
104
+ owned.indices.push(999_999);
105
+ expect(JSON.stringify(tessellateStrokePath(makeOpenPath(), OPEN_STYLE))).toBe(baseline);
106
+ });
107
+
108
+ it('is reentrant when a nested call runs inside an in-progress one', () => {
109
+ const openBaseline = JSON.stringify(tessellateStrokePath(makeOpenPath(), OPEN_STYLE));
110
+ const closedBaseline = JSON.stringify(tessellateStrokePath(makeClosedPath(), CLOSED_STYLE, 0.1));
111
+ let nestedRan = false;
112
+ let nestedMatched = false;
113
+ // Reading `width` mid-tessellation triggers the inner call, so the two are genuinely interleaved
114
+ // rather than merely sequential.
115
+ const reentrantStyle = new Proxy(
116
+ { ...OPEN_STYLE },
117
+ {
118
+ get(target, key) {
119
+ if (key === 'width' && !nestedRan) {
120
+ nestedRan = true;
121
+ nestedMatched =
122
+ JSON.stringify(tessellateStrokePath(makeClosedPath(), CLOSED_STYLE, 0.1)) === closedBaseline;
123
+ }
124
+ return (target as Record<string | symbol, unknown>)[key];
125
+ },
126
+ },
127
+ ) as StrokeStyle;
128
+ const outer = JSON.stringify(tessellateStrokePath(makeOpenPath(), reentrantStyle));
129
+ expect(nestedRan).toBe(true);
130
+ expect(nestedMatched).toBe(true);
131
+ expect(outer).toBe(openBaseline);
132
+ });
133
+
134
+ it('does not mutate the path or style it is given', () => {
135
+ const path = makeOpenPath();
136
+ const style = { ...OPEN_STYLE };
137
+ const pathBefore = JSON.stringify(path);
138
+ const styleBefore = JSON.stringify(style);
139
+ tessellateStrokePath(path, style);
140
+ expect(JSON.stringify(path)).toBe(pathBefore);
141
+ expect(JSON.stringify(style)).toBe(styleBefore);
142
+ });
66
143
  });
67
144
 
68
145
  function bowTie() {
@@ -89,3 +166,22 @@ function getMeshArea(mesh: { indices: readonly number[]; vertices: readonly numb
89
166
  }
90
167
  return area;
91
168
  }
169
+
170
+ // Fixtures for the sharing-contract probes: one open polyline and one closed ring, both chosen because
171
+ // they produce real geometry (12 and 80 vertices).
172
+ const OPEN_STYLE: StrokeStyle = { cap: 'butt', join: 'miter', width: 10 };
173
+ const CLOSED_STYLE: StrokeStyle = { join: 'round', width: 7 };
174
+
175
+ function makeOpenPath(): ReturnType<typeof createPath> {
176
+ const path = createPath();
177
+ appendPathMoveTo(path, 0, 0);
178
+ appendPathLineTo(path, 100, 0);
179
+ appendPathLineTo(path, 100, 100);
180
+ return path;
181
+ }
182
+
183
+ function makeClosedPath(): ReturnType<typeof createPath> {
184
+ const path = createPath();
185
+ appendPathRectangle(path, 0, 0, 100, 50);
186
+ return path;
187
+ }