@datarailsshared/dr_renderer 1.5.150 → 1.5.159
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 +5 -2
- package/src/charts/dr_donut_chart.d.ts +79 -0
- package/src/charts/dr_donut_chart.js +7 -2
- package/src/charts/dr_gauge_categories_summary_chart.d.ts +136 -0
- package/src/charts/dr_gauge_chart.d.ts +18 -0
- package/src/charts/dr_gauge_chart.js +31 -0
- package/src/dr-renderer-helpers.d.ts +18 -0
- package/src/dr-renderer-helpers.js +2 -2
- package/src/dr_pivottable.d.ts +2 -0
- package/src/dr_pivottable.js +32 -75
- package/src/errors.js +1 -0
- package/src/{types/graph-table-renderer.d.ts → graph-table-renderer.d.ts} +57 -4
- package/src/graph-table-renderer.js +74 -2
- package/src/highcharts_renderer.d.ts +5 -0
- package/src/highcharts_renderer.js +1 -0
- package/src/index.d.ts +83 -86
- package/src/index.js +77 -3
- package/src/novix_renderer.d.ts +2 -0
- package/src/novix_renderer.js +7 -0
- package/src/options/builders.js +1 -0
- package/src/options/constants.js +1 -0
- package/src/options/elements.js +1 -0
- package/src/options/helpers.js +1 -0
- package/src/options/index.js +1 -0
- package/src/options/presets.js +1 -0
- package/src/pivot-table/freeze-panes/constants.d.ts +26 -0
- package/src/pivot-table/freeze-panes/constants.js +42 -0
- package/src/pivot-table/freeze-panes/freeze-panes.css +282 -0
- package/src/pivot-table/freeze-panes/index.d.ts +115 -0
- package/src/pivot-table/freeze-panes/index.js +143 -0
- package/src/pivot-table/freeze-panes/sticky-strategy.d.ts +38 -0
- package/src/pivot-table/freeze-panes/sticky-strategy.js +247 -0
- package/src/pivot-table/freeze-panes/transform-strategy.d.ts +61 -0
- package/src/pivot-table/freeze-panes/transform-strategy.js +131 -0
- package/src/pivot.css +2 -98
- package/src/published_items_renderer.d.ts +10 -0
- package/src/seriesPointStyles-helper.d.ts +2 -0
- package/src/smart_queries_helper.d.ts +12 -0
- package/src/value.formatter.d.ts +3 -0
- package/tests/dr-renderer-helpers.test.js +29 -0
- package/tests/pivot-table/freeze-panes/constants.test.js +92 -0
- package/tests/pivot-table/freeze-panes/index.test.js +193 -0
- package/tests/pivot-table/freeze-panes/sticky-strategy.test.js +542 -0
- package/tests/pivot-table/freeze-panes/transform-strategy.test.js +304 -0
- package/tsconfig.json +7 -10
- package/src/types/index.d.ts +0 -12
- package/tsconfig.tsbuildinfo +0 -1
- /package/src/{types/errors.d.ts → errors.d.ts} +0 -0
- /package/src/{types/options → options}/builders.d.ts +0 -0
- /package/src/{types/options → options}/constants.d.ts +0 -0
- /package/src/{types/options → options}/elements.d.ts +0 -0
- /package/src/{types/options → options}/helpers.d.ts +0 -0
- /package/src/{types/options → options}/index.d.ts +0 -0
- /package/src/{types/options → options}/presets.d.ts +0 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
const stickyStrategy = require('../../../src/pivot-table/freeze-panes/sticky-strategy');
|
|
2
|
+
const { STICKY_CSS_CLASSES, FREEZE_PANES_THRESHOLD_CHECK_DEBOUNCE_MS } = require('../../../src/pivot-table/freeze-panes/constants');
|
|
3
|
+
|
|
4
|
+
describe('freeze-panes/sticky-strategy', () => {
|
|
5
|
+
describe('getClassStrings', () => {
|
|
6
|
+
it('should return sticky class strings when enabled', () => {
|
|
7
|
+
const result = stickyStrategy.getClassStrings(true);
|
|
8
|
+
|
|
9
|
+
expect(result.horizontal).toBe(' ' + STICKY_CSS_CLASSES.HORIZONTAL);
|
|
10
|
+
expect(result.vertical).toBe(' ' + STICKY_CSS_CLASSES.VERTICAL);
|
|
11
|
+
expect(result.axis).toBe(' ' + STICKY_CSS_CLASSES.AXIS);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should return empty strings when disabled', () => {
|
|
15
|
+
const result = stickyStrategy.getClassStrings(false);
|
|
16
|
+
|
|
17
|
+
expect(result.horizontal).toBe('');
|
|
18
|
+
expect(result.vertical).toBe('');
|
|
19
|
+
expect(result.axis).toBe('');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return class strings with leading space', () => {
|
|
23
|
+
const result = stickyStrategy.getClassStrings(true);
|
|
24
|
+
|
|
25
|
+
expect(result.horizontal.startsWith(' ')).toBe(true);
|
|
26
|
+
expect(result.vertical.startsWith(' ')).toBe(true);
|
|
27
|
+
expect(result.axis.startsWith(' ')).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('initialize', () => {
|
|
32
|
+
let container;
|
|
33
|
+
let mockObserve;
|
|
34
|
+
let mockDisconnect;
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
container = document.createElement('div');
|
|
38
|
+
Object.defineProperty(container, 'clientWidth', { value: 1000, configurable: true });
|
|
39
|
+
Object.defineProperty(container, 'clientHeight', { value: 500, configurable: true });
|
|
40
|
+
document.body.appendChild(container);
|
|
41
|
+
|
|
42
|
+
mockObserve = jest.fn();
|
|
43
|
+
mockDisconnect = jest.fn();
|
|
44
|
+
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
|
45
|
+
observe: mockObserve,
|
|
46
|
+
disconnect: mockDisconnect,
|
|
47
|
+
}));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
document.body.removeChild(container);
|
|
52
|
+
jest.restoreAllMocks();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should apply container class', () => {
|
|
56
|
+
const table = document.createElement('table');
|
|
57
|
+
table.className = 'pvtTable';
|
|
58
|
+
container.appendChild(table);
|
|
59
|
+
|
|
60
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 1 });
|
|
61
|
+
|
|
62
|
+
expect(container.classList.contains(STICKY_CSS_CLASSES.CONTAINER)).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return destroy function', () => {
|
|
66
|
+
const table = document.createElement('table');
|
|
67
|
+
table.className = 'pvtTable';
|
|
68
|
+
container.appendChild(table);
|
|
69
|
+
|
|
70
|
+
const result = stickyStrategy.initialize(container, { rowAttrsLength: 1 });
|
|
71
|
+
|
|
72
|
+
expect(result).toBeDefined();
|
|
73
|
+
expect(result.destroy).toBeInstanceOf(Function);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should disconnect observer on destroy', () => {
|
|
77
|
+
const table = document.createElement('table');
|
|
78
|
+
table.className = 'pvtTable';
|
|
79
|
+
container.appendChild(table);
|
|
80
|
+
|
|
81
|
+
const result = stickyStrategy.initialize(container, { rowAttrsLength: 1 });
|
|
82
|
+
result.destroy();
|
|
83
|
+
|
|
84
|
+
expect(mockDisconnect).toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should observe container for resize', () => {
|
|
88
|
+
const table = document.createElement('table');
|
|
89
|
+
table.className = 'pvtTable';
|
|
90
|
+
container.appendChild(table);
|
|
91
|
+
|
|
92
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 1 });
|
|
93
|
+
|
|
94
|
+
expect(mockObserve).toHaveBeenCalledWith(container);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should return noop destroy function when no table found', () => {
|
|
98
|
+
const result = stickyStrategy.initialize(container, { rowAttrsLength: 1 });
|
|
99
|
+
|
|
100
|
+
expect(result.destroy).toBeInstanceOf(Function);
|
|
101
|
+
// Should not throw
|
|
102
|
+
result.destroy();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should handle missing rowAttrsLength', () => {
|
|
106
|
+
const table = document.createElement('table');
|
|
107
|
+
table.className = 'pvtTable';
|
|
108
|
+
container.appendChild(table);
|
|
109
|
+
|
|
110
|
+
const result = stickyStrategy.initialize(container, {});
|
|
111
|
+
|
|
112
|
+
expect(result.destroy).toBeInstanceOf(Function);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should calculate sticky positions for header cells', () => {
|
|
116
|
+
const table = document.createElement('table');
|
|
117
|
+
table.className = 'pvtTable';
|
|
118
|
+
table.innerHTML = `
|
|
119
|
+
<thead>
|
|
120
|
+
<tr>
|
|
121
|
+
<th class="${STICKY_CSS_CLASSES.AXIS}">Axis</th>
|
|
122
|
+
<th class="${STICKY_CSS_CLASSES.VERTICAL}">Header</th>
|
|
123
|
+
</tr>
|
|
124
|
+
</thead>
|
|
125
|
+
<tbody>
|
|
126
|
+
<tr>
|
|
127
|
+
<td class="${STICKY_CSS_CLASSES.HORIZONTAL}">Row Header</td>
|
|
128
|
+
<td>Data</td>
|
|
129
|
+
</tr>
|
|
130
|
+
</tbody>
|
|
131
|
+
`;
|
|
132
|
+
container.appendChild(table);
|
|
133
|
+
|
|
134
|
+
// Mock offsetHeight and offsetWidth
|
|
135
|
+
const th = table.querySelector('th');
|
|
136
|
+
Object.defineProperty(th, 'offsetHeight', { value: 30, configurable: true });
|
|
137
|
+
Object.defineProperty(th, 'offsetWidth', { value: 100, configurable: true });
|
|
138
|
+
|
|
139
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 1 });
|
|
140
|
+
|
|
141
|
+
// The axis cell should have top style set
|
|
142
|
+
const axisCell = table.querySelector('.' + STICKY_CSS_CLASSES.AXIS);
|
|
143
|
+
expect(axisCell.style.top).toBe('0px');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should toggle rows disabled class based on threshold', () => {
|
|
147
|
+
const table = document.createElement('table');
|
|
148
|
+
table.className = 'pvtTable';
|
|
149
|
+
table.innerHTML = `
|
|
150
|
+
<thead>
|
|
151
|
+
<tr><th>H1</th></tr>
|
|
152
|
+
<tr><th>H2</th></tr>
|
|
153
|
+
<tr><th>H3</th></tr>
|
|
154
|
+
<tr><th>H4</th></tr>
|
|
155
|
+
</thead>
|
|
156
|
+
<tbody><tr><td>Data</td></tr></tbody>
|
|
157
|
+
`;
|
|
158
|
+
container.appendChild(table);
|
|
159
|
+
|
|
160
|
+
// Set container height small so rows exceed threshold
|
|
161
|
+
Object.defineProperty(container, 'clientHeight', { value: 100, configurable: true });
|
|
162
|
+
|
|
163
|
+
// Mock row heights to exceed 70% of container
|
|
164
|
+
table.querySelectorAll('thead tr').forEach(row => {
|
|
165
|
+
Object.defineProperty(row, 'offsetHeight', { value: 25, configurable: true });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 0 });
|
|
169
|
+
|
|
170
|
+
expect(table.classList.contains(STICKY_CSS_CLASSES.ROWS_DISABLED)).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should toggle columns disabled class based on threshold', () => {
|
|
174
|
+
const table = document.createElement('table');
|
|
175
|
+
table.className = 'pvtTable';
|
|
176
|
+
table.innerHTML = `
|
|
177
|
+
<thead><tr><th>Header</th></tr></thead>
|
|
178
|
+
<tbody>
|
|
179
|
+
<tr>
|
|
180
|
+
<td class="rowcol0 ${STICKY_CSS_CLASSES.HORIZONTAL}">Row 1</td>
|
|
181
|
+
<td>Data</td>
|
|
182
|
+
</tr>
|
|
183
|
+
</tbody>
|
|
184
|
+
`;
|
|
185
|
+
container.appendChild(table);
|
|
186
|
+
|
|
187
|
+
// Set container width small so columns exceed threshold
|
|
188
|
+
Object.defineProperty(container, 'clientWidth', { value: 100, configurable: true });
|
|
189
|
+
|
|
190
|
+
// Mock cell width to exceed 70% of container
|
|
191
|
+
const firstCell = table.querySelector('tbody td');
|
|
192
|
+
Object.defineProperty(firstCell, 'offsetWidth', { value: 80, configurable: true });
|
|
193
|
+
|
|
194
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 1 });
|
|
195
|
+
|
|
196
|
+
expect(table.classList.contains(STICKY_CSS_CLASSES.COLUMNS_DISABLED)).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should enable freeze when dimensions are zero', () => {
|
|
200
|
+
const table = document.createElement('table');
|
|
201
|
+
table.className = 'pvtTable';
|
|
202
|
+
table.innerHTML = '<thead><tr><th>H</th></tr></thead><tbody><tr><td>D</td></tr></tbody>';
|
|
203
|
+
container.appendChild(table);
|
|
204
|
+
|
|
205
|
+
Object.defineProperty(container, 'clientWidth', { value: 0, configurable: true });
|
|
206
|
+
Object.defineProperty(container, 'clientHeight', { value: 0, configurable: true });
|
|
207
|
+
|
|
208
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 1 });
|
|
209
|
+
|
|
210
|
+
// Should not disable rows/columns when dimensions are zero
|
|
211
|
+
expect(table.classList.contains(STICKY_CSS_CLASSES.ROWS_DISABLED)).toBe(false);
|
|
212
|
+
expect(table.classList.contains(STICKY_CSS_CLASSES.COLUMNS_DISABLED)).toBe(false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should enable columns freeze when rowAttrsLength is 0', () => {
|
|
216
|
+
const table = document.createElement('table');
|
|
217
|
+
table.className = 'pvtTable';
|
|
218
|
+
table.innerHTML = '<thead><tr><th>H</th></tr></thead><tbody><tr><td>D</td></tr></tbody>';
|
|
219
|
+
container.appendChild(table);
|
|
220
|
+
|
|
221
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 0 });
|
|
222
|
+
|
|
223
|
+
expect(table.classList.contains(STICKY_CSS_CLASSES.COLUMNS_DISABLED)).toBe(false);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('debounce behavior', () => {
|
|
228
|
+
let container;
|
|
229
|
+
|
|
230
|
+
beforeEach(() => {
|
|
231
|
+
jest.useFakeTimers();
|
|
232
|
+
container = document.createElement('div');
|
|
233
|
+
Object.defineProperty(container, 'clientWidth', { value: 1000, configurable: true });
|
|
234
|
+
Object.defineProperty(container, 'clientHeight', { value: 500, configurable: true });
|
|
235
|
+
document.body.appendChild(container);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
afterEach(() => {
|
|
239
|
+
document.body.removeChild(container);
|
|
240
|
+
jest.useRealTimers();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should debounce resize observer callback', () => {
|
|
244
|
+
let observerCallback;
|
|
245
|
+
global.ResizeObserver = jest.fn().mockImplementation((callback) => {
|
|
246
|
+
observerCallback = callback;
|
|
247
|
+
return {
|
|
248
|
+
observe: jest.fn(),
|
|
249
|
+
disconnect: jest.fn(),
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const table = document.createElement('table');
|
|
254
|
+
table.className = 'pvtTable';
|
|
255
|
+
table.innerHTML = '<thead><tr><th>H</th></tr></thead><tbody><tr><td>D</td></tr></tbody>';
|
|
256
|
+
container.appendChild(table);
|
|
257
|
+
|
|
258
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 1 });
|
|
259
|
+
|
|
260
|
+
// Trigger multiple resize events
|
|
261
|
+
observerCallback();
|
|
262
|
+
observerCallback();
|
|
263
|
+
observerCallback();
|
|
264
|
+
|
|
265
|
+
// Fast-forward time but not enough for debounce
|
|
266
|
+
jest.advanceTimersByTime(100);
|
|
267
|
+
|
|
268
|
+
// Fast-forward past debounce time
|
|
269
|
+
jest.advanceTimersByTime(FREEZE_PANES_THRESHOLD_CHECK_DEBOUNCE_MS);
|
|
270
|
+
|
|
271
|
+
// The callback should have been called (no assertion needed, just ensuring no errors)
|
|
272
|
+
expect(true).toBe(true);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('calculateStickyPositions', () => {
|
|
277
|
+
let container;
|
|
278
|
+
|
|
279
|
+
beforeEach(() => {
|
|
280
|
+
container = document.createElement('div');
|
|
281
|
+
Object.defineProperty(container, 'clientWidth', { value: 1000, configurable: true });
|
|
282
|
+
Object.defineProperty(container, 'clientHeight', { value: 500, configurable: true });
|
|
283
|
+
document.body.appendChild(container);
|
|
284
|
+
|
|
285
|
+
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
|
286
|
+
observe: jest.fn(),
|
|
287
|
+
disconnect: jest.fn(),
|
|
288
|
+
}));
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
afterEach(() => {
|
|
292
|
+
document.body.removeChild(container);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should set top positions for multiple header rows', () => {
|
|
296
|
+
const table = document.createElement('table');
|
|
297
|
+
table.className = 'pvtTable';
|
|
298
|
+
table.innerHTML = `
|
|
299
|
+
<thead>
|
|
300
|
+
<tr>
|
|
301
|
+
<th class="${STICKY_CSS_CLASSES.VERTICAL}">Row 1</th>
|
|
302
|
+
</tr>
|
|
303
|
+
<tr>
|
|
304
|
+
<th class="${STICKY_CSS_CLASSES.VERTICAL}">Row 2</th>
|
|
305
|
+
</tr>
|
|
306
|
+
</thead>
|
|
307
|
+
<tbody><tr><td>Data</td></tr></tbody>
|
|
308
|
+
`;
|
|
309
|
+
container.appendChild(table);
|
|
310
|
+
|
|
311
|
+
const rows = table.querySelectorAll('thead tr');
|
|
312
|
+
rows.forEach(row => {
|
|
313
|
+
Object.defineProperty(row, 'offsetHeight', { value: 30, configurable: true });
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 0 });
|
|
317
|
+
|
|
318
|
+
const verticalCells = table.querySelectorAll('.' + STICKY_CSS_CLASSES.VERTICAL);
|
|
319
|
+
expect(verticalCells[0].style.top).toBe('0px');
|
|
320
|
+
expect(verticalCells[1].style.top).toBe('30px');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should set left positions for row headers in tbody', () => {
|
|
324
|
+
const table = document.createElement('table');
|
|
325
|
+
table.className = 'pvtTable';
|
|
326
|
+
table.innerHTML = `
|
|
327
|
+
<thead><tr><th>Header</th></tr></thead>
|
|
328
|
+
<tbody>
|
|
329
|
+
<tr>
|
|
330
|
+
<td class="rowcol0 ${STICKY_CSS_CLASSES.HORIZONTAL}">Col 1</td>
|
|
331
|
+
<td class="rowcol1 ${STICKY_CSS_CLASSES.HORIZONTAL}">Col 2</td>
|
|
332
|
+
<td>Data</td>
|
|
333
|
+
</tr>
|
|
334
|
+
</tbody>
|
|
335
|
+
`;
|
|
336
|
+
container.appendChild(table);
|
|
337
|
+
|
|
338
|
+
const cells = table.querySelectorAll('.' + STICKY_CSS_CLASSES.HORIZONTAL);
|
|
339
|
+
cells.forEach(cell => {
|
|
340
|
+
Object.defineProperty(cell, 'offsetWidth', { value: 80, configurable: true });
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 2 });
|
|
344
|
+
|
|
345
|
+
expect(cells[0].style.left).toBe('0px');
|
|
346
|
+
expect(cells[1].style.left).toBe('80px');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should set left positions for axis cells in thead', () => {
|
|
350
|
+
const table = document.createElement('table');
|
|
351
|
+
table.className = 'pvtTable';
|
|
352
|
+
table.innerHTML = `
|
|
353
|
+
<thead>
|
|
354
|
+
<tr>
|
|
355
|
+
<th class="${STICKY_CSS_CLASSES.AXIS}">Axis 1</th>
|
|
356
|
+
<th class="${STICKY_CSS_CLASSES.AXIS}">Axis 2</th>
|
|
357
|
+
<th class="${STICKY_CSS_CLASSES.VERTICAL}">Header</th>
|
|
358
|
+
</tr>
|
|
359
|
+
</thead>
|
|
360
|
+
<tbody><tr><td>Data</td></tr></tbody>
|
|
361
|
+
`;
|
|
362
|
+
container.appendChild(table);
|
|
363
|
+
|
|
364
|
+
const rows = table.querySelectorAll('thead tr');
|
|
365
|
+
rows.forEach(row => {
|
|
366
|
+
Object.defineProperty(row, 'offsetHeight', { value: 30, configurable: true });
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const axisCells = table.querySelectorAll('.' + STICKY_CSS_CLASSES.AXIS);
|
|
370
|
+
axisCells.forEach(cell => {
|
|
371
|
+
Object.defineProperty(cell, 'offsetWidth', { value: 100, configurable: true });
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 2 });
|
|
375
|
+
|
|
376
|
+
expect(axisCells[0].style.left).toBe('0px');
|
|
377
|
+
expect(axisCells[1].style.left).toBe('100px');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should correctly position cells when subtotals are present', () => {
|
|
381
|
+
const table = document.createElement('table');
|
|
382
|
+
table.className = 'pvtTable';
|
|
383
|
+
// Simulates subtotal structure where one <tr> has multiple <th> cells
|
|
384
|
+
table.innerHTML = `
|
|
385
|
+
<thead><tr><th>Header</th></tr></thead>
|
|
386
|
+
<tbody>
|
|
387
|
+
<tr class="pvtRowSubtotal row0">
|
|
388
|
+
<th class="pvtRowLabel row0 rowcol0 ${STICKY_CSS_CLASSES.HORIZONTAL}">Category A</th>
|
|
389
|
+
<th class="pvtRowLabel pvtRowSubtotal row0 rowcol0">Subtotal</th>
|
|
390
|
+
</tr>
|
|
391
|
+
<tr class="row1">
|
|
392
|
+
<th class="pvtRowLabel row1 rowcol0 ${STICKY_CSS_CLASSES.HORIZONTAL}">Category A</th>
|
|
393
|
+
<th class="pvtRowLabel row1 rowcol1 ${STICKY_CSS_CLASSES.HORIZONTAL}">Item 1</th>
|
|
394
|
+
<td>Data</td>
|
|
395
|
+
</tr>
|
|
396
|
+
<tr class="row2">
|
|
397
|
+
<th class="pvtRowLabel row2 rowcol0 ${STICKY_CSS_CLASSES.HORIZONTAL}">Category A</th>
|
|
398
|
+
<th class="pvtRowLabel row2 rowcol1 ${STICKY_CSS_CLASSES.HORIZONTAL}">Item 2</th>
|
|
399
|
+
<td>Data</td>
|
|
400
|
+
</tr>
|
|
401
|
+
</tbody>
|
|
402
|
+
`;
|
|
403
|
+
container.appendChild(table);
|
|
404
|
+
|
|
405
|
+
// Mock offsetWidth for cells - different widths for different column levels
|
|
406
|
+
const horizontalCells = table.querySelectorAll('.' + STICKY_CSS_CLASSES.HORIZONTAL);
|
|
407
|
+
horizontalCells.forEach(cell => {
|
|
408
|
+
if (cell.classList.contains('rowcol0')) {
|
|
409
|
+
Object.defineProperty(cell, 'offsetWidth', { value: 120, configurable: true });
|
|
410
|
+
} else if (cell.classList.contains('rowcol1')) {
|
|
411
|
+
Object.defineProperty(cell, 'offsetWidth', { value: 80, configurable: true });
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 2 });
|
|
416
|
+
|
|
417
|
+
// All rowcol0 cells should have left: 0px
|
|
418
|
+
// All rowcol1 cells should have left: 120px (width of rowcol0)
|
|
419
|
+
horizontalCells.forEach(cell => {
|
|
420
|
+
if (cell.classList.contains('rowcol0')) {
|
|
421
|
+
expect(cell.style.left).toBe('0px');
|
|
422
|
+
} else if (cell.classList.contains('rowcol1')) {
|
|
423
|
+
expect(cell.style.left).toBe('120px');
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('should skip subtotal placeholder cells when calculating widths', () => {
|
|
429
|
+
const table = document.createElement('table');
|
|
430
|
+
table.className = 'pvtTable';
|
|
431
|
+
table.innerHTML = `
|
|
432
|
+
<thead><tr><th>Header</th></tr></thead>
|
|
433
|
+
<tbody>
|
|
434
|
+
<tr class="pvtRowSubtotal row0">
|
|
435
|
+
<th class="pvtRowLabel row0 rowcol0 ${STICKY_CSS_CLASSES.HORIZONTAL}">Category</th>
|
|
436
|
+
<th class="pvtRowLabel pvtRowSubtotal row0 rowcol0 ${STICKY_CSS_CLASSES.HORIZONTAL}">Subtotal</th>
|
|
437
|
+
</tr>
|
|
438
|
+
</tbody>
|
|
439
|
+
`;
|
|
440
|
+
container.appendChild(table);
|
|
441
|
+
|
|
442
|
+
// Regular cell has width 100, subtotal cell has width 200
|
|
443
|
+
const horizontalCells = table.querySelectorAll('.' + STICKY_CSS_CLASSES.HORIZONTAL);
|
|
444
|
+
Object.defineProperty(horizontalCells[0], 'offsetWidth', { value: 100, configurable: true });
|
|
445
|
+
Object.defineProperty(horizontalCells[1], 'offsetWidth', { value: 200, configurable: true });
|
|
446
|
+
|
|
447
|
+
// The subtotal cell (pvtRowSubtotal class) should be ignored when calculating column widths
|
|
448
|
+
// So the positioning should be based on 100px, not 200px
|
|
449
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 1 });
|
|
450
|
+
|
|
451
|
+
// Both cells are rowcol0, so both should have left: 0px
|
|
452
|
+
expect(horizontalCells[0].style.left).toBe('0px');
|
|
453
|
+
expect(horizontalCells[1].style.left).toBe('0px');
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('should handle multiple row attribute levels with subtotals correctly', () => {
|
|
457
|
+
const table = document.createElement('table');
|
|
458
|
+
table.className = 'pvtTable';
|
|
459
|
+
table.innerHTML = `
|
|
460
|
+
<thead><tr><th>Header</th></tr></thead>
|
|
461
|
+
<tbody>
|
|
462
|
+
<tr class="row0">
|
|
463
|
+
<th class="rowcol0 ${STICKY_CSS_CLASSES.HORIZONTAL}">Level 0</th>
|
|
464
|
+
<th class="rowcol1 ${STICKY_CSS_CLASSES.HORIZONTAL}">Level 1</th>
|
|
465
|
+
<th class="rowcol2 ${STICKY_CSS_CLASSES.HORIZONTAL}">Level 2</th>
|
|
466
|
+
<td>Data</td>
|
|
467
|
+
</tr>
|
|
468
|
+
<tr class="pvtRowSubtotal row1">
|
|
469
|
+
<th class="rowcol0 ${STICKY_CSS_CLASSES.HORIZONTAL}">Category</th>
|
|
470
|
+
<th class="pvtRowSubtotal rowcol0">Subtotal</th>
|
|
471
|
+
</tr>
|
|
472
|
+
</tbody>
|
|
473
|
+
`;
|
|
474
|
+
container.appendChild(table);
|
|
475
|
+
|
|
476
|
+
const horizontalCells = table.querySelectorAll('.' + STICKY_CSS_CLASSES.HORIZONTAL);
|
|
477
|
+
horizontalCells.forEach(cell => {
|
|
478
|
+
if (cell.classList.contains('rowcol0')) {
|
|
479
|
+
Object.defineProperty(cell, 'offsetWidth', { value: 50, configurable: true });
|
|
480
|
+
} else if (cell.classList.contains('rowcol1')) {
|
|
481
|
+
Object.defineProperty(cell, 'offsetWidth', { value: 60, configurable: true });
|
|
482
|
+
} else if (cell.classList.contains('rowcol2')) {
|
|
483
|
+
Object.defineProperty(cell, 'offsetWidth', { value: 70, configurable: true });
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 3 });
|
|
488
|
+
|
|
489
|
+
// Check positions: rowcol0 -> 0, rowcol1 -> 50, rowcol2 -> 110
|
|
490
|
+
horizontalCells.forEach(cell => {
|
|
491
|
+
if (cell.classList.contains('rowcol0')) {
|
|
492
|
+
expect(cell.style.left).toBe('0px');
|
|
493
|
+
} else if (cell.classList.contains('rowcol1')) {
|
|
494
|
+
expect(cell.style.left).toBe('50px');
|
|
495
|
+
} else if (cell.classList.contains('rowcol2')) {
|
|
496
|
+
expect(cell.style.left).toBe('110px');
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('should handle empty tbody gracefully', () => {
|
|
502
|
+
const table = document.createElement('table');
|
|
503
|
+
table.className = 'pvtTable';
|
|
504
|
+
table.innerHTML = `
|
|
505
|
+
<thead><tr><th>Header</th></tr></thead>
|
|
506
|
+
<tbody></tbody>
|
|
507
|
+
`;
|
|
508
|
+
container.appendChild(table);
|
|
509
|
+
|
|
510
|
+
// Should not throw
|
|
511
|
+
expect(() => {
|
|
512
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 2 });
|
|
513
|
+
}).not.toThrow();
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it('should handle cells without rowcol classes', () => {
|
|
517
|
+
const table = document.createElement('table');
|
|
518
|
+
table.className = 'pvtTable';
|
|
519
|
+
table.innerHTML = `
|
|
520
|
+
<thead><tr><th>Header</th></tr></thead>
|
|
521
|
+
<tbody>
|
|
522
|
+
<tr>
|
|
523
|
+
<td class="${STICKY_CSS_CLASSES.HORIZONTAL}">No rowcol class</td>
|
|
524
|
+
<td>Data</td>
|
|
525
|
+
</tr>
|
|
526
|
+
</tbody>
|
|
527
|
+
`;
|
|
528
|
+
container.appendChild(table);
|
|
529
|
+
|
|
530
|
+
const horizontalCell = table.querySelector('.' + STICKY_CSS_CLASSES.HORIZONTAL);
|
|
531
|
+
Object.defineProperty(horizontalCell, 'offsetWidth', { value: 80, configurable: true });
|
|
532
|
+
|
|
533
|
+
// Should not throw, cell without rowcol class should not get positioned
|
|
534
|
+
expect(() => {
|
|
535
|
+
stickyStrategy.initialize(container, { rowAttrsLength: 1 });
|
|
536
|
+
}).not.toThrow();
|
|
537
|
+
|
|
538
|
+
// Cell without rowcol class should not have left style set
|
|
539
|
+
expect(horizontalCell.style.left).toBe('');
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
});
|