@cdmx/wappler_ag_grid 2.1.4 → 2.1.5

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.
@@ -0,0 +1,374 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>13 · Flags &amp; Styles · AG Grid Test Suite</title>
6
+ <link rel="stylesheet" href="./lib/test-suite.css">
7
+ <link rel="stylesheet" href="../node_modules/ag-grid-community/styles/ag-theme-alpine.css">
8
+ <link rel="stylesheet" href="../ag-theme-custom.css">
9
+ <link rel="stylesheet" href="../switch-toggle-slider.css">
10
+ <script src="./dmxAppConnect/dmxAppConnect.js" defer></script>
11
+ <script src="./lib/version-banner.js" defer></script>
12
+ <script src="./lib/test-helpers.js" defer></script>
13
+ <script src="./lib/page-template.js" defer></script>
14
+ <script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
15
+ <script src="../dmx-ag-grid.js" defer></script>
16
+ <script src="./data/sample-data.js" defer></script>
17
+
18
+ <script>
19
+ // Window-scoped function condition target for rstyles[*].condition = 'isRowVip()'.
20
+ // Must take row data and return boolean.
21
+ window.isRowVip = function (row) {
22
+ return !!row && row.rating >= 4.7 && row.active === true;
23
+ };
24
+ </script>
25
+ </head>
26
+ <body is="dmx-app" id="page13">
27
+
28
+ <script>
29
+ window.addEventListener('DOMContentLoaded', () => {
30
+ TestPage.header({
31
+ title: '13 · Flags & Styles (rstyles v2.1.4)',
32
+ summary: 'Suppression flags + the full rstyles condition matrix added in 2.1.4: function, value/operator, compound, field+shorthand, first-match-wins, legacy object form.'
33
+ });
34
+ });
35
+ </script>
36
+
37
+ <main class="test-main">
38
+ <dmx-test-data id="rows" source="employees"></dmx-test-data>
39
+
40
+ <section class="feature">
41
+ <h2>Suppression flags + ci_sort + hide_filters/sort</h2>
42
+ <p class="desc">
43
+ Most behavior-suppression flags toggled on at once. <code>hide_filters="email,rating"</code>
44
+ and <code>hide_sort="department"</code> selectively disable filtering / sorting per column.
45
+ </p>
46
+ <div class="grid-wrap">
47
+ <dmx-ag-grid
48
+ id="flagsGrid"
49
+ dmx-bind:data="rows.value"
50
+ grid_theme="ag-theme-alpine"
51
+ dmx-bind:ci_sort="true"
52
+ dmx-bind:suppress_row_deselection="true"
53
+ dmx-bind:suppress_row_click_selection="true"
54
+ dmx-bind:suppress_menu_hide="true"
55
+ dmx-bind:suppress_movable_columns="true"
56
+ dmx-bind:suppress_clipboard_paste="true"
57
+ dmx-bind:suppress_scroll_on_new_data="true"
58
+ dmx-bind:always_show_vertical_scroll="true"
59
+ hide_filters="email,rating"
60
+ hide_sort="department"
61
+ dmx-bind:tooltip_show_delay="500"
62
+ dmx-bind:pagination="false">
63
+ </dmx-ag-grid>
64
+ </div>
65
+ </section>
66
+
67
+ <!--
68
+ rstyles entry shape (2.1.4):
69
+ { field?: string, condition: string, customColor: css-color }
70
+ condition forms (first matching entry wins, color is applied as row background):
71
+ 1. Function: condition = 'myFn()' — window.myFn(rowData) returns boolean
72
+ 2. Value/operator: condition = 'salary > 90000', '==', '!=', '>=', '<=', '<'
73
+ 3. Compound: condition = 'active == true && department == Engineering'
74
+ 4. Field+shorthand: field = 'department', condition = 'Engineering' (exact match)
75
+ Bare values (no operator, no field) are skipped. customColor is applied as
76
+ `background` on the row.
77
+ -->
78
+
79
+ <section class="feature">
80
+ <h2>rstyles · function condition</h2>
81
+ <p class="desc">
82
+ Single entry: <code>{ condition: 'isRowVip()', customColor: '#fef3c7' }</code>.
83
+ <code>window.isRowVip(row)</code> returns true when rating ≥ 4.7 and active.
84
+ Expect ~4 highlighted rows (David, Kate, Olivia, Sam, Xavier).
85
+ </p>
86
+ <div class="grid-wrap">
87
+ <dmx-ag-grid
88
+ id="rsFn"
89
+ dmx-bind:data="rows.value"
90
+ grid_theme="ag-theme-alpine"
91
+ dmx-bind:rstyles='[{"condition":"isRowVip()","customColor":"#fef3c7"}]'
92
+ dmx-bind:pagination="false">
93
+ </dmx-ag-grid>
94
+ </div>
95
+ </section>
96
+
97
+ <section class="feature">
98
+ <h2>rstyles · value/operator condition</h2>
99
+ <p class="desc">
100
+ <code>condition: 'salary &gt; 90000'</code> → light green for high earners,
101
+ <code>condition: 'salary &lt;= 65000'</code> → light red for low (exercises the 2.1.4
102
+ <code>&lt;=</code>/<code>&gt;=</code> parser fix).
103
+ </p>
104
+ <div class="grid-wrap">
105
+ <dmx-ag-grid
106
+ id="rsOp"
107
+ dmx-bind:data="rows.value"
108
+ grid_theme="ag-theme-alpine"
109
+ dmx-bind:rstyles='[{"condition":"salary > 90000","customColor":"#d1fae5"},{"condition":"salary <= 65000","customColor":"#fee2e2"}]'
110
+ dmx-bind:pagination="false">
111
+ </dmx-ag-grid>
112
+ </div>
113
+ </section>
114
+
115
+ <section class="feature">
116
+ <h2>rstyles · compound &amp;&amp; / ||</h2>
117
+ <p class="desc">
118
+ <code>active == true &amp;&amp; department == Engineering</code> →
119
+ light blue.
120
+ </p>
121
+ <div class="grid-wrap">
122
+ <dmx-ag-grid
123
+ id="rsCompound"
124
+ dmx-bind:data="rows.value"
125
+ grid_theme="ag-theme-alpine"
126
+ dmx-bind:rstyles='[{"condition":"active == true && department == Engineering","customColor":"#dbeafe"}]'
127
+ dmx-bind:pagination="false">
128
+ </dmx-ag-grid>
129
+ </div>
130
+ </section>
131
+
132
+ <section class="feature">
133
+ <h2>rstyles · Field + plain-value shorthand</h2>
134
+ <p class="desc">
135
+ <code>{ field: 'department', condition: 'Marketing', customColor: '#fce7f3' }</code> —
136
+ exact string match on <code>row.department</code>.
137
+ </p>
138
+ <div class="grid-wrap">
139
+ <dmx-ag-grid
140
+ id="rsShort"
141
+ dmx-bind:data="rows.value"
142
+ grid_theme="ag-theme-alpine"
143
+ dmx-bind:rstyles='[{"field":"department","condition":"Marketing","customColor":"#fce7f3"}]'
144
+ dmx-bind:pagination="false">
145
+ </dmx-ag-grid>
146
+ </div>
147
+ </section>
148
+
149
+ <section class="feature">
150
+ <h2>rstyles · first-match wins</h2>
151
+ <p class="desc">
152
+ Two overlapping conditions on the same field. The first entry
153
+ (<code>salary &gt; 95000 → orange</code>) should win for David (110k) and
154
+ Kate (105k); the second entry (<code>salary &gt; 90000 → yellow</code>) catches
155
+ the next band only.
156
+ </p>
157
+ <div class="grid-wrap">
158
+ <dmx-ag-grid
159
+ id="rsFirst"
160
+ dmx-bind:data="rows.value"
161
+ grid_theme="ag-theme-alpine"
162
+ dmx-bind:rstyles='[{"condition":"salary > 95000","customColor":"#fed7aa"},{"condition":"salary > 90000","customColor":"#fef9c3"}]'
163
+ dmx-bind:pagination="false">
164
+ </dmx-ag-grid>
165
+ </div>
166
+ </section>
167
+
168
+ <section class="feature">
169
+ <h2>rstyles · legacy object form (backwards compat)</h2>
170
+ <p class="desc">
171
+ Pre-2.1.4 callers passed an object keyed by field name. The runtime still accepts both
172
+ shapes (<code>Object.values()</code> normalises to the array path), but
173
+ <code>rstyles</code>'s schema is <code>type: Array</code>, so AppConnect coerces an
174
+ object-shaped <code>dmx-bind:</code> attribute to <code>[]</code> before the runtime sees
175
+ it. The legacy code path is only reachable programmatically — this scenario sets
176
+ <code>rstyles</code> via <code>dmx.app.get('rsLegacy').set(...)</code> and reloads.
177
+ </p>
178
+ <div class="grid-wrap">
179
+ <dmx-ag-grid
180
+ id="rsLegacy"
181
+ dmx-bind:data="rows.value"
182
+ grid_theme="ag-theme-alpine"
183
+ dmx-bind:pagination="false">
184
+ </dmx-ag-grid>
185
+ </div>
186
+ </section>
187
+
188
+ <section class="feature">
189
+ <h2>cstyles · text + cell area, with &gt;= / &lt;= operators</h2>
190
+ <p class="desc">
191
+ Two cstyles entries for <code>salary</code>:
192
+ <code>area:'text'</code> recolors the text (green if ≥ 90k),
193
+ <code>area:'cell'</code> sets the cell background (red if &lt;= 65k).
194
+ Exercises the same 2.1.4 operator parsing fix.
195
+ </p>
196
+ <div class="grid-wrap">
197
+ <dmx-ag-grid
198
+ id="csCell"
199
+ dmx-bind:data="rows.value"
200
+ grid_theme="ag-theme-alpine"
201
+ dmx-bind:cstyles='[{"field":"salary","condition":"salary >= 90000","customColor":"#047857","font":"bold","area":"text"},{"field":"salary","condition":"salary <= 65000","customColor":"#fee2e2","area":"cell"}]'
202
+ dmx-bind:pagination="false">
203
+ </dmx-ag-grid>
204
+ </div>
205
+ </section>
206
+
207
+ <section class="feature">
208
+ <h2>data_changes &amp; display_data_changes</h2>
209
+ <p class="desc">
210
+ <code>data_changes</code> rewrites field values before display.
211
+ <code>display_data_changes</code> overlays HTML badges on values.
212
+ </p>
213
+ <div class="grid-wrap">
214
+ <dmx-ag-grid
215
+ id="transformGrid"
216
+ dmx-bind:data="rows.value"
217
+ grid_theme="ag-theme-alpine"
218
+ dmx-bind:data_changes="[{field:'department', changes:[{from:'Engineering', to:'Eng.'},{from:'Marketing', to:'Mktg.'}]}]"
219
+ dmx-bind:display_data_changes="[{field:'active', changes:[{from:true, to:'<span style=color:green>ACTIVE</span>'},{from:false, to:'<span style=color:red>INACTIVE</span>'}]}]"
220
+ dmx-bind:pagination="false">
221
+ </dmx-ag-grid>
222
+ </div>
223
+ </section>
224
+
225
+ <section class="feature">
226
+ <h2>Custom tooltip</h2>
227
+ <p class="desc"><code>custom_tooltip</code> sets an HTML tooltip template; <code>tooltip_show_delay</code> shortens the hover delay.</p>
228
+ <div class="grid-wrap">
229
+ <dmx-ag-grid
230
+ id="tipGrid"
231
+ dmx-bind:data="rows.value"
232
+ grid_theme="ag-theme-alpine"
233
+ custom_tooltip="<b>{{field}}</b>: {{value}}"
234
+ dmx-bind:tooltip_config="[{field:'email'},{field:'salary'}]"
235
+ dmx-bind:tooltip_show_delay="300"
236
+ dmx-bind:pagination="false">
237
+ </dmx-ag-grid>
238
+ </div>
239
+ </section>
240
+
241
+ <section class="feature">
242
+ <h2>Live rstyles assertions</h2>
243
+ <p class="desc">
244
+ Runs in the page after the grids render and counts coloured rows per scenario.
245
+ The puppeteer runner reads <code>window.__rsCheck</code> and fails the page if any
246
+ condition didn't apply at least one matching row.
247
+ </p>
248
+ <pre class="log" id="rs-check"></pre>
249
+ </section>
250
+ </main>
251
+
252
+ <script>
253
+ // Helpers: count rows in a grid's DOM with a given background colour. Multiple matches
254
+ // are accepted (puppeteer normalises CSS to rgb()).
255
+ function bgCount(gridId, predicate) {
256
+ const root = document.querySelector('#' + gridId + '-grid');
257
+ if (!root) return -1;
258
+ let n = 0;
259
+ root.querySelectorAll('.ag-row').forEach(row => {
260
+ const bg = (row.getAttribute('style') || '').toLowerCase();
261
+ if (predicate(bg)) n++;
262
+ });
263
+ return n;
264
+ }
265
+ // Match a hex color by translating to the rgb() AG Grid leaves on style="".
266
+ function hexToRgb(hex) {
267
+ hex = hex.replace('#', '');
268
+ if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
269
+ const num = parseInt(hex, 16);
270
+ return 'rgb(' + ((num >> 16) & 255) + ', ' + ((num >> 8) & 255) + ', ' + (num & 255) + ')';
271
+ }
272
+ function hasBg(color) {
273
+ const rgb = hexToRgb(color);
274
+ return bg => bg.includes(rgb) || bg.includes(color.toLowerCase());
275
+ }
276
+
277
+ // Inject the legacy object-form rstyles directly onto the component, then trigger a
278
+ // re-render — this is the only way to exercise the backwards-compat branch in 2.1.4
279
+ // because `dmx-bind:rstyles="{…}"` is coerced to [] by AppConnect's Array type schema.
280
+ function primeLegacy() {
281
+ if (!window.dmx || !dmx.app || !dmx.app.get) return setTimeout(primeLegacy, 100);
282
+ const c = dmx.app.get('rsLegacy');
283
+ if (!c) return setTimeout(primeLegacy, 100);
284
+ if (!c.__primed) {
285
+ const legacy = {
286
+ sales: { field: 'department', condition: 'Sales', customColor: '#e9d5ff' },
287
+ hr: { field: 'department', condition: 'HR', customColor: '#fef9c3' }
288
+ };
289
+ // Reach the underlying component instance so we can mutate props.rstyles and call
290
+ // refreshGrid — `c` is the reactive proxy, not the component.
291
+ const inst = document.getElementById('rsLegacy');
292
+ const comp = inst && inst.dmxComponent;
293
+ if (comp) {
294
+ comp.props.rstyles = legacy;
295
+ if (typeof comp.refreshGrid === 'function') {
296
+ const gi = comp.refreshGrid();
297
+ comp.set('gridInstance', gi);
298
+ }
299
+ c.__primed = true;
300
+ }
301
+ }
302
+ }
303
+ primeLegacy();
304
+
305
+ // Wait for ready then evaluate every rstyles scenario and store outcomes for the runner.
306
+ function runChecks() {
307
+ if (document.body.dataset.gridsReady !== 'true') return setTimeout(runChecks, 200);
308
+ // Read fixtures here — they're populated by the deferred sample-data.js, so capturing
309
+ // them at module scope would freeze them at [] before that script ran.
310
+ const employees = (window.SampleData && window.SampleData.employees) || [];
311
+ if (!employees.length) return setTimeout(runChecks, 200);
312
+ // Wait an extra cycle so the legacy-form re-render lands before we count rows.
313
+ if (!document.querySelector('#rsLegacy-grid .ag-row [col-id]')) return setTimeout(runChecks, 200);
314
+
315
+ // Expected hand-counts derived from sample-data.js
316
+ const vipRows = employees.filter(r => r.rating >= 4.7 && r.active === true).length;
317
+ const highSalary = employees.filter(r => r.salary > 90000).length;
318
+ const lowSalary = employees.filter(r => r.salary <= 65000).length;
319
+ const activeEng = employees.filter(r => r.active === true && r.department === 'Engineering').length;
320
+ const marketingRows = employees.filter(r => r.department === 'Marketing').length;
321
+ const over95 = employees.filter(r => r.salary > 95000).length;
322
+ const between90_95 = employees.filter(r => r.salary > 90000 && r.salary <= 95000).length;
323
+ const salesRows = employees.filter(r => r.department === 'Sales').length;
324
+ const hrRows = employees.filter(r => r.department === 'HR').length;
325
+
326
+ const checks = {
327
+ rsFn: { expect: vipRows, actual: bgCount('rsFn', hasBg('#fef3c7')) },
328
+ rsOp_high: { expect: highSalary, actual: bgCount('rsOp', hasBg('#d1fae5')) },
329
+ rsOp_low: { expect: lowSalary, actual: bgCount('rsOp', hasBg('#fee2e2')) },
330
+ rsCompound: { expect: activeEng, actual: bgCount('rsCompound', hasBg('#dbeafe')) },
331
+ rsShort: { expect: marketingRows, actual: bgCount('rsShort', hasBg('#fce7f3')) },
332
+ rsFirst_top: { expect: over95, actual: bgCount('rsFirst', hasBg('#fed7aa')) },
333
+ rsFirst_mid: { expect: between90_95, actual: bgCount('rsFirst', hasBg('#fef9c3')) },
334
+ rsLegacy_sl: { expect: salesRows, actual: bgCount('rsLegacy', hasBg('#e9d5ff')) },
335
+ rsLegacy_hr: { expect: hrRows, actual: bgCount('rsLegacy', hasBg('#fef9c3')) }
336
+ };
337
+ // cstyles: text-area uses inline color: rgb(...) on the cell, cell-area uses background-color
338
+ const csTextHigh = (() => {
339
+ const root = document.querySelector('#csCell-grid');
340
+ if (!root) return -1;
341
+ let n = 0;
342
+ const targetRgb = hexToRgb('#047857');
343
+ root.querySelectorAll('[col-id="salary"]').forEach(cell => {
344
+ const s = (cell.getAttribute('style') || '').toLowerCase();
345
+ if (s.includes(targetRgb)) n++;
346
+ });
347
+ return n;
348
+ })();
349
+ const csCellLow = (() => {
350
+ const root = document.querySelector('#csCell-grid');
351
+ if (!root) return -1;
352
+ let n = 0;
353
+ const targetRgb = hexToRgb('#fee2e2');
354
+ root.querySelectorAll('[col-id="salary"]').forEach(cell => {
355
+ const s = (cell.getAttribute('style') || '').toLowerCase();
356
+ if (s.includes(targetRgb)) n++;
357
+ });
358
+ return n;
359
+ })();
360
+ checks.cs_textHigh = { expect: highSalary, actual: csTextHigh };
361
+ checks.cs_cellLow = { expect: lowSalary, actual: csCellLow };
362
+
363
+ const failed = Object.entries(checks).filter(([_, v]) => v.actual !== v.expect);
364
+ window.__rsCheck = { checks, ok: failed.length === 0, failed: failed.map(([k]) => k) };
365
+
366
+ const box = document.getElementById('rs-check');
367
+ box.textContent = Object.entries(checks)
368
+ .map(([k, v]) => (v.actual === v.expect ? '✓' : '✗') + ' ' + k + ' — expected ' + v.expect + ', got ' + v.actual)
369
+ .join('\n');
370
+ }
371
+ runChecks();
372
+ </script>
373
+ </body>
374
+ </html>
@@ -0,0 +1,112 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>14 · Compact View &amp; Extended Buttons</title>
6
+ <link rel="stylesheet" href="./lib/test-suite.css">
7
+ <link rel="stylesheet" href="../node_modules/ag-grid-community/styles/ag-theme-alpine.css">
8
+ <link rel="stylesheet" href="../ag-theme-custom.css">
9
+ <link rel="stylesheet" href="../switch-toggle-slider.css">
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <script src="./dmxAppConnect/dmxAppConnect.js" defer></script>
12
+ <script src="./lib/version-banner.js" defer></script>
13
+ <script src="./lib/test-helpers.js" defer></script>
14
+ <script src="./lib/page-template.js" defer></script>
15
+ <script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
16
+ <script src="../dmx-ag-grid.js" defer></script>
17
+ <script src="./data/sample-data.js" defer></script>
18
+ </head>
19
+ <body is="dmx-app" id="page14">
20
+
21
+ <script>
22
+ window.addEventListener('DOMContentLoaded', () => {
23
+ TestPage.header({
24
+ title: '14 · Compact View & Extended Buttons',
25
+ summary: 'compact_view layout; buttons 6–15 wired up to verify the extended button slots and class toggles.'
26
+ });
27
+ });
28
+ </script>
29
+
30
+ <main class="test-main">
31
+ <dmx-test-data id="rows" source="employees"></dmx-test-data>
32
+ <dmx-event-log id="evLog"></dmx-event-log>
33
+
34
+ <section class="feature">
35
+ <h2>Compact view mode</h2>
36
+ <p class="desc"><code>compact_view=true</code> with a 3-column grid, row height 20px.</p>
37
+ <div class="grid-wrap">
38
+ <dmx-ag-grid
39
+ id="compactGrid"
40
+ dmx-bind:data="rows.value"
41
+ grid_theme="ag-theme-alpine"
42
+ dmx-bind:compact_view="true"
43
+ dmx-bind:compact_view_grid_size="3"
44
+ dmx-bind:compact_view_item_height="20"
45
+ dmx-bind:pagination="false">
46
+ </dmx-ag-grid>
47
+ </div>
48
+ </section>
49
+
50
+ <section class="feature">
51
+ <h2>Buttons 6 – 15 + class toggles</h2>
52
+ <p class="desc">
53
+ Exercises the upper range of custom action buttons + the
54
+ <code>action_button_class_toggles</code> / <code>action_button_icon_class_toggles</code> arrays.
55
+ </p>
56
+ <div class="grid-wrap">
57
+ <dmx-ag-grid
58
+ id="extButtons"
59
+ dmx-bind:data="rows.value"
60
+ grid_theme="ag-theme-alpine"
61
+ dmx-bind:enable_actions="true"
62
+ actions_column_position="right"
63
+ dmx-bind:pin_actions="true"
64
+ dmx-bind:enable_custom_action_btns="true"
65
+ dmx-bind:button6_action_btn="true" button6_action_tooltip="Link" button6_action_icon_class="fas fa-link"
66
+ dmx-bind:button7_action_btn="true" button7_action_tooltip="DL" button7_action_icon_class="fas fa-download"
67
+ dmx-bind:button8_action_btn="true" button8_action_tooltip="PDF" button8_action_icon_class="fas fa-file-pdf"
68
+ dmx-bind:button9_action_btn="true" button9_action_tooltip="Star" button9_action_icon_class="fas fa-star"
69
+ dmx-bind:button10_action_btn="true" button10_action_tooltip="Trash" button10_action_icon_class="fas fa-trash-alt"
70
+ dmx-bind:button11_action_btn="true" button11_action_tooltip="Cog" button11_action_icon_class="fas fa-cog"
71
+ dmx-bind:button12_action_btn="true" button12_action_tooltip="Search" button12_action_icon_class="fas fa-search"
72
+ dmx-bind:button13_action_btn="true" button13_action_tooltip="Like" button13_action_icon_class="fas fa-thumbs-up"
73
+ dmx-bind:button14_action_btn="true" button14_action_tooltip="Bell" button14_action_icon_class="fas fa-bell"
74
+ dmx-bind:button15_action_btn="true" button15_action_tooltip="Ban" button15_action_icon_class="fas fa-ban"
75
+ dmx-bind:action_button_class_toggles="[{btn_id:'button6', condition:'active', class:'btn-success'}]"
76
+ dmx-bind:action_button_icon_class_toggles="[{btn_id:'button9', condition:'rating >= 4.5', class:'fa-spin'}]"
77
+ dmx-bind:pagination="false"
78
+ dmx-on:row_action_button6="evLog.add('row_action_button6')"
79
+ dmx-on:row_action_button10="evLog.add('row_action_button10')"
80
+ dmx-on:row_action_button15="evLog.add('row_action_button15')">
81
+ </dmx-ag-grid>
82
+ </div>
83
+ <div class="kv">
84
+ <div class="k">last action</div><div class="v" dmx-text="evLog.last.name">—</div>
85
+ <div class="k">total actions</div><div class="v" dmx-text="evLog.count">0</div>
86
+ </div>
87
+ </section>
88
+
89
+ <section class="feature">
90
+ <h2>Cell + row events explicitly enabled</h2>
91
+ <p class="desc">
92
+ <code>cell_click_event</code> and <code>row_double_click_event</code> with actions enabled — cell clicks
93
+ report as cell events instead of row events.
94
+ </p>
95
+ <div class="grid-wrap">
96
+ <dmx-ag-grid
97
+ id="cellEvtGrid"
98
+ dmx-bind:data="rows.value"
99
+ grid_theme="ag-theme-alpine"
100
+ dmx-bind:enable_actions="true"
101
+ dmx-bind:edit_action_btn="true"
102
+ dmx-bind:cell_click_event="true"
103
+ dmx-bind:row_double_click_event="true"
104
+ dmx-bind:pagination="false"
105
+ dmx-on:cell_clicked="evLog.add('cell_clicked')"
106
+ dmx-on:row_double_clicked="evLog.add('row_double_clicked')">
107
+ </dmx-ag-grid>
108
+ </div>
109
+ </section>
110
+ </main>
111
+ </body>
112
+ </html>
@@ -0,0 +1,146 @@
1
+ # AG Grid · Wappler Module · Browser Test Suite
2
+
3
+ End-to-end UI tests for `<dmx-ag-grid>` driven by the
4
+ [agent-browser](https://www.npmjs.com/package/agent-browser) CLI. Works on Windows
5
+ and Linux/macOS.
6
+
7
+ ## Quick run
8
+
9
+ From the repo root:
10
+
11
+ ```bash
12
+ # 1. start the static server (project root must be served; tests/* uses relative
13
+ # paths to ../node_modules and ../dmx-ag-grid.js)
14
+ node tests/serve.cjs # http://localhost:8765/tests/index.html
15
+
16
+ # 2. drive every scenario, capture findings.json
17
+ # recommended — headless Chrome via puppeteer-core (faster, more reliable):
18
+ node tests/run-all-puppeteer.cjs
19
+ # alternative — drives the agent-browser CLI (Windows + Linux):
20
+ node tests/run-all.cjs
21
+ ```
22
+
23
+ Both produce the same `tests/findings.json` and per-page status table.
24
+ See `FINDINGS.md` for the last run's results and the bugs that were patched
25
+ along the way.
26
+
27
+ A human can also open `http://localhost:8765/tests/index.html` to click through
28
+ scenarios manually.
29
+
30
+ ## Layout
31
+
32
+ ```
33
+ tests/
34
+ ├── README.md ← this file
35
+ ├── VERSIONS.json ← pinned dependency versions
36
+ ├── FINDINGS.md ← last run's results + bugs/fixes
37
+ ├── findings.json ← machine-readable last run output
38
+ ├── index.html ← scenario directory
39
+ ├── 01-basic-render.html ← default columns, sort, filter, pagination
40
+ ├── 02-themes-locale.html ← all 5 themes + dark + RTL + locale
41
+ ├── 03-editing.html ← cell + row + static-select editors
42
+ ├── 04-selection.html ← single/multi selection + checkbox + status
43
+ ├── 05-action-buttons.html ← edit/view/delete + button1..5 + conditions
44
+ ├── 06-export.html ← CSV / XLSX / PDF
45
+ ├── 07-import.html ← importFileData (CSV + XLSX)
46
+ ├── 08-formatting.html ← amount, date, ctypes, cnames, cwidths
47
+ ├── 09-grouping.html ← group_config + columns_to_sum/count
48
+ ├── 10-state-persistence.html ← saveColumnState + applyFilters
49
+ ├── 11-layout.html ← autoHeight, fixed_header, scroll
50
+ ├── 12-methods.html ← every imperative method as a button
51
+ ├── 13-flags-styles.html ← suppress_*, rstyles, cstyles, data_changes
52
+ ├── 14-compact-view.html ← compact_view + buttons 6–15
53
+ ├── serve.cjs ← cross-platform static server
54
+ ├── run-all.cjs ← agent-browser CLI driver (cross-platform)
55
+ ├── run-all-puppeteer.cjs ← headless Chrome driver (recommended)
56
+ ├── data/
57
+ │ └── sample-data.js ← employees + orders fixtures
58
+ ├── dmxAppConnect/
59
+ │ ├── dmxAppConnect.js ← AppConnect runtime (pinned: 2.2.4)
60
+ │ └── dmxAppConnect.js.map
61
+ └── lib/
62
+ ├── test-suite.css ← page chrome styling
63
+ ├── version-banner.js ← detects + warns on AppConnect drift
64
+ ├── test-helpers.js ← <dmx-test-data>, <dmx-event-log>, TestKit
65
+ ├── page-template.js ← header + body.dataset.gridsReady
66
+ ├── run-collector.js ← injected via --init-script for headless runs
67
+ └── common-head.html ← reference snippet of the vendor load order
68
+ ```
69
+
70
+ ## Readiness signal
71
+
72
+ `page-template.js` sets `document.body.dataset.gridsReady = 'true'` once every
73
+ `<dmx-ag-grid>` on the page has fired `onGridReady` or `onFirstDataRendered`.
74
+ Agents (or the `run-all.cjs` driver) wait on that flag instead of guessing
75
+ timings.
76
+
77
+ ## Console helpers
78
+
79
+ Loaded by every scenario:
80
+
81
+ ```js
82
+ TestKit.list() // ['basicGrid', ...]
83
+ TestKit.grid('basicGrid') // reactive data proxy
84
+ TestKit.snapshot('basicGrid') // { count, state, selectedRows, … }
85
+ TestKit.callMethod('basicGrid', 'reloadGrid', true) // -> grid.__reloadGrid(true)
86
+ TestKit.waitForGrid('basicGrid') // promise resolving on gridReady
87
+ ```
88
+
89
+ Note: `dmx.app.get(id)` in AppConnect 2.2.4 returns a reactive data **proxy**
90
+ (direct field access, methods exposed as `__method`), not the component instance.
91
+ `TestKit.callMethod` translates the friendly name automatically.
92
+
93
+ ## What's covered
94
+
95
+ | # | Feature group | Notable attrs / methods |
96
+ |----|------------------------|-----------------------------------------------------------------------------------------------|
97
+ | 01 | Basic render | inferred columnDefs, sort, filter, floating_filter, pagination, count binding, quick filter |
98
+ | 02 | Themes & locale | grid_theme × 5, dark_mode, enable_rtl, locale_text (EN/HE/RU/ES/PT) |
99
+ | 03 | Editing | cell_editable, row_editable, editable_fields, cstatic_select_editors |
100
+ | 04 | Selection | row_selection (single/multi), row_checkbox_event, row_status_event, row_clicked |
101
+ | 05 | Action buttons | enable_actions, edit/view/delete + button1..5 + btn_condition |
102
+ | 06 | Export | export_to_csv/xls/pdf + trim + exclude_hidden + remove_html + custom filename |
103
+ | 07 | Import | importFileData(fieldId) for .csv and .xlsx |
104
+ | 08 | Formatting | amount_fields + precision, date_format, ctypes, cnames, cwidths, wrap_header_text |
105
+ | 09 | Grouping & aggregation | group_config, columns_to_sum, columns_to_count, footer_sum_precision |
106
+ | 10 | State persistence | column_state_storage_key, save/reset, applyFilters/getAppliedFilters |
107
+ | 11 | Layout | dom_layout, fixed_header, fixed_footer, fixed_horizontal_scroll, hide_id_field |
108
+ | 12 | Methods | loadGrid, reloadGrid, destroyGrid, pinColumns, hideColumns, getSelectedRows, getFilteredData |
109
+ | 13 | Flags & styles | suppress_*, hide_filters, hide_sort, rstyles, cstyles, data_changes, custom_tooltip, ci_sort |
110
+ | 14 | Compact + ext buttons | compact_view, action_button_class_toggles, buttons 6–15, cell_click_event |
111
+
112
+ ## What's intentionally NOT covered
113
+
114
+ - Server-side data sources (`serverConnect`) — substituted by `<dmx-test-data>`
115
+ for static fixtures
116
+ - The 30 Wappler IDE input panels in `app_connect/components.hjson`
117
+ - `dmxS3Upload`, `dmxFormatter`, etc. — those modules were removed from
118
+ `dmxAppConnect/`
119
+
120
+ ## Version manifest
121
+
122
+ `VERSIONS.json` records the AppConnect build (currently **2.2.4**, built
123
+ 2026-01-19) plus every vendor library. `lib/version-banner.js` logs a warning
124
+ to the console if a different AppConnect drops in.
125
+
126
+ Upgrade flow:
127
+
128
+ 1. Replace `tests/dmxAppConnect/dmxAppConnect.js`
129
+ 2. Run `node tests/run-all.cjs`
130
+ 3. If `tests/findings.json` still shows all PASS, bump `appConnect.expected` in
131
+ `VERSIONS.json`. Otherwise file the regressions in `FINDINGS.md`.
132
+
133
+ ## Cross-platform notes
134
+
135
+ - `run-all.cjs` invokes `agent-browser` via `shell:true`. On Windows that picks
136
+ up `agent-browser.cmd`; on Linux/macOS it finds `agent-browser` from `$PATH`.
137
+ Override the binary via `AGENT_BROWSER_BIN=…`
138
+ - `serve.cjs` is plain Node http — no Windows-only paths
139
+ - Test pages use `/` paths everywhere
140
+
141
+ ## Known limitations of the run-all driver
142
+
143
+ - agent-browser maintains a single Chrome session; navigating between 14 pages
144
+ can occasionally trip a CDP timeout. The driver retries each scenario once
145
+ after a daemon close. If a scenario still fails, run it in isolation by
146
+ opening the URL directly.