@cdmx/wappler_ag_grid 2.1.3 → 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,107 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>AG Grid Wappler · Test Suite</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <link rel="stylesheet" href="./lib/test-suite.css">
8
+ <script src="./dmxAppConnect/dmxAppConnect.js" defer></script>
9
+ <script src="./lib/version-banner.js" defer></script>
10
+ <script src="./lib/page-template.js" defer></script>
11
+ </head>
12
+ <body is="dmx-app" id="suite-index">
13
+ <script>
14
+ // Render header after defer-scripts load.
15
+ window.addEventListener('DOMContentLoaded', () => {
16
+ TestPage.header({
17
+ title: 'AG Grid Wappler — Test Suite',
18
+ summary: 'End-to-end feature coverage for the <dmx-ag-grid> component, designed for agent-driven browser testing.'
19
+ });
20
+ });
21
+ </script>
22
+
23
+ <main class="test-main">
24
+ <section class="feature">
25
+ <h2>How to run</h2>
26
+ <p class="desc">
27
+ Serve the project root over HTTP (e.g. <code>npx http-server . -p 8080 -c-1</code>) then visit
28
+ <code>/tests/index.html</code>. Each scenario waits until <code>document.body.dataset.gridsReady === 'true'</code>
29
+ before the grids are interactable — agents can poll that flag instead of guessing timings.
30
+ </p>
31
+ <p class="desc">
32
+ Console helpers: <code>TestKit.grid(id)</code>, <code>TestKit.waitForGrid(id)</code>,
33
+ <code>TestKit.snapshot(id)</code>, <code>TestKit.callMethod(id, name, ...args)</code>.
34
+ Version drift between the running AppConnect build and <code>tests/VERSIONS.json</code> is logged on every page.
35
+ </p>
36
+ </section>
37
+
38
+ <section class="feature">
39
+ <h2>Scenarios</h2>
40
+ <p class="desc">Each scenario isolates a feature group so failures are easy to attribute.</p>
41
+ <div class="scenario-grid">
42
+ <a class="scenario-card" href="./01-basic-render.html">
43
+ <h3>01 · Basic Render <span class="badge">core</span></h3>
44
+ <p>Default column inference, sort, filter, floating filter, pagination, count binding.</p>
45
+ </a>
46
+ <a class="scenario-card" href="./02-themes-locale.html">
47
+ <h3>02 · Themes &amp; Locale <span class="badge">visual</span></h3>
48
+ <p>All five built-in themes, dark mode toggle, RTL, locale switch (EN/HE/RU/ES/PT).</p>
49
+ </a>
50
+ <a class="scenario-card" href="./03-editing.html">
51
+ <h3>03 · Editing <span class="badge">edit</span></h3>
52
+ <p>Cell editable, row editable, static/dynamic select editors, edited-event payloads.</p>
53
+ </a>
54
+ <a class="scenario-card" href="./04-selection.html">
55
+ <h3>04 · Selection &amp; Row Events <span class="badge">events</span></h3>
56
+ <p>Single/multi-row selection, row checkboxes, row click / double-click / status events.</p>
57
+ </a>
58
+ <a class="scenario-card" href="./05-action-buttons.html">
59
+ <h3>05 · Action Buttons <span class="badge">actions</span></h3>
60
+ <p>Built-in edit/view/delete + 5 custom action buttons with conditions and class toggles.</p>
61
+ </a>
62
+ <a class="scenario-card" href="./06-export.html">
63
+ <h3>06 · Export <span class="badge">io</span></h3>
64
+ <p>CSV / XLSX / PDF download, trim, exclude hidden, exclude fields, strip HTML, custom filename.</p>
65
+ </a>
66
+ <a class="scenario-card" href="./07-import.html">
67
+ <h3>07 · Import <span class="badge">io</span></h3>
68
+ <p>importFileData() with CSV and Excel sources, surfacing fileData binding.</p>
69
+ </a>
70
+ <a class="scenario-card" href="./08-formatting.html">
71
+ <h3>08 · Formatting &amp; Types <span class="badge">format</span></h3>
72
+ <p>Custom column types, amount fields with precision, date format + timezone, column align.</p>
73
+ </a>
74
+ <a class="scenario-card" href="./09-grouping.html">
75
+ <h3>09 · Grouping &amp; Aggregation <span class="badge">group</span></h3>
76
+ <p>group_config, columns_to_sum / columns_to_count, footer sum precision.</p>
77
+ </a>
78
+ <a class="scenario-card" href="./10-state-persistence.html">
79
+ <h3>10 · State Persistence <span class="badge">state</span></h3>
80
+ <p>Save/reset column state to localStorage, applied filter introspection, programmatic filters.</p>
81
+ </a>
82
+ <a class="scenario-card" href="./11-layout.html">
83
+ <h3>11 · Layout &amp; Scroll <span class="badge">layout</span></h3>
84
+ <p>autoHeight vs normal layout, fixed header/footer, horizontal scroll, hide_id_field.</p>
85
+ </a>
86
+ <a class="scenario-card" href="./12-methods.html">
87
+ <h3>12 · Component Methods <span class="badge">api</span></h3>
88
+ <p>reloadGrid, loadGrid, destroyGrid, pinColumns, hideColumns, quickFilter, applyFilters, clearFilters.</p>
89
+ </a>
90
+ </div>
91
+ </section>
92
+
93
+ <section class="feature">
94
+ <h2>Version manifest</h2>
95
+ <p class="desc">If the AppConnect pill in the header doesn't match, run every scenario and bump <code>tests/VERSIONS.json</code>.</p>
96
+ <pre class="log" id="version-dump">loading...</pre>
97
+ </section>
98
+ </main>
99
+
100
+ <script>
101
+ fetch('./VERSIONS.json').then(r => r.json()).then(j => {
102
+ const el = document.getElementById('version-dump');
103
+ if (el) el.textContent = JSON.stringify(j, null, 2);
104
+ }).catch(() => {});
105
+ </script>
106
+ </body>
107
+ </html>
@@ -0,0 +1,30 @@
1
+ <!--
2
+ Common head snippet for AG Grid scenario pages. Copy the block below into each
3
+ scenario's <head>. Kept here so adding a new vendor file means editing one place.
4
+ Load order matters: AppConnect → helpers/banner → AG Grid lib + locale → exports → dmx-ag-grid.js
5
+ -->
6
+
7
+ <link rel="stylesheet" href="./lib/test-suite.css">
8
+ <link rel="stylesheet" href="../node_modules/ag-grid-community/styles/ag-theme-alpine.css">
9
+ <link rel="stylesheet" href="../node_modules/ag-grid-community/styles/ag-theme-balham.css">
10
+ <link rel="stylesheet" href="../node_modules/ag-grid-community/styles/ag-theme-material.css">
11
+ <link rel="stylesheet" href="../node_modules/ag-grid-community/styles/ag-theme-quartz.css">
12
+ <link rel="stylesheet" href="../ag-theme-custom.css">
13
+ <link rel="stylesheet" href="../switch-toggle-slider.css">
14
+
15
+ <script src="./dmxAppConnect/dmxAppConnect.js" defer></script>
16
+ <script src="./lib/version-banner.js" defer></script>
17
+ <script src="./lib/test-helpers.js" defer></script>
18
+ <script src="./lib/page-template.js" defer></script>
19
+
20
+ <script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
21
+ <script src="../node_modules/@ag-grid-community/locale/dist/umd/@ag-grid-community/locale.min.js" defer></script>
22
+ <script src="../ru.js" defer></script>
23
+ <script src="../node_modules/papaparse/papaparse.min.js" defer></script>
24
+ <script src="../node_modules/read-excel-file/bundle/read-excel-file.min.js" defer></script>
25
+ <script src="../node_modules/pdfmake/build/pdfmake.min.js" defer></script>
26
+ <script src="../node_modules/pdfmake/build/vfs_fonts.js" defer></script>
27
+ <script src="../node_modules/exceljs/dist/exceljs.min.js" defer></script>
28
+ <script src="../dmx-ag-grid.js" defer></script>
29
+
30
+ <script src="./data/sample-data.js" defer></script>
@@ -0,0 +1,54 @@
1
+ // Shared page chrome — renders header + breadcrumbs so each scenario stays consistent.
2
+ // Call once at the top of <body>: TestPage.header({ title: '01 - Basic', summary: '...' });
3
+ window.TestPage = (function () {
4
+ const VERSION = window.__AG_GRID_TEST_SUITE__ || {};
5
+ return {
6
+ header: function (opts) {
7
+ const wrap = document.createElement('header');
8
+ wrap.className = 'test-header';
9
+ wrap.innerHTML = `
10
+ <div>
11
+ <nav class="crumbs"><a href="./index.html">&larr; Suite</a></nav>
12
+ <h1>${opts.title || 'AG Grid Scenario'}</h1>
13
+ <div class="meta">${opts.summary || ''}</div>
14
+ </div>
15
+ <div class="meta">
16
+ <span class="tag-status"><span class="dot" id="page-status"></span><span id="page-status-text">loading</span></span>
17
+ <div>AppConnect <code>${VERSION.appConnect || '?'}</code> · Module <code>${VERSION.module || '?'}</code></div>
18
+ </div>`;
19
+ document.body.insertBefore(wrap, document.body.firstChild);
20
+
21
+ // Update status dot when AppConnect parses and any grids report ready.
22
+ const setStatus = (state, text) => {
23
+ const dot = document.getElementById('page-status');
24
+ const txt = document.getElementById('page-status-text');
25
+ if (dot) dot.className = 'dot ' + state;
26
+ if (txt) txt.textContent = text;
27
+ };
28
+ const wait = () => {
29
+ if (!window.dmx || !dmx.app) return setTimeout(wait, 50);
30
+ setStatus('ready', 'app parsed');
31
+ // Best-effort: look for any registered grids and watch their state.
32
+ const grids = Array.from(document.querySelectorAll('dmx-ag-grid'));
33
+ if (!grids.length) return;
34
+ const ids = grids.map(g => g.id).filter(Boolean);
35
+ const tick = () => {
36
+ if (!dmx.app || !dmx.app.get) return setTimeout(tick, 100);
37
+ const allReady = ids.every(id => {
38
+ const c = dmx.app.get(id);
39
+ // gridReady fires first, then firstDataRendered — either is proof the grid mounted.
40
+ return c && c.state && (c.state.gridReady || c.state.firstDataRendered);
41
+ });
42
+ if (allReady) {
43
+ setStatus('ready', 'grids ready: ' + ids.join(', '));
44
+ document.body.dataset.gridsReady = 'true';
45
+ } else {
46
+ setTimeout(tick, 150);
47
+ }
48
+ };
49
+ tick();
50
+ };
51
+ wait();
52
+ }
53
+ };
54
+ })();
@@ -0,0 +1,32 @@
1
+ // Init script — injected on every page load by `agent-browser open ... --init-script`.
2
+ // Exposes window.__abCollect(), used by tests/run-all.cjs to harvest grid state
3
+ // without having to send a giant eval expression across the CLI boundary.
4
+ window.__abCollect = function () {
5
+ try {
6
+ if (!window.dmx) return JSON.stringify({ ready: false, reason: 'dmx-not-loaded' });
7
+ if (!window.TestKit) return JSON.stringify({ ready: false, reason: 'testkit-not-loaded' });
8
+ const grids = TestKit.list();
9
+ const out = {
10
+ url: location.href,
11
+ appConnect: dmx.version,
12
+ ready: document.body.dataset.gridsReady === 'true',
13
+ grids: {}
14
+ };
15
+ grids.forEach(id => {
16
+ const c = TestKit.grid(id);
17
+ if (!c) return;
18
+ out.grids[id] = {
19
+ count: c.count,
20
+ state: c.state,
21
+ dataLen: Array.isArray(c.data) ? c.data.length : null,
22
+ selectedRowsLen: (c.selectedRows || []).length,
23
+ filterStateKeys: c.filterState && typeof c.filterState === 'object' ? Object.keys(c.filterState) : [],
24
+ columnStateLen: (c.columnState || []).length,
25
+ domRows: document.querySelectorAll('#' + id + '-grid .ag-row').length
26
+ };
27
+ });
28
+ return JSON.stringify(out);
29
+ } catch (e) {
30
+ return JSON.stringify({ fatal: e.message, stack: (e.stack || '').split('\n').slice(0, 4) });
31
+ }
32
+ };
@@ -0,0 +1,145 @@
1
+ // Test helpers — exposed as both an AppConnect component and a console-friendly API.
2
+ //
3
+ // <dmx-test-data id="rows" source="employees"></dmx-test-data>
4
+ // → exposes rows.value = window.SampleData.employees
5
+ //
6
+ // <dmx-event-log id="log"></dmx-event-log>
7
+ // → use dmx-on:event_name="log.add(event)" on a grid to record event names.
8
+ //
9
+ // Both load before dmxAppConnect.js auto-parse, so they are available
10
+ // when the page is first parsed by AppConnect.
11
+
12
+ (function () {
13
+ if (typeof dmx === 'undefined') {
14
+ console.error('[test-helpers] dmx is not loaded yet — load dmxAppConnect.js before this file.');
15
+ return;
16
+ }
17
+
18
+ dmx.Component('test-data', {
19
+ initialData: {
20
+ value: [],
21
+ ready: false
22
+ },
23
+ attributes: {
24
+ source: { type: String, default: null },
25
+ data: { type: Array, default: null }
26
+ },
27
+ init: function () {
28
+ // Defer to next tick so the grid (or any consumer) sees a [] -> [...] transition,
29
+ // which is the only way dmx-ag-grid's requestUpdate('data', oldValue) fires.
30
+ const self = this;
31
+ const apply = function () { self._apply(); };
32
+ if (typeof dmx !== 'undefined' && typeof dmx.nextTick === 'function') {
33
+ dmx.nextTick(apply, self);
34
+ } else {
35
+ setTimeout(apply, 0);
36
+ }
37
+ },
38
+ requestUpdate: function () {
39
+ // If `data` or `source` attribute changes later, re-apply.
40
+ this._apply();
41
+ },
42
+ methods: {
43
+ reload: function () { this._apply(); },
44
+ mutate: function (mutator) {
45
+ if (typeof mutator === 'function') {
46
+ const next = mutator((this.data.value || []).slice());
47
+ this.set('value', next);
48
+ }
49
+ }
50
+ },
51
+ _apply: function () {
52
+ let next = [];
53
+ if (Array.isArray(this.props.data) && this.props.data.length) {
54
+ next = this.props.data;
55
+ } else if (this.props.source && window.SampleData && window.SampleData[this.props.source]) {
56
+ next = window.SampleData[this.props.source].slice();
57
+ }
58
+ this.set('value', next);
59
+ this.set('ready', true);
60
+ }
61
+ });
62
+
63
+ dmx.Component('event-log', {
64
+ initialData: {
65
+ events: [],
66
+ last: null,
67
+ count: 0
68
+ },
69
+ attributes: {
70
+ max: { type: Number, default: 100 }
71
+ },
72
+ methods: {
73
+ add: function (entry) {
74
+ const events = this.data.events.slice();
75
+ const stamp = new Date().toISOString();
76
+ const record = typeof entry === 'string'
77
+ ? { name: entry, at: stamp }
78
+ : { name: (entry && entry.type) || 'unknown', at: stamp, detail: entry };
79
+ events.unshift(record);
80
+ if (events.length > this.props.max) events.length = this.props.max;
81
+ this.set('events', events);
82
+ this.set('last', record);
83
+ this.set('count', events.length);
84
+ },
85
+ clear: function () {
86
+ this.set('events', []);
87
+ this.set('last', null);
88
+ this.set('count', 0);
89
+ }
90
+ }
91
+ });
92
+
93
+ // Console / agent helper: window.TestKit
94
+ // dmx.app.get(id) returns the reactive-data proxy, with state on .state and
95
+ // public methods exposed as __methodName (e.g. __reloadGrid).
96
+ window.TestKit = {
97
+ grid: function (id) {
98
+ return dmx.app && typeof dmx.app.get === 'function' ? dmx.app.get(id) : null;
99
+ },
100
+ waitForGrid: function (id, timeout) {
101
+ timeout = timeout || 5000;
102
+ return new Promise((resolve, reject) => {
103
+ const start = Date.now();
104
+ const tick = () => {
105
+ const g = window.TestKit.grid(id);
106
+ if (g && g.state && g.state.gridReady) return resolve(g);
107
+ if (Date.now() - start > timeout) return reject(new Error('grid ' + id + ' did not become ready'));
108
+ setTimeout(tick, 50);
109
+ };
110
+ tick();
111
+ });
112
+ },
113
+ snapshot: function (id) {
114
+ const g = window.TestKit.grid(id);
115
+ if (!g) return null;
116
+ return {
117
+ count: g.count,
118
+ state: g.state,
119
+ selectedRows: g.selectedRows,
120
+ filterState: g.filterState,
121
+ columnState: g.columnState,
122
+ fields: g.fields,
123
+ rowId: g.id
124
+ };
125
+ },
126
+ callMethod: function (id, method) {
127
+ const args = Array.prototype.slice.call(arguments, 2);
128
+ const g = window.TestKit.grid(id);
129
+ const key = '__' + method;
130
+ if (!g || typeof g[key] !== 'function') {
131
+ console.error('[TestKit] no method', method, 'on', id);
132
+ return null;
133
+ }
134
+ return g[key].apply(g, args);
135
+ },
136
+ list: function () {
137
+ // Find dmx-ag-grid elements in the DOM and return their dmx ids — more reliable
138
+ // than poking at internal component metadata, which AppConnect doesn't expose
139
+ // consistently across versions.
140
+ return Array.from(document.querySelectorAll('dmx-ag-grid'))
141
+ .map(el => el.id)
142
+ .filter(Boolean);
143
+ }
144
+ };
145
+ })();
@@ -0,0 +1,178 @@
1
+ /* Minimal styling for the AG Grid test pages — no Bootstrap dependency. */
2
+ :root {
3
+ --bg: #f7f9fc;
4
+ --fg: #1f2937;
5
+ --muted: #6b7280;
6
+ --accent: #2563eb;
7
+ --border: #e5e7eb;
8
+ --ok: #047857;
9
+ --bad: #b91c1c;
10
+ }
11
+ * { box-sizing: border-box; }
12
+ body {
13
+ font-family: -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
14
+ margin: 0;
15
+ padding: 0;
16
+ background: var(--bg);
17
+ color: var(--fg);
18
+ font-size: 14px;
19
+ }
20
+ header.test-header {
21
+ background: #fff;
22
+ border-bottom: 1px solid var(--border);
23
+ padding: 12px 20px;
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: space-between;
27
+ gap: 12px;
28
+ }
29
+ header.test-header h1 {
30
+ font-size: 18px;
31
+ margin: 0;
32
+ }
33
+ header.test-header .meta {
34
+ font-size: 12px;
35
+ color: var(--muted);
36
+ }
37
+ nav.crumbs a {
38
+ color: var(--accent);
39
+ text-decoration: none;
40
+ margin-right: 8px;
41
+ }
42
+ main.test-main {
43
+ padding: 16px 20px 40px;
44
+ max-width: 1400px;
45
+ margin: 0 auto;
46
+ }
47
+ section.feature {
48
+ background: #fff;
49
+ border: 1px solid var(--border);
50
+ border-radius: 8px;
51
+ padding: 16px;
52
+ margin-bottom: 20px;
53
+ box-shadow: 0 1px 2px rgba(0,0,0,0.03);
54
+ }
55
+ section.feature h2 {
56
+ font-size: 16px;
57
+ margin: 0 0 4px;
58
+ }
59
+ section.feature .desc {
60
+ color: var(--muted);
61
+ font-size: 13px;
62
+ margin-bottom: 12px;
63
+ }
64
+ .controls {
65
+ display: flex;
66
+ flex-wrap: wrap;
67
+ gap: 8px;
68
+ margin: 8px 0 12px;
69
+ }
70
+ .controls button,
71
+ .controls .btn {
72
+ background: #fff;
73
+ border: 1px solid var(--border);
74
+ border-radius: 6px;
75
+ padding: 6px 12px;
76
+ cursor: pointer;
77
+ font-size: 13px;
78
+ font-family: inherit;
79
+ color: var(--fg);
80
+ }
81
+ .controls button:hover { border-color: var(--accent); color: var(--accent); }
82
+ .controls .primary {
83
+ background: var(--accent);
84
+ color: #fff;
85
+ border-color: var(--accent);
86
+ }
87
+ .controls .primary:hover { color: #fff; opacity: 0.9; }
88
+ .controls input[type=text],
89
+ .controls input[type=search] {
90
+ padding: 6px 10px;
91
+ border: 1px solid var(--border);
92
+ border-radius: 6px;
93
+ font-size: 13px;
94
+ font-family: inherit;
95
+ min-width: 220px;
96
+ }
97
+ .controls label {
98
+ display: inline-flex;
99
+ align-items: center;
100
+ gap: 4px;
101
+ font-size: 13px;
102
+ }
103
+ .kv {
104
+ display: grid;
105
+ grid-template-columns: 160px 1fr;
106
+ gap: 4px 12px;
107
+ font-size: 12px;
108
+ margin-top: 12px;
109
+ }
110
+ .kv .k { color: var(--muted); }
111
+ .kv .v { font-family: ui-monospace, Menlo, Consolas, monospace; word-break: break-all; }
112
+ .log {
113
+ background: #0b1020;
114
+ color: #e6edf3;
115
+ border-radius: 6px;
116
+ padding: 8px 12px;
117
+ max-height: 240px;
118
+ overflow: auto;
119
+ font-family: ui-monospace, Menlo, Consolas, monospace;
120
+ font-size: 12px;
121
+ }
122
+ .log-entry { padding: 2px 0; border-bottom: 1px solid #1f2937; }
123
+ .log-entry:last-child { border-bottom: none; }
124
+ .grid-wrap {
125
+ border: 1px solid var(--border);
126
+ border-radius: 6px;
127
+ overflow: hidden;
128
+ background: #fff;
129
+ }
130
+ .scenario-grid {
131
+ display: grid;
132
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
133
+ gap: 12px;
134
+ }
135
+ .scenario-card {
136
+ background: #fff;
137
+ border: 1px solid var(--border);
138
+ border-radius: 8px;
139
+ padding: 12px 14px;
140
+ text-decoration: none;
141
+ color: inherit;
142
+ display: block;
143
+ transition: border-color .15s;
144
+ }
145
+ .scenario-card:hover { border-color: var(--accent); }
146
+ .scenario-card h3 {
147
+ margin: 0 0 4px;
148
+ font-size: 14px;
149
+ color: var(--accent);
150
+ }
151
+ .scenario-card p {
152
+ margin: 0;
153
+ font-size: 12px;
154
+ color: var(--muted);
155
+ line-height: 1.4;
156
+ }
157
+ .badge {
158
+ display: inline-block;
159
+ font-size: 10px;
160
+ padding: 2px 6px;
161
+ border-radius: 9999px;
162
+ background: #eef2ff;
163
+ color: #4338ca;
164
+ margin-left: 6px;
165
+ }
166
+ .tag-status {
167
+ display: inline-flex;
168
+ align-items: center;
169
+ gap: 6px;
170
+ font-size: 12px;
171
+ color: var(--muted);
172
+ }
173
+ .dot {
174
+ width: 8px; height: 8px; border-radius: 50%;
175
+ display: inline-block; background: #d1d5db;
176
+ }
177
+ .dot.ready { background: var(--ok); }
178
+ .dot.error { background: var(--bad); }