@svgrid/grid 1.1.2 → 1.2.3

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 (143) hide show
  1. package/dist/SvGrid.css +294 -16
  2. package/dist/SvGrid.svelte +4 -1
  3. package/dist/SvGridDropdown.svelte +7 -3
  4. package/dist/cdn/svgrid.js +13506 -0
  5. package/dist/cdn/svgrid.svelte-external.js +11190 -0
  6. package/package.json +12 -2
  7. package/src/SvGrid.css +294 -16
  8. package/src/SvGrid.svelte +4 -1
  9. package/src/SvGridDropdown.svelte +7 -3
  10. package/dist/SvGrid.helpers.test.d.ts +0 -1
  11. package/dist/SvGrid.helpers.test.js +0 -298
  12. package/dist/SvGridChart.test.d.ts +0 -1
  13. package/dist/SvGridChart.test.js +0 -62
  14. package/dist/a11y.contract.test.d.ts +0 -1
  15. package/dist/a11y.contract.test.js +0 -39
  16. package/dist/a11y.test.d.ts +0 -1
  17. package/dist/a11y.test.js +0 -44
  18. package/dist/aligned-grids.test.d.ts +0 -1
  19. package/dist/aligned-grids.test.js +0 -75
  20. package/dist/build-api.coverage.test.d.ts +0 -20
  21. package/dist/build-api.coverage.test.js +0 -505
  22. package/dist/cell-formatting.test.d.ts +0 -1
  23. package/dist/cell-formatting.test.js +0 -234
  24. package/dist/cell-render.test.d.ts +0 -1
  25. package/dist/cell-render.test.js +0 -338
  26. package/dist/chart-export.test.d.ts +0 -1
  27. package/dist/chart-export.test.js +0 -302
  28. package/dist/chart.coverage.test.d.ts +0 -1
  29. package/dist/chart.coverage.test.js +0 -748
  30. package/dist/chart.test.d.ts +0 -1
  31. package/dist/chart.test.js +0 -289
  32. package/dist/clipboard.test.d.ts +0 -1
  33. package/dist/clipboard.test.js +0 -700
  34. package/dist/collaboration.coverage.test.d.ts +0 -1
  35. package/dist/collaboration.coverage.test.js +0 -200
  36. package/dist/collaboration.test.d.ts +0 -1
  37. package/dist/collaboration.test.js +0 -65
  38. package/dist/column-groups.test.d.ts +0 -1
  39. package/dist/column-groups.test.js +0 -56
  40. package/dist/column-types.test.d.ts +0 -1
  41. package/dist/column-types.test.js +0 -62
  42. package/dist/columns.test.d.ts +0 -1
  43. package/dist/columns.test.js +0 -625
  44. package/dist/conditional-formatting.test.d.ts +0 -1
  45. package/dist/conditional-formatting.test.js +0 -87
  46. package/dist/core.coverage.test.d.ts +0 -1
  47. package/dist/core.coverage.test.js +0 -186
  48. package/dist/core.performance.test.d.ts +0 -1
  49. package/dist/core.performance.test.js +0 -29
  50. package/dist/createGrid.test.d.ts +0 -1
  51. package/dist/createGrid.test.js +0 -9
  52. package/dist/editing.test.d.ts +0 -1
  53. package/dist/editing.test.js +0 -732
  54. package/dist/editors/cell-editors.coverage.test.d.ts +0 -1
  55. package/dist/editors/cell-editors.coverage.test.js +0 -139
  56. package/dist/editors/cell-editors.test.d.ts +0 -1
  57. package/dist/editors/cell-editors.test.js +0 -75
  58. package/dist/facet-buckets.test.d.ts +0 -1
  59. package/dist/facet-buckets.test.js +0 -296
  60. package/dist/fill-patterns.test.d.ts +0 -1
  61. package/dist/fill-patterns.test.js +0 -81
  62. package/dist/filter-operators.test.d.ts +0 -1
  63. package/dist/filter-operators.test.js +0 -135
  64. package/dist/filtering/excel-filters.test.d.ts +0 -1
  65. package/dist/filtering/excel-filters.test.js +0 -116
  66. package/dist/filtering/locale-filter.test.d.ts +0 -13
  67. package/dist/filtering/locale-filter.test.js +0 -189
  68. package/dist/flex-render.test.d.ts +0 -1
  69. package/dist/flex-render.test.js +0 -142
  70. package/dist/group-aggregate.test.d.ts +0 -1
  71. package/dist/group-aggregate.test.js +0 -32
  72. package/dist/hyperformula-adapter.test.d.ts +0 -1
  73. package/dist/hyperformula-adapter.test.js +0 -205
  74. package/dist/keyboard-handlers.coverage.test.d.ts +0 -1
  75. package/dist/keyboard-handlers.coverage.test.js +0 -495
  76. package/dist/keyboard.test.d.ts +0 -1
  77. package/dist/keyboard.test.js +0 -27
  78. package/dist/menus.test.d.ts +0 -1
  79. package/dist/menus.test.js +0 -560
  80. package/dist/named-views.coverage.test.d.ts +0 -1
  81. package/dist/named-views.coverage.test.js +0 -180
  82. package/dist/named-views.test.d.ts +0 -1
  83. package/dist/named-views.test.js +0 -57
  84. package/dist/new-features.test.d.ts +0 -1
  85. package/dist/new-features.test.js +0 -183
  86. package/dist/render-component.test.d.ts +0 -1
  87. package/dist/render-component.test.js +0 -41
  88. package/dist/row-drag.test.d.ts +0 -1
  89. package/dist/row-drag.test.js +0 -142
  90. package/dist/row-resize.test.d.ts +0 -1
  91. package/dist/row-resize.test.js +0 -329
  92. package/dist/scroll-sync.test.d.ts +0 -1
  93. package/dist/scroll-sync.test.js +0 -290
  94. package/dist/selection.multi-range.test.d.ts +0 -1
  95. package/dist/selection.multi-range.test.js +0 -55
  96. package/dist/selection.test.d.ts +0 -1
  97. package/dist/selection.test.js +0 -647
  98. package/dist/server-data-source.coverage.test.d.ts +0 -1
  99. package/dist/server-data-source.coverage.test.js +0 -154
  100. package/dist/server-data-source.test.d.ts +0 -1
  101. package/dist/server-data-source.test.js +0 -81
  102. package/dist/sparkline.test.d.ts +0 -1
  103. package/dist/sparkline.test.js +0 -52
  104. package/dist/spreadsheet.test.d.ts +0 -1
  105. package/dist/spreadsheet.test.js +0 -446
  106. package/dist/subscribe.test.d.ts +0 -1
  107. package/dist/subscribe.test.js +0 -93
  108. package/dist/svgrid.api-extensions.test.d.ts +0 -14
  109. package/dist/svgrid.api-extensions.test.js +0 -334
  110. package/dist/svgrid.api.test.d.ts +0 -15
  111. package/dist/svgrid.api.test.js +0 -169
  112. package/dist/svgrid.behavior.test.d.ts +0 -15
  113. package/dist/svgrid.behavior.test.js +0 -625
  114. package/dist/svgrid.column-reorder.test.d.ts +0 -15
  115. package/dist/svgrid.column-reorder.test.js +0 -224
  116. package/dist/svgrid.comments-autocomplete.test.d.ts +0 -1
  117. package/dist/svgrid.comments-autocomplete.test.js +0 -96
  118. package/dist/svgrid.context-menu.test.d.ts +0 -1
  119. package/dist/svgrid.context-menu.test.js +0 -102
  120. package/dist/svgrid.features.test.d.ts +0 -1
  121. package/dist/svgrid.features.test.js +0 -129
  122. package/dist/svgrid.interaction.test.d.ts +0 -9
  123. package/dist/svgrid.interaction.test.js +0 -349
  124. package/dist/svgrid.locale-filtering.test.d.ts +0 -15
  125. package/dist/svgrid.locale-filtering.test.js +0 -261
  126. package/dist/svgrid.new-features.wrapper.test.d.ts +0 -1
  127. package/dist/svgrid.new-features.wrapper.test.js +0 -194
  128. package/dist/svgrid.row-pinning.test.d.ts +0 -14
  129. package/dist/svgrid.row-pinning.test.js +0 -250
  130. package/dist/svgrid.set-filter.test.d.ts +0 -17
  131. package/dist/svgrid.set-filter.test.js +0 -268
  132. package/dist/svgrid.wrapper.test.d.ts +0 -1
  133. package/dist/svgrid.wrapper.test.js +0 -59
  134. package/dist/test-setup.d.ts +0 -1
  135. package/dist/test-setup.js +0 -35
  136. package/dist/transaction.test.d.ts +0 -1
  137. package/dist/transaction.test.js +0 -89
  138. package/dist/virtualization/column-virtualizer.test.d.ts +0 -1
  139. package/dist/virtualization/column-virtualizer.test.js +0 -25
  140. package/dist/virtualization/scroll-scaling.test.d.ts +0 -1
  141. package/dist/virtualization/scroll-scaling.test.js +0 -127
  142. package/dist/virtualization/virtualizer.test.d.ts +0 -1
  143. package/dist/virtualization/virtualizer.test.js +0 -41
