@viewscript/renderer 0.1.0-20260515014709 → 0.1.0-20260515015849

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 (34) hide show
  1. package/dist/compiler/chunk-splitter.d.ts +1 -1
  2. package/dist/rasterizer/canvas-mapper.d.ts +1 -1
  3. package/dist/rasterizer/error-distribution.d.ts +1 -1
  4. package/dist/rasterizer/gradient-mapper.d.ts +1 -1
  5. package/dist/rasterizer/topology-rounding.d.ts +1 -1
  6. package/dist/runtime/event-backpressure.d.ts +1 -1
  7. package/dist/runtime/render-loop.d.ts +1 -1
  8. package/dist/semantic/semantic-translator.d.ts +1 -1
  9. package/package.json +1 -1
  10. package/src/compiler/chunk-splitter.ts +1 -1
  11. package/src/rasterizer/__tests__/error-distribution.test.ts +11 -11
  12. package/src/rasterizer/canvas-mapper.ts +1 -1
  13. package/src/rasterizer/error-distribution.ts +1 -1
  14. package/src/rasterizer/gradient-mapper.ts +1 -1
  15. package/src/rasterizer/topology-rounding.ts +1 -1
  16. package/src/runtime/__tests__/event-backpressure.test.ts +3 -3
  17. package/src/runtime/__tests__/ffi-integration.test.ts +8 -0
  18. package/src/runtime/event-backpressure.ts +1 -1
  19. package/src/runtime/render-loop.ts +1 -1
  20. package/src/semantic/__tests__/semantic-translator.test.ts +2 -2
  21. package/src/semantic/semantic-translator.ts +1 -1
  22. package/tsconfig.json +2 -1
  23. package/dist/rasterizer/__tests__/error-distribution.test.d.ts +0 -7
  24. package/dist/rasterizer/__tests__/error-distribution.test.js +0 -322
  25. package/dist/runtime/__tests__/event-backpressure.test.d.ts +0 -10
  26. package/dist/runtime/__tests__/event-backpressure.test.js +0 -190
  27. package/dist/runtime/__tests__/ffi-dispatcher.test.d.ts +0 -11
  28. package/dist/runtime/__tests__/ffi-dispatcher.test.js +0 -408
  29. package/dist/runtime/__tests__/ffi-integration.test.d.ts +0 -9
  30. package/dist/runtime/__tests__/ffi-integration.test.js +0 -514
  31. package/dist/runtime/__tests__/wasm-solver-bridge.test.d.ts +0 -9
  32. package/dist/runtime/__tests__/wasm-solver-bridge.test.js +0 -214
  33. package/dist/semantic/__tests__/semantic-translator.test.d.ts +0 -4
  34. package/dist/semantic/__tests__/semantic-translator.test.js +0 -203
@@ -35,7 +35,7 @@
35
35
  * 5. Compute chunk dependency DAG
36
36
  * ```
37
37
  */
38
- import type { EntityId, ConstraintId, ChunkId, Chunk, Rational } from '../ast/types';
38
+ import type { EntityId, ConstraintId, ChunkId, Chunk, Rational } from '../ast/types.js';
39
39
  interface IRConstraint {
40
40
  id: ConstraintId;
41
41
  target: EntityId;
@@ -28,7 +28,7 @@
28
28
  * coordinates, ensuring seamless curve connections
29
29
  * 3. **Fill Rule Semantics**: SVG fill-rule (nonzero/evenodd) is preserved
30
30
  */
31
- import type { EntityId, Rational, PathCommand } from '../ast/types';
31
+ import type { EntityId, Rational, PathCommand } from '../ast/types.js';
32
32
  /**
33
33
  * Control point with resolved rational coordinates.
34
34
  */
@@ -34,7 +34,7 @@
34
34
  *
35
35
  * LRM distributes exactly (T - Σ⌊rᵢ⌋) extra pixels to achieve Σ = T.
36
36
  */
37
- import type { EntityId } from '../ast/types';
37
+ import type { EntityId } from '../ast/types.js';
38
38
  /**
39
39
  * A sibling group: children that must sum to parent's dimension.
40
40
  */
@@ -36,7 +36,7 @@
36
36
  * canvas.drawRect(bounds, paint);
37
37
  * ```
38
38
  */
39
- import type { Rational, RasterBounds } from '../ast/types';
39
+ import type { Rational, RasterBounds } from '../ast/types.js';
40
40
  /** GPU shader backend interface (minimal subset needed for gradients) */
