argent-grid 0.1.0
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/.github/workflows/pages.yml +68 -0
- package/AGENTS.md +179 -0
- package/README.md +222 -0
- package/demo-app/README.md +70 -0
- package/demo-app/angular.json +78 -0
- package/demo-app/e2e/benchmark.spec.ts +53 -0
- package/demo-app/e2e/demo-page.spec.ts +77 -0
- package/demo-app/e2e/grid-features.spec.ts +269 -0
- package/demo-app/package-lock.json +14023 -0
- package/demo-app/package.json +36 -0
- package/demo-app/playwright-test-menu.js +19 -0
- package/demo-app/playwright.config.ts +23 -0
- package/demo-app/src/app/app.component.ts +10 -0
- package/demo-app/src/app/app.config.ts +13 -0
- package/demo-app/src/app/app.routes.ts +7 -0
- package/demo-app/src/app/demo-page/demo-page.component.css +313 -0
- package/demo-app/src/app/demo-page/demo-page.component.html +124 -0
- package/demo-app/src/app/demo-page/demo-page.component.ts +366 -0
- package/demo-app/src/index.html +19 -0
- package/demo-app/src/main.ts +6 -0
- package/demo-app/tsconfig.json +31 -0
- package/ng-package.json +8 -0
- package/package.json +60 -0
- package/plan.md +131 -0
- package/setup-vitest.ts +18 -0
- package/src/lib/argent-grid.module.ts +21 -0
- package/src/lib/components/argent-grid.component.css +483 -0
- package/src/lib/components/argent-grid.component.html +320 -0
- package/src/lib/components/argent-grid.component.spec.ts +189 -0
- package/src/lib/components/argent-grid.component.ts +1188 -0
- package/src/lib/directives/ag-grid-compatibility.directive.ts +92 -0
- package/src/lib/rendering/canvas-renderer.ts +962 -0
- package/src/lib/rendering/render/blit.spec.ts +453 -0
- package/src/lib/rendering/render/blit.ts +393 -0
- package/src/lib/rendering/render/cells.ts +369 -0
- package/src/lib/rendering/render/index.ts +105 -0
- package/src/lib/rendering/render/lines.ts +363 -0
- package/src/lib/rendering/render/theme.spec.ts +282 -0
- package/src/lib/rendering/render/theme.ts +201 -0
- package/src/lib/rendering/render/types.ts +279 -0
- package/src/lib/rendering/render/walk.spec.ts +360 -0
- package/src/lib/rendering/render/walk.ts +360 -0
- package/src/lib/rendering/utils/damage-tracker.spec.ts +444 -0
- package/src/lib/rendering/utils/damage-tracker.ts +423 -0
- package/src/lib/rendering/utils/index.ts +7 -0
- package/src/lib/services/grid.service.spec.ts +1039 -0
- package/src/lib/services/grid.service.ts +1284 -0
- package/src/lib/types/ag-grid-types.ts +970 -0
- package/src/public-api.ts +22 -0
- package/tsconfig.json +32 -0
- package/tsconfig.lib.json +11 -0
- package/tsconfig.spec.json +8 -0
- package/vitest.config.ts +55 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for Damage Tracker
|
|
3
|
+
*
|
|
4
|
+
* Tests for DamageTracker class and related utilities.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
DamageTracker,
|
|
9
|
+
getDirtyBounds,
|
|
10
|
+
mergeRectangles,
|
|
11
|
+
} from './damage-tracker';
|
|
12
|
+
import { DirtyRegions, Rectangle } from '../render/types';
|
|
13
|
+
|
|
14
|
+
describe('DamageTracker', () => {
|
|
15
|
+
let tracker: DamageTracker;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
tracker = new DamageTracker();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('markCellDirty', () => {
|
|
22
|
+
it('should mark a single cell as dirty', () => {
|
|
23
|
+
tracker.markCellDirty(2, 5);
|
|
24
|
+
|
|
25
|
+
expect(tracker.isCellDirty(2, 5)).toBe(true);
|
|
26
|
+
expect(tracker.isCellDirty(2, 6)).toBe(false);
|
|
27
|
+
expect(tracker.isCellDirty(3, 5)).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should mark multiple cells', () => {
|
|
31
|
+
tracker.markCellsDirty([
|
|
32
|
+
[0, 0],
|
|
33
|
+
[1, 1],
|
|
34
|
+
[2, 2],
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
expect(tracker.isCellDirty(0, 0)).toBe(true);
|
|
38
|
+
expect(tracker.isCellDirty(1, 1)).toBe(true);
|
|
39
|
+
expect(tracker.isCellDirty(2, 2)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('markRowDirty', () => {
|
|
44
|
+
it('should mark a row as dirty', () => {
|
|
45
|
+
tracker.markRowDirty(5);
|
|
46
|
+
|
|
47
|
+
expect(tracker.isRowDirty(5)).toBe(true);
|
|
48
|
+
expect(tracker.isRowDirty(4)).toBe(false);
|
|
49
|
+
expect(tracker.isRowDirty(6)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should mark multiple rows', () => {
|
|
53
|
+
tracker.markRowsDirty([1, 3, 5]);
|
|
54
|
+
|
|
55
|
+
expect(tracker.isRowDirty(1)).toBe(true);
|
|
56
|
+
expect(tracker.isRowDirty(3)).toBe(true);
|
|
57
|
+
expect(tracker.isRowDirty(5)).toBe(true);
|
|
58
|
+
expect(tracker.isRowDirty(2)).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should make all cells in row dirty', () => {
|
|
62
|
+
tracker.markRowDirty(3);
|
|
63
|
+
|
|
64
|
+
expect(tracker.isCellDirty(0, 3)).toBe(true);
|
|
65
|
+
expect(tracker.isCellDirty(5, 3)).toBe(true);
|
|
66
|
+
expect(tracker.isCellDirty(10, 3)).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('markColumnDirty', () => {
|
|
71
|
+
it('should mark a column as dirty', () => {
|
|
72
|
+
tracker.markColumnDirty(2);
|
|
73
|
+
|
|
74
|
+
expect(tracker.isColumnDirty(2)).toBe(true);
|
|
75
|
+
expect(tracker.isColumnDirty(1)).toBe(false);
|
|
76
|
+
expect(tracker.isColumnDirty(3)).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should mark multiple columns', () => {
|
|
80
|
+
tracker.markColumnsDirty([0, 2, 4]);
|
|
81
|
+
|
|
82
|
+
expect(tracker.isColumnDirty(0)).toBe(true);
|
|
83
|
+
expect(tracker.isColumnDirty(2)).toBe(true);
|
|
84
|
+
expect(tracker.isColumnDirty(4)).toBe(true);
|
|
85
|
+
expect(tracker.isColumnDirty(1)).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should make all cells in column dirty', () => {
|
|
89
|
+
tracker.markColumnDirty(3);
|
|
90
|
+
|
|
91
|
+
expect(tracker.isCellDirty(3, 0)).toBe(true);
|
|
92
|
+
expect(tracker.isCellDirty(3, 5)).toBe(true);
|
|
93
|
+
expect(tracker.isCellDirty(3, 10)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('markAllDirty', () => {
|
|
98
|
+
it('should require full redraw', () => {
|
|
99
|
+
tracker.markAllDirty();
|
|
100
|
+
|
|
101
|
+
expect(tracker.needsFullRedraw()).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should clear existing damage', () => {
|
|
105
|
+
tracker.markCellDirty(0, 0);
|
|
106
|
+
tracker.markRowDirty(5);
|
|
107
|
+
tracker.markColumnDirty(2);
|
|
108
|
+
tracker.markAllDirty();
|
|
109
|
+
|
|
110
|
+
const regions = tracker.getDirtyRegions();
|
|
111
|
+
expect(regions.rows.size).toBe(0);
|
|
112
|
+
expect(regions.columns.size).toBe(0);
|
|
113
|
+
expect(regions.cells.size).toBe(0);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('markSelectionChanged', () => {
|
|
118
|
+
it('should mark rows that changed selection state', () => {
|
|
119
|
+
const oldSelection = new Set([1, 2, 3]);
|
|
120
|
+
const newSelection = new Set([2, 3, 4]);
|
|
121
|
+
|
|
122
|
+
tracker.markSelectionChanged(oldSelection, newSelection);
|
|
123
|
+
|
|
124
|
+
// Row 1 was deselected
|
|
125
|
+
expect(tracker.isRowDirty(1)).toBe(true);
|
|
126
|
+
// Row 4 was newly selected
|
|
127
|
+
expect(tracker.isRowDirty(4)).toBe(true);
|
|
128
|
+
// Rows 2, 3 unchanged selection state
|
|
129
|
+
expect(tracker.isRowDirty(2)).toBe(false);
|
|
130
|
+
expect(tracker.isRowDirty(3)).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should handle empty selections', () => {
|
|
134
|
+
tracker.markSelectionChanged(new Set(), new Set([1, 2, 3]));
|
|
135
|
+
|
|
136
|
+
expect(tracker.isRowDirty(1)).toBe(true);
|
|
137
|
+
expect(tracker.isRowDirty(2)).toBe(true);
|
|
138
|
+
expect(tracker.isRowDirty(3)).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('hasDamage', () => {
|
|
143
|
+
it('should return false when clean', () => {
|
|
144
|
+
expect(tracker.hasDamage()).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should return true when cell is dirty', () => {
|
|
148
|
+
tracker.markCellDirty(0, 0);
|
|
149
|
+
expect(tracker.hasDamage()).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should return true when row is dirty', () => {
|
|
153
|
+
tracker.markRowDirty(0);
|
|
154
|
+
expect(tracker.hasDamage()).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should return true when column is dirty', () => {
|
|
158
|
+
tracker.markColumnDirty(0);
|
|
159
|
+
expect(tracker.hasDamage()).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should return true when full redraw needed', () => {
|
|
163
|
+
tracker.markAllDirty();
|
|
164
|
+
expect(tracker.hasDamage()).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('isCellDirty', () => {
|
|
169
|
+
it('should return true when cell is directly marked', () => {
|
|
170
|
+
tracker.markCellDirty(3, 7);
|
|
171
|
+
expect(tracker.isCellDirty(3, 7)).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should return true when row is marked', () => {
|
|
175
|
+
tracker.markRowDirty(5);
|
|
176
|
+
expect(tracker.isCellDirty(0, 5)).toBe(true);
|
|
177
|
+
expect(tracker.isCellDirty(10, 5)).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should return true when column is marked', () => {
|
|
181
|
+
tracker.markColumnDirty(2);
|
|
182
|
+
expect(tracker.isCellDirty(2, 0)).toBe(true);
|
|
183
|
+
expect(tracker.isCellDirty(2, 100)).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should return true when full redraw needed', () => {
|
|
187
|
+
tracker.markAllDirty();
|
|
188
|
+
expect(tracker.isCellDirty(100, 100)).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('getDirtyRegions', () => {
|
|
193
|
+
it('should return all dirty information', () => {
|
|
194
|
+
tracker.markCellDirty(0, 0);
|
|
195
|
+
tracker.markRowDirty(5);
|
|
196
|
+
tracker.markColumnDirty(3);
|
|
197
|
+
|
|
198
|
+
const regions = tracker.getDirtyRegions();
|
|
199
|
+
|
|
200
|
+
expect(regions.full).toBe(false);
|
|
201
|
+
expect(regions.cells.has('0,0')).toBe(true);
|
|
202
|
+
expect(regions.rows.has(5)).toBe(true);
|
|
203
|
+
expect(regions.columns.has(3)).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('getDirtyRows/Cols/Cells', () => {
|
|
208
|
+
it('should return dirty rows as array', () => {
|
|
209
|
+
tracker.markRowsDirty([1, 3, 5]);
|
|
210
|
+
expect(tracker.getDirtyRows()).toEqual(expect.arrayContaining([1, 3, 5]));
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should return dirty columns as array', () => {
|
|
214
|
+
tracker.markColumnsDirty([0, 2, 4]);
|
|
215
|
+
expect(tracker.getDirtyColumns()).toEqual(expect.arrayContaining([0, 2, 4]));
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should return dirty cells as array', () => {
|
|
219
|
+
tracker.markCellsDirty([
|
|
220
|
+
[1, 2],
|
|
221
|
+
[3, 4],
|
|
222
|
+
]);
|
|
223
|
+
const cells = tracker.getDirtyCells();
|
|
224
|
+
expect(cells).toEqual(expect.arrayContaining([[1, 2], [3, 4]]));
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('optimize', () => {
|
|
229
|
+
it('should promote cells to rows when concentrated', () => {
|
|
230
|
+
// Many cells in the same row
|
|
231
|
+
for (let col = 0; col < 20; col++) {
|
|
232
|
+
tracker.markCellDirty(col, 5);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
tracker.optimize({ maxCells: 10 });
|
|
236
|
+
|
|
237
|
+
// Should have promoted to row
|
|
238
|
+
expect(tracker.isRowDirty(5)).toBe(true);
|
|
239
|
+
// Individual cells should be cleared
|
|
240
|
+
expect(tracker.getDirtyCells().length).toBe(0);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should do full redraw when too many rows', () => {
|
|
244
|
+
for (let i = 0; i < 150; i++) {
|
|
245
|
+
tracker.markRowDirty(i);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
tracker.optimize({ maxRows: 100 });
|
|
249
|
+
|
|
250
|
+
expect(tracker.needsFullRedraw()).toBe(true);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should do full redraw when too many columns', () => {
|
|
254
|
+
for (let i = 0; i < 25; i++) {
|
|
255
|
+
tracker.markColumnDirty(i);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
tracker.optimize({ maxColumns: 20 });
|
|
259
|
+
|
|
260
|
+
expect(tracker.needsFullRedraw()).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('clear', () => {
|
|
265
|
+
it('should clear all damage', () => {
|
|
266
|
+
tracker.markCellDirty(0, 0);
|
|
267
|
+
tracker.markRowDirty(5);
|
|
268
|
+
tracker.markColumnDirty(3);
|
|
269
|
+
tracker.markAllDirty();
|
|
270
|
+
|
|
271
|
+
tracker.clear();
|
|
272
|
+
|
|
273
|
+
expect(tracker.hasDamage()).toBe(false);
|
|
274
|
+
expect(tracker.needsFullRedraw()).toBe(false);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('reset', () => {
|
|
279
|
+
it('should clear all damage and reset timing', () => {
|
|
280
|
+
tracker.markCellDirty(0, 0);
|
|
281
|
+
tracker.clear();
|
|
282
|
+
|
|
283
|
+
tracker.reset();
|
|
284
|
+
|
|
285
|
+
expect(tracker.hasDamage()).toBe(false);
|
|
286
|
+
expect(tracker.getLastRenderTime()).toBe(0);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('render timing', () => {
|
|
291
|
+
it('should track last render time', () => {
|
|
292
|
+
vi.useRealTimers();
|
|
293
|
+
tracker.clear(); // Sets lastRenderTime
|
|
294
|
+
expect(tracker.getLastRenderTime()).toBeGreaterThan(0);
|
|
295
|
+
vi.useFakeTimers();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should calculate time since last render', () => {
|
|
299
|
+
vi.useRealTimers();
|
|
300
|
+
tracker.clear();
|
|
301
|
+
const time = tracker.getTimeSinceLastRender();
|
|
302
|
+
expect(time).toBeGreaterThanOrEqual(0);
|
|
303
|
+
vi.useFakeTimers();
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('getDirtyBounds', () => {
|
|
309
|
+
it('should return full viewport for full redraw', () => {
|
|
310
|
+
const dirty: DirtyRegions = {
|
|
311
|
+
full: true,
|
|
312
|
+
rows: new Set(),
|
|
313
|
+
columns: new Set(),
|
|
314
|
+
cells: new Set(),
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const bounds = getDirtyBounds(dirty, 32, [100, 100, 100], 0, 0, 400, 600);
|
|
318
|
+
|
|
319
|
+
expect(bounds).toHaveLength(1);
|
|
320
|
+
expect(bounds[0]).toEqual({ x: 0, y: 0, width: 400, height: 600 });
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should return bounds for dirty rows', () => {
|
|
324
|
+
const dirty: DirtyRegions = {
|
|
325
|
+
full: false,
|
|
326
|
+
rows: new Set([5, 10]),
|
|
327
|
+
columns: new Set(),
|
|
328
|
+
cells: new Set(),
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const bounds = getDirtyBounds(dirty, 32, [100, 100], 0, 0, 400, 600);
|
|
332
|
+
|
|
333
|
+
// Row 5 at y=160, Row 10 at y=320
|
|
334
|
+
expect(bounds.length).toBe(2);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should clip bounds to viewport', () => {
|
|
338
|
+
const dirty: DirtyRegions = {
|
|
339
|
+
full: false,
|
|
340
|
+
rows: new Set([0]), // First row
|
|
341
|
+
columns: new Set(),
|
|
342
|
+
cells: new Set(),
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// With scroll, first row is above viewport
|
|
346
|
+
const bounds = getDirtyBounds(dirty, 32, [100], 100, 0, 400, 600);
|
|
347
|
+
|
|
348
|
+
// Should be empty since row 0 is scrolled out
|
|
349
|
+
expect(bounds.length).toBe(0);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should return bounds for dirty columns', () => {
|
|
353
|
+
const dirty: DirtyRegions = {
|
|
354
|
+
full: false,
|
|
355
|
+
rows: new Set(),
|
|
356
|
+
columns: new Set([1]),
|
|
357
|
+
cells: new Set(),
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const bounds = getDirtyBounds(dirty, 32, [50, 100, 75], 0, 0, 400, 600);
|
|
361
|
+
|
|
362
|
+
// Column 1 starts at x=50, width=100
|
|
363
|
+
expect(bounds.length).toBe(1);
|
|
364
|
+
expect(bounds[0].x).toBe(50);
|
|
365
|
+
expect(bounds[0].width).toBe(100);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should return bounds for dirty cells', () => {
|
|
369
|
+
const dirty: DirtyRegions = {
|
|
370
|
+
full: false,
|
|
371
|
+
rows: new Set(),
|
|
372
|
+
columns: new Set(),
|
|
373
|
+
cells: new Set(['2,5']),
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const bounds = getDirtyBounds(dirty, 32, [50, 100, 75], 0, 0, 400, 600);
|
|
377
|
+
|
|
378
|
+
// Cell (2,5): column 2 starts at x=150, row 5 at y=160
|
|
379
|
+
expect(bounds.length).toBe(1);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should skip cells covered by dirty rows/columns', () => {
|
|
383
|
+
const dirty: DirtyRegions = {
|
|
384
|
+
full: false,
|
|
385
|
+
rows: new Set([5]),
|
|
386
|
+
columns: new Set(),
|
|
387
|
+
cells: new Set(['2,5']), // Same row as dirty row
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const bounds = getDirtyBounds(dirty, 32, [100, 100, 100], 0, 0, 400, 600);
|
|
391
|
+
|
|
392
|
+
// Should only have bounds for row 5, not separate cell
|
|
393
|
+
expect(bounds.length).toBe(1);
|
|
394
|
+
expect(bounds[0].y).toBe(160); // Row 5
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
describe('mergeRectangles', () => {
|
|
399
|
+
it('should return single rectangle unchanged', () => {
|
|
400
|
+
const rects: Rectangle[] = [{ x: 0, y: 0, width: 100, height: 100 }];
|
|
401
|
+
expect(mergeRectangles(rects)).toEqual(rects);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should merge overlapping rectangles', () => {
|
|
405
|
+
const rects: Rectangle[] = [
|
|
406
|
+
{ x: 0, y: 0, width: 100, height: 100 },
|
|
407
|
+
{ x: 50, y: 50, width: 100, height: 100 },
|
|
408
|
+
];
|
|
409
|
+
|
|
410
|
+
const merged = mergeRectangles(rects);
|
|
411
|
+
|
|
412
|
+
expect(merged.length).toBe(1);
|
|
413
|
+
expect(merged[0].x).toBe(0);
|
|
414
|
+
expect(merged[0].y).toBe(0);
|
|
415
|
+
expect(merged[0].width).toBe(150);
|
|
416
|
+
expect(merged[0].height).toBe(150);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('should not merge non-overlapping rectangles', () => {
|
|
420
|
+
const rects: Rectangle[] = [
|
|
421
|
+
{ x: 0, y: 0, width: 50, height: 50 },
|
|
422
|
+
{ x: 100, y: 100, width: 50, height: 50 },
|
|
423
|
+
];
|
|
424
|
+
|
|
425
|
+
const merged = mergeRectangles(rects);
|
|
426
|
+
expect(merged.length).toBe(2);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('should merge adjacent rectangles', () => {
|
|
430
|
+
const rects: Rectangle[] = [
|
|
431
|
+
{ x: 0, y: 0, width: 100, height: 50 },
|
|
432
|
+
{ x: 0, y: 50, width: 100, height: 50 },
|
|
433
|
+
];
|
|
434
|
+
|
|
435
|
+
const merged = mergeRectangles(rects);
|
|
436
|
+
|
|
437
|
+
expect(merged.length).toBe(1);
|
|
438
|
+
expect(merged[0].height).toBe(100);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('should handle empty array', () => {
|
|
442
|
+
expect(mergeRectangles([])).toEqual([]);
|
|
443
|
+
});
|
|
444
|
+
});
|