@@ -1,234 +0,0 @@
1
- /**
2
- * Unit tests for cell-formatting helpers.
3
- *
4
- * Covers every code path in:
5
- * - resolveDatePattern (every shortcut + datetime variant + unknown fallback)
6
- * - getDateFormatter (cache hit + cache miss path)
7
- * - formatNumericWithConfig (number, currency, percent + valueIsPercentPoints,
8
- * NaN/Infinity/null fallbacks, locale variants, option overrides)
9
- *
10
- * Locale assertions use partial substring matches where the exact Intl output
11
- * differs between Node ICU builds.
12
- */
13
- import { describe, expect, it } from 'vitest';
14
- import { formatNumericWithConfig, resolveDatePattern, getDateFormatter, } from './cell-formatting';
15
- describe('resolveDatePattern', () => {
16
- it('returns undefined for missing pattern', () => {
17
- expect(resolveDatePattern(undefined, 'date')).toBeUndefined();
18
- expect(resolveDatePattern('', 'date')).toBeUndefined();
19
- });
20
- it('handles the "d" (short numeric) date pattern', () => {
21
- expect(resolveDatePattern('d', 'date')).toEqual({
22
- year: 'numeric',
23
- month: 'numeric',
24
- day: 'numeric',
25
- });
26
- });
27
- it('handles the "d" datetime pattern (adds hour + minute)', () => {
28
- const opts = resolveDatePattern('d', 'datetime');
29
- expect(opts.hour).toBe('numeric');
30
- expect(opts.minute).toBe('numeric');
31
- expect(opts.year).toBe('numeric');
32
- });
33
- it('handles the "D" (long) date pattern', () => {
34
- expect(resolveDatePattern('D', 'date')).toEqual({
35
- weekday: 'long',
36
- year: 'numeric',
37
- month: 'long',
38
- day: 'numeric',
39
- });
40
- });
41
- it('handles the "D" datetime pattern with time', () => {
42
- const opts = resolveDatePattern('D', 'datetime');
43
- expect(opts.weekday).toBe('long');
44
- expect(opts.month).toBe('long');
45
- expect(opts.hour).toBe('numeric');
46
- });
47
- it('handles "y-m-d" ISO-friendly date pattern', () => {
48
- expect(resolveDatePattern('y-m-d', 'date')).toEqual({
49
- year: 'numeric',
50
- month: '2-digit',
51
- day: '2-digit',
52
- });
53
- });
54
- it('handles "y-m-d" datetime pattern (24h, 2-digit)', () => {
55
- const opts = resolveDatePattern('y-m-d', 'datetime');
56
- expect(opts.hour).toBe('2-digit');
57
- expect(opts.minute).toBe('2-digit');
58
- expect(opts.hour12).toBe(false);
59
- });
60
- it('handles dateStyle shortcuts (medium, short, long) without timeStyle for date', () => {
61
- expect(resolveDatePattern('medium', 'date')).toEqual({
62
- dateStyle: 'medium',
63
- timeStyle: undefined,
64
- });
65
- expect(resolveDatePattern('short', 'date')).toEqual({
66
- dateStyle: 'short',
67
- timeStyle: undefined,
68
- });
69
- expect(resolveDatePattern('long', 'date')).toEqual({
70
- dateStyle: 'long',
71
- timeStyle: undefined,
72
- });
73
- });
74
- it('attaches timeStyle: short when kind is datetime for dateStyle shortcuts', () => {
75
- expect(resolveDatePattern('medium', 'datetime')).toEqual({
76
- dateStyle: 'medium',
77
- timeStyle: 'short',
78
- });
79
- expect(resolveDatePattern('short', 'datetime')).toEqual({
80
- dateStyle: 'short',
81
- timeStyle: 'short',
82
- });
83
- expect(resolveDatePattern('long', 'datetime')).toEqual({
84
- dateStyle: 'long',
85
- timeStyle: 'short',
86
- });
87
- });
88
- it('returns undefined for unknown patterns', () => {
89
- expect(resolveDatePattern('weird', 'date')).toBeUndefined();
90
- expect(resolveDatePattern('yyyy-mm', 'datetime')).toBeUndefined();
91
- });
92
- it('trims whitespace around the pattern', () => {
93
- expect(resolveDatePattern(' d ', 'date')).toEqual({
94
- year: 'numeric',
95
- month: 'numeric',
96
- day: 'numeric',
97
- });
98
- });
99
- });
100
- describe('getDateFormatter', () => {
101
- it('returns the same instance for repeated calls with the same signature', () => {
102
- const a = getDateFormatter('en-US', { dateStyle: 'short' });
103
- const b = getDateFormatter('en-US', { dateStyle: 'short' });
104
- expect(a).toBe(b);
105
- });
106
- it('returns a different instance when the locale changes', () => {
107
- const en = getDateFormatter('en-US', { dateStyle: 'short' });
108
- const de = getDateFormatter('de-DE', { dateStyle: 'short' });
109
- expect(en).not.toBe(de);
110
- });
111
- it('returns a different instance when options change', () => {
112
- const a = getDateFormatter('en-US', { dateStyle: 'short' });
113
- const b = getDateFormatter('en-US', { dateStyle: 'long' });
114
- expect(a).not.toBe(b);
115
- });
116
- it('treats undefined locale and empty-array locale as distinct', () => {
117
- const undefLocale = getDateFormatter(undefined, { dateStyle: 'short' });
118
- const arrLocale = getDateFormatter([], { dateStyle: 'short' });
119
- // Both produce a working formatter; just smoke-check they work.
120
- expect(typeof undefLocale.format(new Date())).toBe('string');
121
- expect(typeof arrLocale.format(new Date())).toBe('string');
122
- });
123
- it('keys correctly off every option dimension (smoke: each option produces a distinct cache entry)', () => {
124
- // We exercise each conditional branch in the key construction so v8/c8
125
- // counts the line as covered.
126
- getDateFormatter('en-US', { year: 'numeric' });
127
- getDateFormatter('en-US', { month: 'long' });
128
- getDateFormatter('en-US', { day: '2-digit' });
129
- getDateFormatter('en-US', { hour: '2-digit' });
130
- getDateFormatter('en-US', { minute: '2-digit' });
131
- getDateFormatter('en-US', { second: '2-digit' });
132
- getDateFormatter('en-US', { weekday: 'short' });
133
- getDateFormatter('en-US', { hour12: true });
134
- getDateFormatter('en-US', { hour12: false });
135
- getDateFormatter(['en-US', 'en-GB'], { dateStyle: 'short' });
136
- });
137
- });
138
- describe('formatNumericWithConfig - number', () => {
139
- it('formats a basic integer with default locale', () => {
140
- expect(formatNumericWithConfig(1234, { type: 'number' })).toMatch(/1[\.,]?234/);
141
- });
142
- it('respects locale grouping and decimal separators', () => {
143
- const de = formatNumericWithConfig(1234.5, { type: 'number', locales: 'de-DE' });
144
- expect(de).toContain(',');
145
- const en = formatNumericWithConfig(1234.5, { type: 'number', locales: 'en-US' });
146
- expect(en).toContain('.');
147
- });
148
- it('forwards fraction-digit options', () => {
149
- expect(formatNumericWithConfig(1.2345, {
150
- type: 'number',
151
- options: { minimumFractionDigits: 3, maximumFractionDigits: 3 },
152
- locales: 'en-US',
153
- })).toBe('1.235');
154
- });
155
- it('returns the raw String(value) for non-numeric input', () => {
156
- expect(formatNumericWithConfig('abc', { type: 'number' })).toBe('abc');
157
- expect(formatNumericWithConfig(NaN, { type: 'number' })).toBe('NaN');
158
- expect(formatNumericWithConfig(Infinity, { type: 'number' })).toBe('Infinity');
159
- });
160
- it('treats null as 0 (Number(null) === 0) and undefined as String("")', () => {
161
- // Number(null) === 0, so null formats as "0".
162
- expect(formatNumericWithConfig(null, { type: 'number', locales: 'en-US' })).toBe('0');
163
- // Number(undefined) === NaN -> falls back to String(undefined ?? '') -> "".
164
- expect(formatNumericWithConfig(undefined, { type: 'number' })).toBe('');
165
- });
166
- it('accepts strings that parse to numbers', () => {
167
- const result = formatNumericWithConfig('42', { type: 'number', locales: 'en-US' });
168
- expect(result).toBe('42');
169
- });
170
- });
171
- describe('formatNumericWithConfig - currency', () => {
172
- it('defaults to USD when currency is omitted', () => {
173
- const usd = formatNumericWithConfig(99.5, { type: 'currency', locales: 'en-US' });
174
- expect(usd).toMatch(/\$99\.50/);
175
- });
176
- it('honors a custom currency', () => {
177
- const eur = formatNumericWithConfig(99.5, {
178
- type: 'currency',
179
- currency: 'EUR',
180
- locales: 'en-US',
181
- });
182
- expect(eur).toMatch(/€99\.50/);
183
- });
184
- it('merges caller options on top of currency defaults', () => {
185
- const result = formatNumericWithConfig(1234.5, {
186
- type: 'currency',
187
- currency: 'USD',
188
- locales: 'en-US',
189
- options: { maximumFractionDigits: 0, minimumFractionDigits: 0 },
190
- });
191
- expect(result).toBe('$1,235');
192
- });
193
- });
194
- describe('formatNumericWithConfig - percent', () => {
195
- it('treats input as a 0..1 fraction by default', () => {
196
- expect(formatNumericWithConfig(0.5, { type: 'percent', locales: 'en-US' })).toMatch(/50%/);
197
- });
198
- it('treats input as 0..100 points when valueIsPercentPoints is true', () => {
199
- expect(formatNumericWithConfig(50, {
200
- type: 'percent',
201
- locales: 'en-US',
202
- valueIsPercentPoints: true,
203
- })).toMatch(/50%/);
204
- });
205
- it('respects fraction-digits override', () => {
206
- const result = formatNumericWithConfig(0.123, {
207
- type: 'percent',
208
- locales: 'en-US',
209
- options: { minimumFractionDigits: 1, maximumFractionDigits: 1 },
210
- });
211
- expect(result).toBe('12.3%');
212
- });
213
- });
214
- describe('formatNumericWithConfig - cache reuse', () => {
215
- it('reuses the cached Intl.NumberFormat instance across calls', () => {
216
- // We can't access the cache directly, but we can verify that repeated
217
- // calls with the same config produce identical output - any cache miss
218
- // would still return the same string, so this is a smoke check.
219
- const a = formatNumericWithConfig(10, { type: 'number', locales: 'en-US' });
220
- const b = formatNumericWithConfig(10, { type: 'number', locales: 'en-US' });
221
- expect(a).toBe(b);
222
- });
223
- it('handles array-of-locales input', () => {
224
- const result = formatNumericWithConfig(1.5, {
225
- type: 'number',
226
- locales: ['en-US', 'en-GB'],
227
- });
228
- expect(result).toBe('1.5');
229
- });
230
- it('handles undefined locale (browser default)', () => {
231
- const result = formatNumericWithConfig(1.5, { type: 'number' });
232
- expect(typeof result).toBe('string');
233
- });
234
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,338 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import { createCellRender } from './cell-render';
3
- function makeRow(id, data) {
4
- return {
5
- id,
6
- original: data,
7
- getCellValueByColumnId: (colId) => data[colId],
8
- };
9
- }
10
- function makeColumn(id, def = {}) {
11
- return { id, columnDef: { ...def } };
12
- }
13
- function makeCtx(overrides = {}) {
14
- return {
15
- props: {},
16
- grid: { id: 'grid' },
17
- conditionalColumnStats: new Map(),
18
- noteOverrides: {},
19
- editorOptionsCache: {},
20
- ...overrides,
21
- };
22
- }
23
- describe('createCellRender / cellConditionalFormat', () => {
24
- it('returns null when no conditionalFormats are configured', () => {
25
- const cr = createCellRender(makeCtx());
26
- const out = cr.cellConditionalFormat(makeRow('r1', { amt: 5 }), makeColumn('amt'), 5);
27
- expect(out).toBeNull();
28
- });
29
- it('returns null when conditionalFormats is an empty array', () => {
30
- const cr = createCellRender(makeCtx({ props: { conditionalFormats: [] } }));
31
- expect(cr.cellConditionalFormat(makeRow('r', {}), makeColumn('amt'), 1)).toBeNull();
32
- });
33
- it('resolves a rule format against the value', () => {
34
- const formats = [
35
- {
36
- type: 'rule',
37
- columns: ['amt'],
38
- when: ({ value }) => Number(value) > 10,
39
- background: '#ff0000',
40
- fontWeight: 700,
41
- },
42
- ];
43
- const cr = createCellRender(makeCtx({ props: { conditionalFormats: formats } }));
44
- const hit = cr.cellConditionalFormat(makeRow('r', { amt: 20 }), makeColumn('amt'), 20);
45
- expect(hit?.background).toBe('#ff0000');
46
- expect(hit?.fontWeight).toBe(700);
47
- const miss = cr.cellConditionalFormat(makeRow('r', { amt: 1 }), makeColumn('amt'), 1);
48
- expect(miss).toEqual({});
49
- });
50
- it('passes the precomputed column stat through to colorScale formats', () => {
51
- const formats = [{ type: 'colorScale', min: '#fff', max: '#000' }];
52
- const stats = new Map([['amt', { min: 0, max: 100 }]]);
53
- const cr = createCellRender(makeCtx({ props: { conditionalFormats: formats }, conditionalColumnStats: stats }));
54
- const out = cr.cellConditionalFormat(makeRow('r', { amt: 50 }), makeColumn('amt'), 50);
55
- expect(out).not.toBeNull();
56
- expect(out?.background).toBeDefined();
57
- });
58
- });
59
- describe('createCellRender / computeRowClass', () => {
60
- it('returns empty string when no rowClass configured', () => {
61
- const cr = createCellRender(makeCtx());
62
- expect(cr.computeRowClass(makeRow('r', {}), 0)).toBe('');
63
- });
64
- it('resolves a string rowClass', () => {
65
- const rowClass = vi.fn(() => 'highlight');
66
- const cr = createCellRender(makeCtx({ props: { rowClass } }));
67
- const row = makeRow('r', { a: 1 });
68
- expect(cr.computeRowClass(row, 3)).toBe('highlight');
69
- expect(rowClass).toHaveBeenCalledWith({ row: row.original, rowIndex: 3 });
70
- });
71
- it('resolves an array / object rowClass', () => {
72
- const cr = createCellRender(makeCtx({ props: { rowClass: () => ['a', '', 'b'] } }));
73
- expect(cr.computeRowClass(makeRow('r', {}), 0)).toBe('a b');
74
- const cr2 = createCellRender(makeCtx({ props: { rowClass: () => ({ on: true, off: false }) } }));
75
- expect(cr2.computeRowClass(makeRow('r', {}), 0)).toBe('on');
76
- });
77
- });
78
- describe('createCellRender / computeCellClass', () => {
79
- it('returns empty string when cellClass is null/undefined', () => {
80
- const cr = createCellRender(makeCtx());
81
- expect(cr.computeCellClass(makeRow('r', {}), makeColumn('a'))).toBe('');
82
- });
83
- it('resolves a string cellClass', () => {
84
- const cr = createCellRender(makeCtx());
85
- expect(cr.computeCellClass(makeRow('r', {}), makeColumn('a', { cellClass: 'red' }))).toBe('red');
86
- });
87
- it('resolves an array cellClass', () => {
88
- const cr = createCellRender(makeCtx());
89
- expect(cr.computeCellClass(makeRow('r', {}), makeColumn('a', { cellClass: ['x', 'y'] }))).toBe('x y');
90
- });
91
- it('invokes a function cellClass with a minimal cell context', () => {
92
- const cellClass = vi.fn((c) => (c.getValue() > 5 ? 'big' : 'small'));
93
- const cr = createCellRender(makeCtx());
94
- const row = makeRow('r', { score: 9 });
95
- expect(cr.computeCellClass(row, makeColumn('score', { cellClass }))).toBe('big');
96
- expect(cellClass).toHaveBeenCalledTimes(1);
97
- const passed = cellClass.mock.calls[0][0];
98
- expect(passed.row).toBe(row);
99
- expect(passed.getValue()).toBe(9);
100
- });
101
- it('returns empty string for an unsupported cellClass type (number)', () => {
102
- const cr = createCellRender(makeCtx());
103
- expect(cr.computeCellClass(makeRow('r', {}), makeColumn('a', { cellClass: 123 }))).toBe('');
104
- });
105
- });
106
- describe('createCellRender / computeCellTooltip', () => {
107
- it('returns null when no tooltip configured', () => {
108
- const cr = createCellRender(makeCtx());
109
- expect(cr.computeCellTooltip(makeRow('r', {}), makeColumn('a'))).toBeNull();
110
- });
111
- it('returns a static string tooltip, but null for empty string', () => {
112
- const cr = createCellRender(makeCtx());
113
- expect(cr.computeCellTooltip(makeRow('r', {}), makeColumn('a', { tooltip: 'help' }))).toBe('help');
114
- expect(cr.computeCellTooltip(makeRow('r', {}), makeColumn('a', { tooltip: '' }))).toBeNull();
115
- });
116
- it('invokes a function tooltip and stringifies the result', () => {
117
- const tooltip = (c) => `val=${c.getValue()}`;
118
- const cr = createCellRender(makeCtx());
119
- const row = makeRow('r', { a: 42 });
120
- expect(cr.computeCellTooltip(row, makeColumn('a', { tooltip }))).toBe('val=42');
121
- });
122
- it('returns null when the function tooltip returns a falsy value', () => {
123
- const cr = createCellRender(makeCtx());
124
- expect(cr.computeCellTooltip(makeRow('r', {}), makeColumn('a', { tooltip: () => '' }))).toBeNull();
125
- });
126
- it('returns null for an unsupported tooltip type', () => {
127
- const cr = createCellRender(makeCtx());
128
- expect(cr.computeCellTooltip(makeRow('r', {}), makeColumn('a', { tooltip: 5 }))).toBeNull();
129
- });
130
- });
131
- describe('createCellRender / computeCellNote', () => {
132
- it('returns null when neither overrides nor notes have an entry', () => {
133
- const cr = createCellRender(makeCtx());
134
- expect(cr.computeCellNote(makeRow('r', {}), makeColumn('a'))).toBeNull();
135
- });
136
- it('prefers the internal note override over props.notes', () => {
137
- const cr = createCellRender(makeCtx({
138
- noteOverrides: { r: { a: 'overlay' } },
139
- props: { notes: { r: { a: 'stored' } } },
140
- }));
141
- expect(cr.computeCellNote(makeRow('r', {}), makeColumn('a'))).toBe('overlay');
142
- });
143
- it('treats a blank override as a removal (null)', () => {
144
- const cr = createCellRender(makeCtx({
145
- noteOverrides: { r: { a: ' ' } },
146
- props: { notes: { r: { a: 'stored' } } },
147
- }));
148
- expect(cr.computeCellNote(makeRow('r', {}), makeColumn('a'))).toBeNull();
149
- });
150
- it('falls back to props.notes when no override exists', () => {
151
- const cr = createCellRender(makeCtx({ props: { notes: { r: { a: 'stored note' } } } }));
152
- expect(cr.computeCellNote(makeRow('r', {}), makeColumn('a'))).toBe('stored note');
153
- });
154
- it('returns null for a whitespace-only stored note', () => {
155
- const cr = createCellRender(makeCtx({ props: { notes: { r: { a: ' ' } } } }));
156
- expect(cr.computeCellNote(makeRow('r', {}), makeColumn('a'))).toBeNull();
157
- });
158
- it('returns null when the row or column is absent in the notes map', () => {
159
- const cr = createCellRender(makeCtx({ props: { notes: { other: { a: 'x' } } } }));
160
- expect(cr.computeCellNote(makeRow('r', {}), makeColumn('a'))).toBeNull();
161
- const cr2 = createCellRender(makeCtx({ props: { notes: { r: { b: 'x' } } } }));
162
- expect(cr2.computeCellNote(makeRow('r', {}), makeColumn('a'))).toBeNull();
163
- });
164
- });
165
- describe('createCellRender / getColumnEditorOptions', () => {
166
- it('returns [] for a function editorOptions when row has no original', () => {
167
- const cr = createCellRender(makeCtx());
168
- const col = makeColumn('a', { editorOptions: () => [{ value: 1, label: 'one' }] });
169
- expect(cr.getColumnEditorOptions(col, null)).toEqual([]);
170
- });
171
- it('evaluates a function editorOptions per row', () => {
172
- const cr = createCellRender(makeCtx());
173
- const col = makeColumn('a', {
174
- editorOptions: (data) => [{ value: data.code, label: data.name }],
175
- });
176
- const row = makeRow('r', { code: 'x', name: 'X' });
177
- const opts = cr.getColumnEditorOptions(col, row);
178
- expect(opts).toEqual([{ value: 'x', label: 'X' }]);
179
- });
180
- it('caches normalized static editorOptions and reuses them', () => {
181
- const ctx = makeCtx();
182
- const cr = createCellRender(ctx);
183
- const src = ['a', 'b'];
184
- const col = makeColumn('a', { editorOptions: src });
185
- const first = cr.getColumnEditorOptions(col);
186
- const second = cr.getColumnEditorOptions(col);
187
- expect(first).toBe(second); // same cached array reference
188
- expect(ctx.editorOptionsCache['a']).toBe(first);
189
- });
190
- it('re-normalizes when the editorOptions source changes', () => {
191
- const ctx = makeCtx();
192
- const cr = createCellRender(ctx);
193
- const first = cr.getColumnEditorOptions(makeColumn('a', { editorOptions: ['a'] }));
194
- const second = cr.getColumnEditorOptions(makeColumn('a', { editorOptions: ['b'] }));
195
- expect(first).not.toBe(second);
196
- });
197
- });
198
- describe('createCellRender / formatListCellValue', () => {
199
- it('joins an array of values using the default separator', () => {
200
- const cr = createCellRender(makeCtx());
201
- const col = makeColumn('a', {
202
- editorOptions: [
203
- { value: 1, label: 'One' },
204
- { value: 2, label: 'Two' },
205
- ],
206
- });
207
- expect(cr.formatListCellValue(col, [1, 2])).toBe('One, Two');
208
- });
209
- it('honors a custom editorSeparator', () => {
210
- const cr = createCellRender(makeCtx());
211
- const col = makeColumn('a', {
212
- editorSeparator: ' | ',
213
- editorOptions: [
214
- { value: 1, label: 'One' },
215
- { value: 2, label: 'Two' },
216
- ],
217
- });
218
- expect(cr.formatListCellValue(col, [1, 2])).toBe('One | Two');
219
- });
220
- it('returns empty string for null / empty scalar values', () => {
221
- const cr = createCellRender(makeCtx());
222
- const col = makeColumn('a', { editorOptions: [] });
223
- expect(cr.formatListCellValue(col, null)).toBe('');
224
- expect(cr.formatListCellValue(col, '')).toBe('');
225
- });
226
- it('maps a single scalar value to its label', () => {
227
- const cr = createCellRender(makeCtx());
228
- const col = makeColumn('a', {
229
- editorOptions: [{ value: 'k', label: 'Known' }],
230
- });
231
- expect(cr.formatListCellValue(col, 'k')).toBe('Known');
232
- // unknown value falls back to its string form
233
- expect(cr.formatListCellValue(col, 'z')).toBe('z');
234
- });
235
- });
236
- describe('createCellRender / formatCellValue', () => {
237
- const row = makeRow('r', {});
238
- it('uses a custom formatter callback when present', () => {
239
- const cr = createCellRender(makeCtx());
240
- const col = makeColumn('a', {
241
- formatter: ({ value }) => `<<${value}>>`,
242
- });
243
- expect(cr.formatCellValue(col, 7, row)).toBe('<<7>>');
244
- });
245
- it('coerces a nullish formatter result to empty string', () => {
246
- const cr = createCellRender(makeCtx());
247
- const col = makeColumn('a', { formatter: () => null });
248
- expect(cr.formatCellValue(col, 1, row)).toBe('');
249
- });
250
- it('masks password columns with bullets, capped at 12', () => {
251
- const cr = createCellRender(makeCtx());
252
- const col = makeColumn('a', { editorType: 'password' });
253
- expect(cr.formatCellValue(col, 'abc', row)).toBe('•••');
254
- expect(cr.formatCellValue(col, '', row)).toBe('');
255
- expect(cr.formatCellValue(col, 'x'.repeat(50), row)).toBe('•'.repeat(12));
256
- });
257
- it('stringifies plain values when there is no format config', () => {
258
- const cr = createCellRender(makeCtx());
259
- const col = makeColumn('a');
260
- expect(cr.formatCellValue(col, 'hello', row)).toBe('hello');
261
- expect(cr.formatCellValue(col, null, row)).toBe('');
262
- });
263
- it('applies numeric / currency / percent format configs', () => {
264
- const cr = createCellRender(makeCtx());
265
- expect(cr.formatCellValue(makeColumn('a', { format: { type: 'number' } }), 1234.5, row)).toContain('1,234');
266
- const cur = cr.formatCellValue(makeColumn('a', { format: { type: 'currency', currency: 'USD' } }), 1000, row);
267
- expect(cur).toContain('$');
268
- const pct = cr.formatCellValue(makeColumn('a', { format: { type: 'percent' } }), 0.25, row);
269
- expect(pct).toContain('%');
270
- });
271
- it('formats date configs and falls back when the value is not a date', () => {
272
- const cr = createCellRender(makeCtx());
273
- const dateCol = makeColumn('a', { format: { type: 'date' } });
274
- const out = cr.formatCellValue(dateCol, '2024-01-15T00:00:00Z', row);
275
- expect(out).toMatch(/2024/);
276
- // Unparseable -> returns String(value)
277
- expect(cr.formatCellValue(dateCol, 'not-a-date', row)).toBe('not-a-date');
278
- });
279
- it('formats datetime configs with merged default options', () => {
280
- const cr = createCellRender(makeCtx());
281
- const col = makeColumn('a', { format: { type: 'datetime' } });
282
- const out = cr.formatCellValue(col, '2024-03-10T13:45:00Z', row);
283
- expect(out).toMatch(/2024/);
284
- });
285
- });
286
- describe('createCellRender / formatPinnedValue', () => {
287
- it('invokes a formatter with a null row', () => {
288
- const cr = createCellRender(makeCtx());
289
- const formatter = vi.fn(({ value, row }) => `${value}:${row}`);
290
- const col = makeColumn('a', { formatter });
291
- expect(cr.formatPinnedValue(col, 5)).toBe('5:null');
292
- expect(formatter.mock.calls[0][0].row).toBeNull();
293
- });
294
- it('masks password pinned values', () => {
295
- const cr = createCellRender(makeCtx());
296
- expect(cr.formatPinnedValue(makeColumn('a', { editorType: 'password' }), 'pw')).toBe('••');
297
- expect(cr.formatPinnedValue(makeColumn('a', { editorType: 'password' }), '')).toBe('');
298
- });
299
- it('stringifies when no format config', () => {
300
- const cr = createCellRender(makeCtx());
301
- expect(cr.formatPinnedValue(makeColumn('a'), 'total')).toBe('total');
302
- expect(cr.formatPinnedValue(makeColumn('a'), null)).toBe('');
303
- });
304
- it('applies numeric and date format configs', () => {
305
- const cr = createCellRender(makeCtx());
306
- expect(cr.formatPinnedValue(makeColumn('a', { format: { type: 'number' } }), 9999)).toContain('9,999');
307
- const d = cr.formatPinnedValue(makeColumn('a', { format: { type: 'date' } }), '2022-06-01T00:00:00Z');
308
- expect(d).toMatch(/2022/);
309
- // unparseable date falls through to String()
310
- expect(cr.formatPinnedValue(makeColumn('a', { format: { type: 'date' } }), 'xyz')).toBe('xyz');
311
- });
312
- it('formats datetime pinned configs', () => {
313
- const cr = createCellRender(makeCtx());
314
- const out = cr.formatPinnedValue(makeColumn('a', { format: { type: 'datetime' } }), '2021-12-31T08:00:00Z');
315
- expect(out).toMatch(/2021/);
316
- });
317
- });
318
- describe('createCellRender / computePinnedCellClass', () => {
319
- it('returns empty string when cellClass is not a function', () => {
320
- const cr = createCellRender(makeCtx());
321
- expect(cr.computePinnedCellClass({ a: 1 }, makeColumn('a', { cellClass: 'x' }))).toBe('');
322
- });
323
- it('invokes a function cellClass with a null row and the pinned value', () => {
324
- const cr = createCellRender(makeCtx());
325
- const cellClass = vi.fn((c) => (c.value > 0 ? 'pos' : 'neg'));
326
- const col = makeColumn('a', {
327
- cellClass,
328
- field: 'a',
329
- });
330
- expect(cr.computePinnedCellClass({ a: 3 }, col)).toBe('pos');
331
- expect(cellClass.mock.calls[0][0].row).toBeNull();
332
- });
333
- it('coerces a falsy cellClass result to empty string', () => {
334
- const cr = createCellRender(makeCtx());
335
- const col = makeColumn('a', { cellClass: () => undefined, field: 'a' });
336
- expect(cr.computePinnedCellClass({ a: 1 }, col)).toBe('');
337
- });
338
- });
@@ -1 +0,0 @@
1
- export {};