41
41
  export interface GpuShaderBackend {
42
42
  Shader: {
@@ -61,7 +61,7 @@
61
61
  * Assert all topological constraints are satisfied
62
62
  * ```
63
63
  */
64
- import type { EntityId, Rational, RasterBounds, PVectorBounds } from '../ast/types';
64
+ import type { EntityId, Rational, RasterBounds, PVectorBounds } from '../ast/types.js';
65
65
  /**
66
66
  * A coordinate in the pre-rasterization space.
67
67
  */
@@ -23,7 +23,7 @@
23
23
  * 3. Configurable throttle strategies
24
24
  * 4. Priority queues for critical events (click > mousemove)
25
25
  */
26
- import type { EntityId } from '../ast/types';
26
+ import type { EntityId } from '../ast/types.js';
27
27
  /**
28
28
  * Semantic T-vector state keys.
29
29
  *
@@ -34,7 +34,7 @@
34
34
  *
35
35
  * We NEVER touch: width, height, top, left, margin, padding (reflow triggers)
36
36
  */
37
- import type { EntityId, RenderableEntity, RasterBounds, PVectorBounds } from '../ast/types';
37
+ import type { EntityId, RenderableEntity, RasterBounds, PVectorBounds } from '../ast/types.js';
38
38
  import type { FfiDispatcher, PendingFfiCall } from './ffi-dispatcher.js';
39
39
  import type { WasmSolverBridge } from './wasm-solver-bridge.js';
40
40
  /**
@@ -34,7 +34,7 @@
34
34
  * const diff = translator.compareSolutions(solution1, solution2);
35
35
  * ```
36
36
  */
37
- import type { EntityId } from '../ast/types';
37
+ import type { EntityId } from '../ast/types.js';
38
38
  /**
39
39
  * Raw solution from solver: "entity_id:component" -> "numerator/denominator"
40
40
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viewscript/renderer",
3
- "version": "0.1.0-20260515014709",
3
+ "version": "0.1.0-20260515015849",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -45,7 +45,7 @@ import type {
45
45
  RenderableEntity,
46
46
  Rational,
47
47
  PVectorBounds,
48
- } from '../ast/types';
48
+ } from '../ast/types.js';
49
49
 
50
50
  // =============================================================================
51
51
  // Input Types (from IR)
@@ -11,7 +11,7 @@ import {
11
11
  applyErrorDistribution,
12
12
  type SiblingGroup,
13
13
  type ContainmentConstraint,
14
- } from '../error-distribution';
14
+ } from '../error-distribution.js';
15
15
 
16
16
  describe('Largest Remainder Method', () => {
17
17
  /**
@@ -44,17 +44,17 @@ describe('Largest Remainder Method', () => {
44
44
  expect(result.isExact).toBe(true);
45
45
 
46
46
  // Assert: Each child gets 33 or 34 pixels
47
- const pixels = result.dimensions.map(d => d.pixels);
48
- expect(pixels.every(p => p === 33 || p === 34)).toBe(true);
47
+ const pixels = result.dimensions.map((d: { pixels: number }) => d.pixels);
48
+ expect(pixels.every((p: number) => p === 33 || p === 34)).toBe(true);
49
49
 
50
50
  // Assert: Exactly one child gets the extra pixel
51
- const count34 = pixels.filter(p => p === 34).length;
52
- const count33 = pixels.filter(p => p === 33).length;
51
+ const count34 = pixels.filter((p: number) => p === 34).length;
52
+ const count33 = pixels.filter((p: number) => p === 33).length;
53
53
  expect(count34).toBe(1);
54
54
  expect(count33).toBe(2);
55
55
 
56
56
  // Assert: Sum is exactly 100 (33 + 33 + 34 = 100)
57
- expect(pixels.reduce((a, b) => a + b, 0)).toBe(100);
57
+ expect(pixels.reduce((a: number, b: number) => a + b, 0)).toBe(100);
58
58
 
59
59
  // Assert: Distribution is [34, 33, 33] (leftmost gets extra due to tie-break)
60
60
  expect(pixels).toEqual([34, 33, 33]);
@@ -81,7 +81,7 @@ describe('Largest Remainder Method', () => {
81
81
  // Assert
82
82
  expect(result.totalPixels).toBe(100);
83
83
  expect(result.isExact).toBe(true);
84
- expect(result.dimensions.map(d => d.pixels)).toEqual([25, 25, 25, 25]);
84
+ expect(result.dimensions.map((d: { pixels: number }) => d.pixels)).toEqual([25, 25, 25, 25]);
85
85
  });
86
86
 
87
87
  it('distributes multiple extra pixels by remainder priority', () => {
@@ -108,7 +108,7 @@ describe('Largest Remainder Method', () => {
108
108
  // Assert
109
109
  expect(result.totalPixels).toBe(100);
110
110
  expect(result.isExact).toBe(true);
111
- expect(result.dimensions.map(d => d.pixels)).toEqual([41, 31, 28]);
111
+ expect(result.dimensions.map((d: { pixels: number }) => d.pixels)).toEqual([41, 31, 28]);
112
112
  });
113
113
 
114
114
  it('handles single child (trivial case)', () => {
@@ -164,7 +164,7 @@ describe('Largest Remainder Method', () => {
164
164
 
165
165
  // Assert: shortfall is 1, so first element (leftmost) gets it
166
166
  expect(result.totalPixels).toBe(7);
167
- expect(result.dimensions.map(d => d.pixels)).toEqual([4, 3]);
167
+ expect(result.dimensions.map((d: { pixels: number }) => d.pixels)).toEqual([4, 3]);
168
168
  });
169
169
 
170
170
  it('handles vertical axis', () => {
@@ -302,8 +302,8 @@ describe('Edge Cases', () => {
302
302
 
303
303
  // 7 * 14 = 98, shortfall = 2
304
304
  // Two elements get 15px, five get 14px
305
- const fifteens = result.dimensions.filter(d => d.pixels === 15).length;
306
- const fourteens = result.dimensions.filter(d => d.pixels === 14).length;
305
+ const fifteens = result.dimensions.filter((d: { pixels: number }) => d.pixels === 15).length;
306
+ const fourteens = result.dimensions.filter((d: { pixels: number }) => d.pixels === 14).length;
307
307
  expect(fifteens).toBe(2);
308
308
  expect(fourteens).toBe(5);
309
309
  });
@@ -29,7 +29,7 @@
29
29
  * 3. **Fill Rule Semantics**: SVG fill-rule (nonzero/evenodd) is preserved
30
30
  */
31
31
 
32
- import type { EntityId, Rational, PathCommand, FillStyle, StrokeStyle } from '../ast/types';
32
+ import type { EntityId, Rational, PathCommand, FillStyle, StrokeStyle } from '../ast/types.js';
33
33
 
34
34
  // =============================================================================
35
35
  // Input Types (from P-Dimension Solver)
@@ -35,7 +35,7 @@
35
35
  * LRM distributes exactly (T - Σ⌊rᵢ⌋) extra pixels to achieve Σ = T.
36
36
  */
37
37
 
38
- import type { EntityId, Rational } from '../ast/types';
38
+ import type { EntityId, Rational } from '../ast/types.js';
39
39
 
40
40
  // =============================================================================
41
41
  // Types
@@ -37,7 +37,7 @@
37
37
  * ```
38
38
  */
39
39
 
40
- import type { Rational, RasterBounds } from '../ast/types';
40
+ import type { Rational, RasterBounds } from '../ast/types.js';
41
41
 
42
42
  // =============================================================================
43
43
  // Types
@@ -62,7 +62,7 @@
62
62
  * ```
63
63
  */
64
64
 
65
- import type { EntityId, Rational, RasterBounds, PVectorBounds } from '../ast/types';
65
+ import type { EntityId, Rational, RasterBounds, PVectorBounds } from '../ast/types.js';
66
66
 
67
67
  // =============================================================================
68
68
  // Types
@@ -14,7 +14,7 @@ import {
14
14
  EventPriority,
15
15
  type QDimensionEvent,
16
16
  type TStateKey,
17
- } from '../event-backpressure';
17
+ } from '../event-backpressure.js';
18
18
 
19
19
  describe('EventBuffer', () => {
20
20
  let buffer: EventBuffer;
@@ -187,8 +187,8 @@ describe('EventBuffer', () => {
187
187
 
188
188
  expect(flushed).toHaveLength(2);
189
189
  // Both events should be present
190
- expect(flushed.some(e => e.entityId === 1)).toBe(true);
191
- expect(flushed.some(e => e.entityId === 2)).toBe(true);
190
+ expect(flushed.some((e: QDimensionEvent) => e.entityId === 1)).toBe(true);
191
+ expect(flushed.some((e: QDimensionEvent) => e.entityId === 2)).toBe(true);
192
192
  });
193
193
  });
194
194
 
@@ -127,6 +127,10 @@ function createBindManifestContext(bindings: {
127
127
  line: i + 2,
128
128
  })),
129
129
  triggers: [],
130
+ componentDecls: [],
131
+ components: [],
132
+ scene: null,
133
+ consts: [],
130
134
  errors: [],
131
135
  };
132
136
 
@@ -168,6 +172,10 @@ function createTriggerManifestContext(triggers: {
168
172
  functionArgs: t.functionArgs,
169
173
  line: i + 2,
170
174
  })),
175
+ componentDecls: [],
176
+ components: [],
177
+ scene: null,
178
+ consts: [],
171
179
  errors: [],
172
180
  };
173
181
 
@@ -24,7 +24,7 @@
24
24
  * 4. Priority queues for critical events (click > mousemove)
25
25
  */
26
26
 
27
- import type { EntityId } from '../ast/types';
27
+ import type { EntityId } from '../ast/types.js';
28
28
 
29
29
  // =============================================================================
30
30
  // Types
@@ -41,7 +41,7 @@ import type {
41
41
  RasterBounds,
42
42
  PVectorBounds,
43
43
  ChunkId,
44
- } from '../ast/types';
44
+ } from '../ast/types.js';
45
45
  import type { FfiDispatcher, PendingFfiCall } from './ffi-dispatcher.js';
46
46
  import type { WasmSolverBridge } from './wasm-solver-bridge.js';
47
47
 
@@ -9,7 +9,7 @@ import {
9
9
  createEmptyRegistry,
10
10
  type EntityMetadata,
11
11
  type RawSolution,
12
- } from '../semantic-translator';
12
+ } from '../semantic-translator.js';
13
13
 
14
14
  describe('SemanticTranslator', () => {
15
15
  describe('parseVarId', () => {
@@ -162,7 +162,7 @@ describe('SemanticTranslator', () => {
162
162
  const result = translator.translateSolution(rawSolution, 0);
163
163
 
164
164
  expect(result.relationships.length).toBeGreaterThan(0);
165
- const alignment = result.relationships.find(r => r.type === 'alignment');
165
+ const alignment = result.relationships.find((r: { type: string }) => r.type === 'alignment');
166
166
  expect(alignment).toBeDefined();
167
167
  expect(alignment?.description).toContain('horizontally aligned');
168
168
  });
@@ -35,7 +35,7 @@
35
35
  * ```
36
36
  */
37
37
 
38
- import type { EntityId, Rational } from '../ast/types';
38
+ import type { EntityId, Rational } from '../ast/types.js';
39
39
 
40
40
  // =============================================================================
41
41
  // Types for Raw Solver Output
package/tsconfig.json CHANGED
@@ -8,5 +8,6 @@
8
8
  "outDir": "dist",
9
9
  "rootDir": "src"
10
10
  },
11
- "include": ["src"]
11
+ "include": ["src"],
12
+ "exclude": ["src/**/__tests__"]
12
13
  }
@@ -1,7 +0,0 @@
1
- /**
2
- * Unit Tests for Subpixel Error Distribution
3
- *
4
- * These tests verify the Largest Remainder Method implementation
5
- * guarantees spatial closure (no gaps, no overflow).
6
- */
7
- export {};
@@ -1,322 +0,0 @@
1
- /**
2
- * Unit Tests for Subpixel Error Distribution
3
- *
4
- * These tests verify the Largest Remainder Method implementation
5
- * guarantees spatial closure (no gaps, no overflow).
6
- */
7
- import { describe, it, expect } from 'vitest';
8
- import { distributeWithLargestRemainder, applyErrorDistribution, } from '../error-distribution';
9
- describe('Largest Remainder Method', () => {
10
- /**
11
- * CRITICAL TEST: Architect's Decision #2
12
- *
13
- * "幅100pxのコンテナ内に、幅33.333...pxの要素が3つ、
14
- * 隙間なく隣接して配置される"
15
- *
16
- * This is the canonical case that motivated error distribution.
17
- */
18
- it('distributes 100px among 3 equal children (33.333...px each) without gaps', () => {
19
- // Arrange
20
- const group = {
21
- parentId: 1,
22
- childIds: [10, 11, 12],
23
- axis: 'horizontal',
24
- parentDimension: 100,
25
- childDimensions: new Map([
26
- [10, 100 / 3], // 33.333...
27
- [11, 100 / 3], // 33.333...
28
- [12, 100 / 3], // 33.333...
29
- ]),
30
- };
31
- // Act
32
- const result = distributeWithLargestRemainder(group);
33
- // Assert: Sum must equal parent exactly
34
- expect(result.totalPixels).toBe(100);
35
- expect(result.isExact).toBe(true);
36
- // Assert: Each child gets 33 or 34 pixels
37
- const pixels = result.dimensions.map(d => d.pixels);
38
- expect(pixels.every(p => p === 33 || p === 34)).toBe(true);
39
- // Assert: Exactly one child gets the extra pixel
40
- const count34 = pixels.filter(p => p === 34).length;
41
- const count33 = pixels.filter(p => p === 33).length;
42
- expect(count34).toBe(1);
43
- expect(count33).toBe(2);
44
- // Assert: Sum is exactly 100 (33 + 33 + 34 = 100)
45
- expect(pixels.reduce((a, b) => a + b, 0)).toBe(100);
46
- // Assert: Distribution is [34, 33, 33] (leftmost gets extra due to tie-break)
47
- expect(pixels).toEqual([34, 33, 33]);
48
- });
49
- it('handles exact division (no remainder)', () => {
50
- // Arrange: 100px / 4 = 25px exactly
51
- const group = {
52
- parentId: 1,
53
- childIds: [10, 11, 12, 13],
54
- axis: 'horizontal',
55
- parentDimension: 100,
56
- childDimensions: new Map([
57
- [10, 25],
58
- [11, 25],
59
- [12, 25],
60
- [13, 25],
61
- ]),
62
- };
63
- // Act
64
- const result = distributeWithLargestRemainder(group);
65
- // Assert
66
- expect(result.totalPixels).toBe(100);
67
- expect(result.isExact).toBe(true);
68
- expect(result.dimensions.map(d => d.pixels)).toEqual([25, 25, 25, 25]);
69
- });
70
- it('distributes multiple extra pixels by remainder priority', () => {
71
- // Arrange: 100px for [40.9, 30.8, 28.3]
72
- // floors: [40, 30, 28] = 98
73
- // remainders: [0.9, 0.8, 0.3]
74
- // shortfall: 2px
75
- // Extra pixels go to: 40.9 → 41, 30.8 → 31
76
- const group = {
77
- parentId: 1,
78
- childIds: [10, 11, 12],
79
- axis: 'horizontal',
80
- parentDimension: 100,
81
- childDimensions: new Map([
82
- [10, 40.9],
83
- [11, 30.8],
84
- [12, 28.3],
85
- ]),
86
- };
87
- // Act
88
- const result = distributeWithLargestRemainder(group);
89
- // Assert
90
- expect(result.totalPixels).toBe(100);
91
- expect(result.isExact).toBe(true);
92
- expect(result.dimensions.map(d => d.pixels)).toEqual([41, 31, 28]);
93
- });
94
- it('handles single child (trivial case)', () => {
95
- const group = {
96
- parentId: 1,
97
- childIds: [10],
98
- axis: 'horizontal',
99
- parentDimension: 100,
100
- childDimensions: new Map([[10, 100]]),
101
- };
102
- const result = distributeWithLargestRemainder(group);
103
- expect(result.totalPixels).toBe(100);
104
- expect(result.dimensions[0].pixels).toBe(100);
105
- });
106
- it('handles empty children', () => {
107
- const group = {
108
- parentId: 1,
109
- childIds: [],
110
- axis: 'horizontal',
111
- parentDimension: 100,
112
- childDimensions: new Map(),
113
- };
114
- const result = distributeWithLargestRemainder(group);
115
- expect(result.totalPixels).toBe(0);
116
- expect(result.dimensions).toEqual([]);
117
- });
118
- it('ties are broken by layout order (leftmost first)', () => {
119
- // Arrange: 10px for [3.5, 3.5] - equal remainders
120
- // floors: [3, 3] = 6
121
- // remainders: [0.5, 0.5] - TIE!
122
- // shortfall: 4px (wait, that's wrong)
123
- // Actually: 10 - 6 = 4... no, 3.5 + 3.5 = 7, not 10
124
- // Let me fix: 7px for [3.5, 3.5]
125
- const group = {
126
- parentId: 1,
127
- childIds: [10, 11],
128
- axis: 'horizontal',
129
- parentDimension: 7,
130
- childDimensions: new Map([
131
- [10, 3.5],
132
- [11, 3.5],
133
- ]),
134
- };
135
- // Act
136
- const result = distributeWithLargestRemainder(group);
137
- // Assert: shortfall is 1, so first element (leftmost) gets it
138
- expect(result.totalPixels).toBe(7);
139
- expect(result.dimensions.map(d => d.pixels)).toEqual([4, 3]);
140
- });
141
- it('handles vertical axis', () => {
142
- const group = {
143
- parentId: 1,
144
- childIds: [10, 11, 12],
145
- axis: 'vertical',
146
- parentDimension: 100,
147
- childDimensions: new Map([
148
- [10, 100 / 3],
149
- [11, 100 / 3],
150
- [12, 100 / 3],
151
- ]),
152
- };
153
- const result = distributeWithLargestRemainder(group);
154
- expect(result.totalPixels).toBe(100);
155
- expect(result.isExact).toBe(true);
156
- });
157
- it('records error for each element', () => {
158
- const group = {
159
- parentId: 1,
160
- childIds: [10, 11, 12],
161
- axis: 'horizontal',
162
- parentDimension: 100,
163
- childDimensions: new Map([
164
- [10, 100 / 3], // ~33.333
165
- [11, 100 / 3],
166
- [12, 100 / 3],
167
- ]),
168
- };
169
- const result = distributeWithLargestRemainder(group);
170
- // First element: 34 - 33.333... = +0.666...
171
- expect(result.dimensions[0].error).toBeCloseTo(0.6667, 3);
172
- // Other elements: 33 - 33.333... = -0.333...
173
- expect(result.dimensions[1].error).toBeCloseTo(-0.3333, 3);
174
- expect(result.dimensions[2].error).toBeCloseTo(-0.3333, 3);
175
- });
176
- });
177
- describe('applyErrorDistribution (Integration)', () => {
178
- it('adjusts child bounds to fit parent exactly', () => {
179
- // Arrange
180
- const roundedBounds = new Map([
181
- [1, { x: 0, y: 0, width: 100, height: 50 }], // Parent
182
- [10, { x: 0, y: 0, width: 33, height: 50 }], // Child 1 (naive)
183
- [11, { x: 33, y: 0, width: 33, height: 50 }], // Child 2
184
- [12, { x: 66, y: 0, width: 33, height: 50 }], // Child 3
185
- // Sum: 33 + 33 + 33 = 99 ← GAP!
186
- ]);
187
- const containments = [
188
- {
189
- parentId: 1,
190
- childIds: [10, 11, 12],
191
- axis: 'horizontal',
192
- },
193
- ];
194
- // Act
195
- const result = applyErrorDistribution(roundedBounds, containments);
196
- // Assert: Children sum to parent width exactly
197
- const child1 = result.get(10);
198
- const child2 = result.get(11);
199
- const child3 = result.get(12);
200
- const totalWidth = child1.width + child2.width + child3.width;
201
- expect(totalWidth).toBe(100);
202
- // Assert: Children are contiguous (no gaps)
203
- expect(child2.x).toBe(child1.x + child1.width);
204
- expect(child3.x).toBe(child2.x + child2.width);
205
- });
206
- it('handles multiple containment constraints', () => {
207
- const roundedBounds = new Map([
208
- // Horizontal container
209
- [1, { x: 0, y: 0, width: 100, height: 50 }],
210
- [10, { x: 0, y: 0, width: 33, height: 50 }],
211
- [11, { x: 33, y: 0, width: 33, height: 50 }],
212
- [12, { x: 66, y: 0, width: 33, height: 50 }],
213
- // Vertical container
214
- [2, { x: 0, y: 50, width: 100, height: 100 }],
215
- [20, { x: 0, y: 50, width: 100, height: 33 }],
216
- [21, { x: 0, y: 83, width: 100, height: 33 }],
217
- [22, { x: 0, y: 116, width: 100, height: 33 }],
218
- ]);
219
- const containments = [
220
- { parentId: 1, childIds: [10, 11, 12], axis: 'horizontal' },
221
- { parentId: 2, childIds: [20, 21, 22], axis: 'vertical' },
222
- ];
223
- const result = applyErrorDistribution(roundedBounds, containments);
224
- // Horizontal container
225
- const hTotal = result.get(10).width + result.get(11).width + result.get(12).width;
226
- expect(hTotal).toBe(100);
227
- // Vertical container
228
- const vTotal = result.get(20).height + result.get(21).height + result.get(22).height;
229
- expect(vTotal).toBe(100);
230
- });
231
- });
232
- describe('Edge Cases', () => {
233
- it('handles very small fractional differences', () => {
234
- // 100px / 7 = 14.285714...
235
- const group = {
236
- parentId: 1,
237
- childIds: [1, 2, 3, 4, 5, 6, 7],
238
- axis: 'horizontal',
239
- parentDimension: 100,
240
- childDimensions: new Map([
241
- [1, 100 / 7],
242
- [2, 100 / 7],
243
- [3, 100 / 7],
244
- [4, 100 / 7],
245
- [5, 100 / 7],
246
- [6, 100 / 7],
247
- [7, 100 / 7],
248
- ]),
249
- };
250
- const result = distributeWithLargestRemainder(group);
251
- expect(result.totalPixels).toBe(100);
252
- expect(result.isExact).toBe(true);
253
- // 7 * 14 = 98, shortfall = 2
254
- // Two elements get 15px, five get 14px
255
- const fifteens = result.dimensions.filter(d => d.pixels === 15).length;
256
- const fourteens = result.dimensions.filter(d => d.pixels === 14).length;
257
- expect(fifteens).toBe(2);
258
- expect(fourteens).toBe(5);
259
- });
260
- it('handles zero-width children', () => {
261
- const group = {
262
- parentId: 1,
263
- childIds: [10, 11],
264
- axis: 'horizontal',
265
- parentDimension: 100,
266
- childDimensions: new Map([
267
- [10, 100],
268
- [11, 0],
269
- ]),
270
- };
271
- const result = distributeWithLargestRemainder(group);
272
- expect(result.totalPixels).toBe(100);
273
- expect(result.dimensions[0].pixels).toBe(100);
274
- expect(result.dimensions[1].pixels).toBe(0);
275
- });
276
- it('uses proportional scaling when children exceed parent', () => {
277
- // Children sum to 150, but parent is only 100
278
- const group = {
279
- parentId: 1,
280
- childIds: [10, 11, 12],
281
- axis: 'horizontal',
282
- parentDimension: 100,
283
- childDimensions: new Map([
284
- [10, 50],
285
- [11, 50],
286
- [12, 50],
287
- ]),
288
- };
289
- const result = distributeWithLargestRemainder(group);
290
- // Falls back to proportional: each gets ~33.33
291
- expect(result.totalPixels).toBe(100);
292
- expect(result.method).toBe('first-fit');
293
- });
294
- });
295
- describe('Proof: No Gaps or Overflow', () => {
296
- /**
297
- * Property test: For ANY valid input, sum(children) === parent
298
- */
299
- it('maintains invariant for random inputs', () => {
300
- const testCases = [
301
- { parent: 100, children: [100 / 3, 100 / 3, 100 / 3] },
302
- { parent: 1000, children: [1000 / 7, 1000 / 7, 1000 / 7, 1000 / 7, 1000 / 7, 1000 / 7, 1000 / 7] },
303
- { parent: 50, children: [12.5, 12.5, 12.5, 12.5] },
304
- { parent: 99, children: [33, 33, 33] },
305
- { parent: 101, children: [50.5, 50.5] },
306
- { parent: 1, children: [0.5, 0.5] },
307
- { parent: 2, children: [0.6, 0.7, 0.7] },
308
- ];
309
- for (const tc of testCases) {
310
- const group = {
311
- parentId: 1,
312
- childIds: tc.children.map((_, i) => i + 10),
313
- axis: 'horizontal',
314
- parentDimension: tc.parent,
315
- childDimensions: new Map(tc.children.map((c, i) => [i + 10, c])),
316
- };
317
- const result = distributeWithLargestRemainder(group);
318
- expect(result.totalPixels).toBe(tc.parent);
319
- expect(result.isExact).toBe(true);
320
- }
321
- });
322
- });
@@ -1,10 +0,0 @@
1
- /**
2
- * Tests for Q-Dimension Event Backpressure Control
3
- *
4
- * Validates:
5
- * 1. Event coalescing (latest-only sampling)
6
- * 2. Priority queue handling (critical events)
7
- * 3. Async event isolation and merging
8
- * 4. P/Q boundary enforcement (T-state keys only)
9
- */
10
- export {};