@cdmx/wappler_ag_grid 2.1.7 โ 2.1.9
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/CHANGELOG.md +17 -0
- package/README.md +73 -10
- package/app_connect/components.hjson +1 -1
- package/dmx-ag-grid.js +25 -4
- package/package.json +1 -1
- package/tests/01-basic-render.html +1 -1
- package/tests/02-themes-locale.html +1 -1
- package/tests/03-editing.html +1 -1
- package/tests/04-selection.html +1 -1
- package/tests/05-action-buttons.html +1 -1
- package/tests/06-export.html +1 -1
- package/tests/07-import.html +1 -1
- package/tests/08-formatting.html +1 -1
- package/tests/09-grouping.html +1 -1
- package/tests/10-state-persistence.html +1 -1
- package/tests/11-layout.html +1 -1
- package/tests/12-methods.html +1 -1
- package/tests/13-flags-styles.html +71 -7
- package/tests/14-compact-view.html +1 -1
- package/tests/15-csp.html +98 -0
- package/tests/FINDINGS.md +229 -0
- package/tests/README.md +6 -0
- package/tests/VERSIONS.json +29 -0
- package/tests/csp-hashes.cjs +191 -0
- package/tests/data/sample-data.js +12 -1
- package/tests/findings.json +928 -0
- package/tests/lib/common-head.html +1 -1
- package/tests/lib/test-helpers.js +3 -1
- package/tests/lib/version-banner.js +46 -0
- package/tests/run-all-puppeteer.cjs +8 -3
- package/tests/run-all.cjs +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## Version 2.1.9 (Filter on JS-rendered columns)
|
|
4
|
+
|
|
5
|
+
### Summary
|
|
6
|
+
Column filters, the floating filter row, the quick filter, `applyFilters` and preset `cfilters` now match the
|
|
7
|
+
text a `js_data_changes` function renders (HTML removed) instead of the raw field value. Columns whose raw
|
|
8
|
+
value is a JSON array/object are matched against their JSON text. Fixes "contains" filters on result-list
|
|
9
|
+
columns (e.g. Monitoring Results / Screening Results) returning no rows.
|
|
10
|
+
|
|
11
|
+
### Changes Made
|
|
12
|
+
1. **`dmx-ag-grid.js`** - `js_data_changes` columns get a `filterValueGetter` that runs the render function
|
|
13
|
+
and strips tags via `removeHtmlTags` (now a hoisted function declaration); the shared filter value getter
|
|
14
|
+
stringifies object/array values.
|
|
15
|
+
2. **`tests/13-flags-styles.html`**, **`tests/data/sample-data.js`** - `jsFilterGrid` scenario with a
|
|
16
|
+
`transactions` fixture and page assertions for column + quick filters.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
3
20
|
## Version 2.1.4 (Row Color Formatting Fix)
|
|
4
21
|
|
|
5
22
|
### Summary
|
package/README.md
CHANGED
|
@@ -2,6 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
**Major Update:** This release upgrades AG Grid to v35.1.0, bringing continued performance improvements and new features.
|
|
4
4
|
|
|
5
|
+
## ๐ What's New in v2.1.8
|
|
6
|
+
|
|
7
|
+
**`style-src` without `'unsafe-inline'`, via a page nonce.** AG Grid v35 injects its theme CSS as
|
|
8
|
+
`<style>` elements at runtime, so any policy without `'unsafe-inline'` broke the grid. This release:
|
|
9
|
+
|
|
10
|
+
- ships `ag-grid-community.min.noStyle.js` under the existing `js/ag-grid-community.min.js` name.
|
|
11
|
+
The with-styles build injected 14 `<style>` blocks at script load that cannot take a nonce, and
|
|
12
|
+
AG Grid deleted them again the moment a grid initialised (they only serve legacy-theme mode, which
|
|
13
|
+
this module never uses). Nothing visible changes and existing `<script>` links keep working โ
|
|
14
|
+
update the module in each project and the file is overwritten.
|
|
15
|
+
- reads a page nonce and passes it to AG Grid (`styleNonce`) and to the module's own three
|
|
16
|
+
`<style>` blocks. The nonce is page-global, as AG Grid requires; it is taken from
|
|
17
|
+
`<meta name="csp-nonce" content="โฆ">` or from any `<script nonce="โฆ">` already on the page. Pages
|
|
18
|
+
without a nonce behave exactly as before.
|
|
19
|
+
|
|
20
|
+
To use it, generate a fresh random nonce **per response** (a fixed value is equivalent to
|
|
21
|
+
`'unsafe-inline'`) and put it in both places:
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<meta name="csp-nonce" content="<nonce>"> <!-- layout <head> -->
|
|
25
|
+
```
|
|
26
|
+
```
|
|
27
|
+
Content-Security-Policy: style-src 'self' 'nonce-<nonce>'; img-src 'self' data:; font-src 'self' data:
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`img-src data:` covers AG Grid's SVG icons, `font-src data:` the icon fonts embedded in the theme
|
|
31
|
+
CSS; `script-src` needs nothing from the module (`'unsafe-eval'` only with `enable_cell_expressions`).
|
|
32
|
+
|
|
33
|
+
Server side:
|
|
34
|
+
|
|
35
|
+
- **Express / Wappler NodeJS** โ middleware registered before the page routes (in Wappler, a custom
|
|
36
|
+
routes file under `extensions/server_connect/routes/` receives the Express `app`):
|
|
37
|
+
```js
|
|
38
|
+
const crypto = require('crypto');
|
|
39
|
+
app.use((req, res, next) => {
|
|
40
|
+
res.locals.cspNonce = crypto.randomBytes(16).toString('base64');
|
|
41
|
+
res.setHeader('Content-Security-Policy',
|
|
42
|
+
`style-src 'self' 'nonce-${res.locals.cspNonce}'; img-src 'self' data:; font-src 'self' data:`);
|
|
43
|
+
next();
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
and `<meta name="csp-nonce" content="<%= cspNonce %>">` in the EJS layout.
|
|
47
|
+
- **nginx** โ `set $csp_nonce $request_id;`, `add_header Content-Security-Policy "style-src 'self'
|
|
48
|
+
'nonce-$csp_nonce'; img-src 'self' data:; font-src 'self' data:" always;`, and inject it into the
|
|
49
|
+
HTML with `sub_filter '__CSP_NONCE__' $csp_nonce; sub_filter_once off;` against
|
|
50
|
+
`<meta name="csp-nonce" content="__CSP_NONCE__">` in the layout (`sub_filter` needs uncompressed
|
|
51
|
+
upstream HTML: `proxy_set_header Accept-Encoding "";`).
|
|
52
|
+
- **PHP** โ `$nonce = base64_encode(random_bytes(16));` then `header("Content-Security-Policy: โฆ
|
|
53
|
+
'nonce-$nonce' โฆ");` and `<meta name="csp-nonce" content="<?= $nonce ?>">`.
|
|
54
|
+
|
|
55
|
+
Regression coverage: `tests/15-csp.html` is served under `style-src 'self' 'nonce-csp15'` with no
|
|
56
|
+
`'unsafe-inline'` and asserts zero violations, every `<style>` nonced, and the layout,
|
|
57
|
+
`vert_center_cell_data` and `fixed_horizontal_scroll` styles applied.
|
|
58
|
+
|
|
5
59
|
## ๐ What's New in v2.1.7
|
|
6
60
|
|
|
7
61
|
**Content Security Policy compliance.** The status toggle column (rendered when a grid has a
|
|
@@ -15,22 +69,29 @@ The renderer now emits `data-status-col` / `data-status-value` / `data-status-id
|
|
|
15
69
|
a single delegated `click` listener on the grid container dispatches the events. Behaviour is
|
|
16
70
|
unchanged; no configuration or view changes are required.
|
|
17
71
|
|
|
18
|
-
- **No inline event handlers**
|
|
19
|
-
`'unsafe-inline'` nor per-row hashes.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'sha256-rW177u2hd7RHx4qSlJmD1nI6607/SZ01lrewgfHuj/M=' cell centering
|
|
24
|
-
'sha256-Et0Yl8993s0IRJOeeDT++DYDRSXLRDRvbgCj9zG2J9A=' sticky horizontal scrollbar
|
|
25
|
-
'sha256-6oQ3cIavZD3cp6igHrAN3HjrosWY41/7+bYLY+je9F0=' pagination panel
|
|
26
|
-
```
|
|
27
|
-
These must be recomputed if that CSS text is ever edited.
|
|
72
|
+
- **No inline event handlers** on any documented rendering path, so `script-src` needs neither
|
|
73
|
+
`'unsafe-inline'` nor per-row hashes. The one exception is the legacy `clickCellRenderer`, which
|
|
74
|
+
is only reachable by passing raw AG Grid column definitions through `column_defs` with
|
|
75
|
+
`cellRenderer: 'clickCellRenderer'`; it still emits `onclick=`/`style=` and cannot be used under
|
|
76
|
+
a strict policy.
|
|
28
77
|
- Toggle values are now attribute-escaped rather than interpolated into a JavaScript string
|
|
29
78
|
literal, closing a quote-breakout in the previous renderer.
|
|
30
79
|
|
|
31
80
|
Regression coverage lives in `tests/04-selection.html` (`window.__statusCheck`), which asserts the
|
|
32
81
|
grid emits zero `[onclick]` attributes and that the toggle still dispatches its events.
|
|
33
82
|
|
|
83
|
+
### Alternative without a nonce: hash allow-list
|
|
84
|
+
|
|
85
|
+
If a per-request nonce is not possible on your stack, every `<style>` block AG Grid and this module
|
|
86
|
+
inject can be allowed by `'sha256-โฆ'` hash instead. `node tests/csp-hashes.cjs` harvests them from
|
|
87
|
+
the test scenarios and prints the `style-src` directive (helmet array form too); `--verify` reloads
|
|
88
|
+
every scenario under that header and exits 1 on any violation. Caveats, which are why the nonce is
|
|
89
|
+
the recommended route: the hashes are bound to the exact bytes of `ag-grid-community.min.js` and
|
|
90
|
+
`dmx-ag-grid.js` (regenerate after **any** upgrade of either), AG Grid injects component CSS lazily
|
|
91
|
+
so a feature the scenarios don't exercise can need one more hash (that component renders unstyled and
|
|
92
|
+
the console names the hash), and hashes cover `<style>` elements only โ inline `style="โฆ"` attributes
|
|
93
|
+
in your own pages still need `style-src-attr` handling.
|
|
94
|
+
|
|
34
95
|
## ๐ What's New in v2.1.1
|
|
35
96
|
|
|
36
97
|
1. **๐ฅ AG Grid v35.1.0** - Latest version with all community features
|
|
@@ -347,6 +408,8 @@ ex: jsChanges.
|
|
|
347
408
|
}
|
|
348
409
|
</script>
|
|
349
410
|
```
|
|
411
|
+
Column filters, the floating filter row and the quick filter match the text this function renders (HTML tags removed, `<br>` treated as a line break), so searching for a word that is visible in the cell finds the row. Columns whose raw value is a JSON array/object are matched against their JSON text.
|
|
412
|
+
|
|
350
413
|
---
|
|
351
414
|
# Custom Headers
|
|
352
415
|
|
|
@@ -3862,7 +3862,7 @@
|
|
|
3862
3862
|
"allowed_children": {},
|
|
3863
3863
|
"copyFiles": [
|
|
3864
3864
|
{
|
|
3865
|
-
"src": "../../../node_modules/ag-grid-community/dist/ag-grid-community.min.js",
|
|
3865
|
+
"src": "../../../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js",
|
|
3866
3866
|
"dst": "js/ag-grid-community.min.js"
|
|
3867
3867
|
},
|
|
3868
3868
|
{
|
package/dmx-ag-grid.js
CHANGED
|
@@ -621,6 +621,10 @@ dmx.Component('ag-grid', {
|
|
|
621
621
|
|
|
622
622
|
const gridThemeClass = options.dark_mode ? `${options.grid_theme}-dark` : options.grid_theme;
|
|
623
623
|
this.$node.innerHTML = `<div id=${options.id}-grid class="${gridThemeClass}"></div>`;
|
|
624
|
+
// CSP: nonce for every <style> AG Grid and this module inject. Page-global, read from
|
|
625
|
+
// <meta name="csp-nonce" content="โฆ"> or any nonce'd <script>; undefined when the page has none.
|
|
626
|
+
const cspNonce = (document.querySelector('meta[name="csp-nonce"]') || {}).content
|
|
627
|
+
|| (document.querySelector('script[nonce]') || {}).nonce || undefined;
|
|
624
628
|
|
|
625
629
|
// Apply theme mode using data attribute
|
|
626
630
|
const themeContainer = this.$node.querySelector(`#${options.id}-grid`);
|
|
@@ -1349,8 +1353,10 @@ dmx.Component('ag-grid', {
|
|
|
1349
1353
|
return parseFloat(value);
|
|
1350
1354
|
}
|
|
1351
1355
|
}
|
|
1352
|
-
// Return the original value if no matching changes were found
|
|
1353
|
-
|
|
1356
|
+
// Return the original value if no matching changes were found.
|
|
1357
|
+
// ponytail: arrays/objects (JSON result lists) stringify so text filters can match nested words;
|
|
1358
|
+
// keys match too - flatten string leaves if that ever bites.
|
|
1359
|
+
return (value !== null && typeof value === 'object') ? JSON.stringify(value) : value;
|
|
1354
1360
|
};
|
|
1355
1361
|
};
|
|
1356
1362
|
if (Array.isArray(this.props.column_defs) && this.props.column_defs.length > 0) {
|
|
@@ -1534,6 +1540,16 @@ dmx.Component('ag-grid', {
|
|
|
1534
1540
|
return cellValue;
|
|
1535
1541
|
}
|
|
1536
1542
|
}
|
|
1543
|
+
// ponytail: filter on what the user sees - renderer output with tags stripped.
|
|
1544
|
+
// Covers column/floating filters, quick filter, applyFilters and cfilters (all read filterValueGetter).
|
|
1545
|
+
const baseFilterValueGetter = filterValueGetter;
|
|
1546
|
+
filterValueGetter = (params) => {
|
|
1547
|
+
const fn = window[matchingJsChange.function];
|
|
1548
|
+
if (typeof fn !== 'function' || !params.data) {
|
|
1549
|
+
return baseFilterValueGetter ? baseFilterValueGetter(params) : undefined;
|
|
1550
|
+
}
|
|
1551
|
+
return removeHtmlTags(fn(params.data));
|
|
1552
|
+
};
|
|
1537
1553
|
}
|
|
1538
1554
|
else {
|
|
1539
1555
|
cellRenderer = undefined;
|
|
@@ -1823,6 +1839,7 @@ dmx.Component('ag-grid', {
|
|
|
1823
1839
|
localeText = AG_GRID_LOCALE_ES
|
|
1824
1840
|
}
|
|
1825
1841
|
const gridOptions = {
|
|
1842
|
+
styleNonce: cspNonce,
|
|
1826
1843
|
...(idFieldPresent ? { getRowId: params => String(params.data.id) } : {}),
|
|
1827
1844
|
columnDefs: (groupedColumnDefs && groupedColumnDefs.length > 0) ? groupedColumnDefs : columnDefs,
|
|
1828
1845
|
getRowStyle: options.rstyles ? createRowStyleFunction(options.rstyles): undefined,
|
|
@@ -2153,6 +2170,7 @@ dmx.Component('ag-grid', {
|
|
|
2153
2170
|
const gridElement = document.getElementById(options.id+'-grid');
|
|
2154
2171
|
if (options.vert_center_cell_data) {
|
|
2155
2172
|
const styleElement = document.createElement('style');
|
|
2173
|
+
if (cspNonce) styleElement.setAttribute('nonce', cspNonce);
|
|
2156
2174
|
if (options.vert_center_cell_data) {
|
|
2157
2175
|
styleElement.textContent += `
|
|
2158
2176
|
.ag-cell {
|
|
@@ -2250,6 +2268,7 @@ dmx.Component('ag-grid', {
|
|
|
2250
2268
|
if (options.fixed_horizontal_scroll) {
|
|
2251
2269
|
const styleElement = document.createElement('style');
|
|
2252
2270
|
styleElement.id = 'hovering-bar-style';
|
|
2271
|
+
if (cspNonce) styleElement.setAttribute('nonce', cspNonce);
|
|
2253
2272
|
const agRootWrapper = gridElement.querySelector('.ag-root-wrapper');
|
|
2254
2273
|
const bodyHorizontalScrollElement = gridElement.querySelector('.ag-body-horizontal-scroll');
|
|
2255
2274
|
const rootWrapperWidth = agRootWrapper.clientWidth;
|
|
@@ -2281,7 +2300,8 @@ dmx.Component('ag-grid', {
|
|
|
2281
2300
|
|
|
2282
2301
|
//CSV Export Function
|
|
2283
2302
|
// Helper function to remove HTML tags from string
|
|
2284
|
-
|
|
2303
|
+
// Function declaration (hoisted): column-def filter getters built earlier in refreshGrid use it.
|
|
2304
|
+
function removeHtmlTags(htmlString) {
|
|
2285
2305
|
if (typeof htmlString !== 'string') return htmlString;
|
|
2286
2306
|
// Remove all HTML tags and decode common HTML entities
|
|
2287
2307
|
return htmlString
|
|
@@ -2293,7 +2313,7 @@ dmx.Component('ag-grid', {
|
|
|
2293
2313
|
.replace(/&/g, '&')
|
|
2294
2314
|
.replace(/"/g, '"')
|
|
2295
2315
|
.replace(/'/g, "'");
|
|
2296
|
-
}
|
|
2316
|
+
}
|
|
2297
2317
|
|
|
2298
2318
|
exportGridData = (currentGridInstance, currentGridConfig) => {
|
|
2299
2319
|
const exportConfig = currentGridConfig.context.exportConfig;
|
|
@@ -2961,6 +2981,7 @@ dmx.Component('ag-grid', {
|
|
|
2961
2981
|
`;
|
|
2962
2982
|
|
|
2963
2983
|
const paginationPanelStyle = document.createElement('style');
|
|
2984
|
+
if (cspNonce) paginationPanelStyle.setAttribute('nonce', cspNonce);
|
|
2964
2985
|
paginationPanelStyle.innerHTML = paginationPanelCss;
|
|
2965
2986
|
document.head.appendChild(paginationPanelStyle);
|
|
2966
2987
|
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<script src="./lib/version-banner.js" defer></script>
|
|
15
15
|
<script src="./lib/test-helpers.js" defer></script>
|
|
16
16
|
<script src="./lib/page-template.js" defer></script>
|
|
17
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
17
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
18
18
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
19
19
|
<script src="./data/sample-data.js" defer></script>
|
|
20
20
|
</head>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<script src="./lib/version-banner.js" defer></script>
|
|
18
18
|
<script src="./lib/test-helpers.js" defer></script>
|
|
19
19
|
<script src="./lib/page-template.js" defer></script>
|
|
20
|
-
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.js" defer></script>
|
|
20
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
21
21
|
<script src="../node_modules/@ag-grid-community/locale/dist/umd/@ag-grid-community/locale.min.js" defer></script>
|
|
22
22
|
<script src="../ru.js" defer></script>
|
|
23
23
|
<script src="../dmx-ag-grid.js" defer></script>
|
package/tests/03-editing.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<script src="./lib/version-banner.js" defer></script>
|
|
13
13
|
<script src="./lib/test-helpers.js" defer></script>
|
|
14
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>
|
|
15
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
16
16
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
17
17
|
<script src="./data/sample-data.js" defer></script>
|
|
18
18
|
</head>
|
package/tests/04-selection.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
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>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
</head>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<script src="./lib/version-banner.js" defer></script>
|
|
13
13
|
<script src="./lib/test-helpers.js" defer></script>
|
|
14
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>
|
|
15
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
16
16
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
17
17
|
<script src="./data/sample-data.js" defer></script>
|
|
18
18
|
</head>
|
package/tests/06-export.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
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>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../node_modules/pdfmake/build/pdfmake.min.js" defer></script>
|
|
16
16
|
<script src="../node_modules/pdfmake/build/vfs_fonts.js" defer></script>
|
|
17
17
|
<script src="../node_modules/exceljs/dist/exceljs.min.js" defer></script>
|
package/tests/07-import.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
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>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../node_modules/papaparse/papaparse.min.js" defer></script>
|
|
16
16
|
<script src="../node_modules/read-excel-file/bundle/read-excel-file.min.js" defer></script>
|
|
17
17
|
<script src="../dmx-ag-grid.js" defer></script>
|
package/tests/08-formatting.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
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>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
</head>
|
package/tests/09-grouping.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
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>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
</head>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
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>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
</head>
|
package/tests/11-layout.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
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>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
<style>
|
package/tests/12-methods.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
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>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
</head>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script src="./lib/version-banner.js" defer></script>
|
|
12
12
|
<script src="./lib/test-helpers.js" defer></script>
|
|
13
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>
|
|
14
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
15
15
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
16
16
|
<script src="./data/sample-data.js" defer></script>
|
|
17
17
|
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
<main class="test-main">
|
|
38
38
|
<dmx-test-data id="rows" source="employees"></dmx-test-data>
|
|
39
|
+
<dmx-test-data id="txns" source="transactions"></dmx-test-data>
|
|
39
40
|
|
|
40
41
|
<section class="feature">
|
|
41
42
|
<h2>Suppression flags + ci_sort + hide_filters/sort</h2>
|
|
@@ -238,6 +239,27 @@
|
|
|
238
239
|
</div>
|
|
239
240
|
</section>
|
|
240
241
|
|
|
242
|
+
<section class="feature">
|
|
243
|
+
<h2>Filters on JS-rendered columns (js_data_changes)</h2>
|
|
244
|
+
<p class="desc">
|
|
245
|
+
<code>monitoring_results</code> / <code>screening_results</code> are JSON lists rendered by
|
|
246
|
+
<code>js_data_changes</code> functions (one entry per line via <code><br></code>). Since 2.1.9 the
|
|
247
|
+
column filters, <code>applyFilters</code> and the quick filter match the rendered text, not the raw array.
|
|
248
|
+
Try typing <code>PAYOUT</code> in a floating filter or the quick filter box.
|
|
249
|
+
</p>
|
|
250
|
+
<input type="search" id="jsqf" placeholder="Quick filterโฆ" oninput="document.getElementById('jsFilterGrid').dmxComponent.get('gridInstance').setGridOption('quickFilterText', this.value)">
|
|
251
|
+
<div class="grid-wrap">
|
|
252
|
+
<dmx-ag-grid
|
|
253
|
+
id="jsFilterGrid"
|
|
254
|
+
dmx-bind:data="txns.value"
|
|
255
|
+
grid_theme="ag-theme-alpine"
|
|
256
|
+
quick_filter_field="jsqf"
|
|
257
|
+
dmx-bind:js_data_changes="[{field:'monitoring_results', function:'renderMonitoring'},{field:'screening_results', function:'renderScreening'}]"
|
|
258
|
+
dmx-bind:pagination="false">
|
|
259
|
+
</dmx-ag-grid>
|
|
260
|
+
</div>
|
|
261
|
+
</section>
|
|
262
|
+
|
|
241
263
|
<section class="feature">
|
|
242
264
|
<h2>Live rstyles assertions</h2>
|
|
243
265
|
<p class="desc">
|
|
@@ -250,6 +272,10 @@
|
|
|
250
272
|
</main>
|
|
251
273
|
|
|
252
274
|
<script>
|
|
275
|
+
// js_data_changes renderers for jsFilterGrid: JSON lists -> one entry per line.
|
|
276
|
+
window.renderMonitoring = data => (data.monitoring_results || []).map(r => '<b>' + r.rule + '</b> (' + r.score + ')').join('<br>');
|
|
277
|
+
window.renderScreening = data => (data.screening_results || []).map(r => r.list + ': ' + r.match).join('<br>');
|
|
278
|
+
|
|
253
279
|
// Helpers: count rows in a grid's DOM with a given background colour. Multiple matches
|
|
254
280
|
// are accepted (puppeteer normalises CSS to rgb()).
|
|
255
281
|
function bgCount(gridId, predicate) {
|
|
@@ -311,6 +337,10 @@
|
|
|
311
337
|
if (!employees.length) return setTimeout(runChecks, 200);
|
|
312
338
|
// Wait an extra cycle so the legacy-form re-render lands before we count rows.
|
|
313
339
|
if (!document.querySelector('#rsLegacy-grid .ag-row [col-id]')) return setTimeout(runChecks, 200);
|
|
340
|
+
// cellStyle lands a tick after the rows: wait for a styled salary cell (capped so a real
|
|
341
|
+
// regression still surfaces as a failed count rather than a page that never publishes).
|
|
342
|
+
runChecks.tries = (runChecks.tries || 0) + 1;
|
|
343
|
+
if (runChecks.tries < 25 && !document.querySelector('#csCell-grid [col-id="salary"][style*="color"]')) return setTimeout(runChecks, 200);
|
|
314
344
|
|
|
315
345
|
// Expected hand-counts derived from sample-data.js
|
|
316
346
|
const vipRows = employees.filter(r => r.rating >= 4.7 && r.active === true).length;
|
|
@@ -360,13 +390,47 @@
|
|
|
360
390
|
checks.cs_textHigh = { expect: highSalary, actual: csTextHigh };
|
|
361
391
|
checks.cs_cellLow = { expect: lowSalary, actual: csCellLow };
|
|
362
392
|
|
|
363
|
-
|
|
364
|
-
|
|
393
|
+
// js_data_changes filter checks (2.1.9): filters must match the rendered text, not the raw JSON array.
|
|
394
|
+
// The component's filter methods defer to dmx.nextTick, so this part is async and __rsCheck is
|
|
395
|
+
// published once it has run.
|
|
396
|
+
(async () => {
|
|
397
|
+
const txns = (window.SampleData && window.SampleData.transactions) || [];
|
|
398
|
+
const hasWord = (list, key, word) => (list || []).some(r => String(r[key]).includes(word));
|
|
399
|
+
const monPayout = txns.filter(r => hasWord(r.monitoring_results, 'rule', 'PAYOUT')).length;
|
|
400
|
+
const scrPayout = txns.filter(r => hasWord(r.screening_results, 'match', 'PAYOUT')).length;
|
|
401
|
+
const anyPayout = txns.filter(r => hasWord(r.monitoring_results, 'rule', 'PAYOUT') || hasWord(r.screening_results, 'match', 'PAYOUT')).length;
|
|
402
|
+
const el = document.getElementById('jsFilterGrid');
|
|
403
|
+
const comp = el && el.dmxComponent;
|
|
404
|
+
const gi = comp && comp.get('gridInstance');
|
|
405
|
+
const tick = () => new Promise(res => dmx.nextTick(res));
|
|
406
|
+
const call = async (name, arg) => { comp.methods[name].call(comp, arg); await tick(); await tick(); };
|
|
407
|
+
const visible = () => gi.getDisplayedRowCount();
|
|
408
|
+
if (gi) {
|
|
409
|
+
await call('applyFilters', { monitoring_results: { type: 'contains', filter: 'PAYOUT' } });
|
|
410
|
+
checks.jsFilter_monitoring = { expect: monPayout, actual: visible() };
|
|
411
|
+
await call('applyFilters', { screening_results: { type: 'contains', filter: 'PAYOUT' } });
|
|
412
|
+
checks.jsFilter_screening = { expect: scrPayout, actual: visible() };
|
|
413
|
+
await call('applyFilters', { monitoring_results: { type: 'contains', filter: 'NO-SUCH-RULE' } });
|
|
414
|
+
checks.jsFilter_noMatch = { expect: 0, actual: visible() };
|
|
415
|
+
await call('applyFilters', { id: { type: 'equals', filter: 9003 } });
|
|
416
|
+
checks.jsFilter_plainColumn = { expect: 1, actual: visible() };
|
|
417
|
+
await call('clearFilters');
|
|
418
|
+
gi.setGridOption('quickFilterText', 'payout');
|
|
419
|
+
checks.jsFilter_quick = { expect: anyPayout, actual: visible() };
|
|
420
|
+
gi.setGridOption('quickFilterText', '');
|
|
421
|
+
checks.jsFilter_cleared = { expect: txns.length, actual: visible() };
|
|
422
|
+
} else {
|
|
423
|
+
checks.jsFilter_gridReady = { expect: true, actual: false };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const failed = Object.entries(checks).filter(([_, v]) => v.actual !== v.expect);
|
|
427
|
+
window.__rsCheck = { checks, ok: failed.length === 0, failed: failed.map(([k]) => k) };
|
|
365
428
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
429
|
+
const box = document.getElementById('rs-check');
|
|
430
|
+
box.textContent = Object.entries(checks)
|
|
431
|
+
.map(([k, v]) => (v.actual === v.expect ? 'โ' : 'โ') + ' ' + k + ' โ expected ' + v.expect + ', got ' + v.actual)
|
|
432
|
+
.join('\n');
|
|
433
|
+
})();
|
|
370
434
|
}
|
|
371
435
|
runChecks();
|
|
372
436
|
</script>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<script src="./lib/version-banner.js" defer></script>
|
|
13
13
|
<script src="./lib/test-helpers.js" defer></script>
|
|
14
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>
|
|
15
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
16
16
|
<script src="../dmx-ag-grid.js" defer></script>
|
|
17
17
|
<script src="./data/sample-data.js" defer></script>
|
|
18
18
|
</head>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<!-- Strict policy: no 'unsafe-inline' anywhere. Every <style> AG Grid or the module injects must carry the nonce. -->
|
|
6
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'nonce-csp15'; style-src 'self' 'nonce-csp15'; img-src 'self' data:; font-src 'self' data:">
|
|
7
|
+
<meta name="csp-nonce" content="csp15">
|
|
8
|
+
<title>15 ยท CSP nonce</title>
|
|
9
|
+
<script nonce="csp15">
|
|
10
|
+
// Must be installed before any deferred script runs so every violation is counted.
|
|
11
|
+
window.__cspViolations = [];
|
|
12
|
+
document.addEventListener('securitypolicyviolation', e => window.__cspViolations.push(e.violatedDirective + ' ' + (e.sourceFile || '').split('/').pop() + ':' + e.lineNumber));
|
|
13
|
+
</script>
|
|
14
|
+
<link rel="stylesheet" href="./lib/test-suite.css">
|
|
15
|
+
<link rel="stylesheet" href="../node_modules/ag-grid-community/styles/ag-theme-alpine.css">
|
|
16
|
+
<link rel="stylesheet" href="../switch-toggle-slider.css">
|
|
17
|
+
<script src="./dmxAppConnect/dmxAppConnect.js" defer></script>
|
|
18
|
+
<script src="./lib/version-banner.js" defer></script>
|
|
19
|
+
<script src="./lib/test-helpers.js" defer></script>
|
|
20
|
+
<script src="./lib/page-template.js" defer></script>
|
|
21
|
+
<script src="../node_modules/ag-grid-community/dist/ag-grid-community.min.noStyle.js" defer></script>
|
|
22
|
+
<script src="../dmx-ag-grid.js" defer></script>
|
|
23
|
+
<script src="./data/sample-data.js" defer></script>
|
|
24
|
+
</head>
|
|
25
|
+
<body is="dmx-app" id="page15">
|
|
26
|
+
|
|
27
|
+
<script nonce="csp15">
|
|
28
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
29
|
+
TestPage.header({
|
|
30
|
+
title: '15 ยท CSP nonce',
|
|
31
|
+
summary: "Page served under style-src 'self' 'nonce-csp15' with no 'unsafe-inline'. The module reads the nonce from <meta name=\"csp-nonce\"> and passes it to AG Grid (styleNonce) and its own <style> blocks."
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<main class="test-main">
|
|
37
|
+
<dmx-test-data id="rows" source="employees"></dmx-test-data>
|
|
38
|
+
<dmx-test-data id="statusData" source="statusRows"></dmx-test-data>
|
|
39
|
+
|
|
40
|
+
<section class="feature">
|
|
41
|
+
<h2>Grid under a nonce-only style-src</h2>
|
|
42
|
+
<p class="desc">
|
|
43
|
+
Pagination, <code>vert_center_cell_data</code>, <code>fixed_horizontal_scroll</code> and the status toggle โ
|
|
44
|
+
every path where the module itself creates a <code><style></code>. The puppeteer runner reads
|
|
45
|
+
<code>window.__cspCheck</code>.
|
|
46
|
+
</p>
|
|
47
|
+
<div class="grid-wrap">
|
|
48
|
+
<dmx-ag-grid
|
|
49
|
+
id="cspGrid"
|
|
50
|
+
dmx-bind:data="rows.value"
|
|
51
|
+
grid_theme="ag-theme-alpine"
|
|
52
|
+
dmx-bind:pagination="true"
|
|
53
|
+
dmx-bind:pagination_page_size="10"
|
|
54
|
+
dmx-bind:vert_center_cell_data="true"
|
|
55
|
+
dmx-bind:fixed_horizontal_scroll="true">
|
|
56
|
+
</dmx-ag-grid>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="grid-wrap">
|
|
59
|
+
<dmx-ag-grid id="statusGrid" dmx-bind:data="statusData.value" grid_theme="ag-theme-alpine" dmx-bind:row_status_event="true" dmx-bind:pagination="false"></dmx-ag-grid>
|
|
60
|
+
</div>
|
|
61
|
+
<pre class="log" id="csp-check"></pre>
|
|
62
|
+
</section>
|
|
63
|
+
</main>
|
|
64
|
+
|
|
65
|
+
<script nonce="csp15">
|
|
66
|
+
function runCspChecks(tries) {
|
|
67
|
+
tries = tries || 0;
|
|
68
|
+
if (document.body.dataset.gridsReady !== 'true') {
|
|
69
|
+
if (tries > 50) return publish({ grids_ready: { expect: true, actual: false } });
|
|
70
|
+
return setTimeout(() => runCspChecks(tries + 1), 200);
|
|
71
|
+
}
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
const root = document.querySelector('#cspGrid-grid');
|
|
74
|
+
const wrap = root && root.querySelector('.ag-root-wrapper');
|
|
75
|
+
const cell = root && root.querySelector('.ag-cell');
|
|
76
|
+
const styles = [...document.querySelectorAll('style')];
|
|
77
|
+
publish({
|
|
78
|
+
csp_violations: { expect: 0, actual: window.__cspViolations.length },
|
|
79
|
+
style_elements_seen: { expect: true, actual: styles.length > 0 },
|
|
80
|
+
all_styles_nonced: { expect: 0, actual: styles.filter(s => s.nonce !== 'csp15').length },
|
|
81
|
+
layout_intact: { expect: 'flex', actual: wrap ? getComputedStyle(wrap).display : 'no-wrapper' },
|
|
82
|
+
vcenter_applied: { expect: 'flex', actual: cell ? getComputedStyle(cell).display : 'no-cell' },
|
|
83
|
+
hscroll_fixed: { expect: 'fixed', actual: (el => el ? getComputedStyle(el).position : 'none')(root && root.querySelector('.ag-body-horizontal-scroll')) }
|
|
84
|
+
});
|
|
85
|
+
if (window.__cspViolations.length) console.warn('CSP violations:', window.__cspViolations);
|
|
86
|
+
}, 400);
|
|
87
|
+
}
|
|
88
|
+
function publish(checks) {
|
|
89
|
+
const failed = Object.entries(checks).filter(([_, v]) => v.actual !== v.expect);
|
|
90
|
+
window.__cspCheck = { checks, ok: failed.length === 0, failed: failed.map(([k]) => k) };
|
|
91
|
+
document.getElementById('csp-check').textContent = Object.entries(checks)
|
|
92
|
+
.map(([k, v]) => (v.actual === v.expect ? 'โ' : 'โ') + ' ' + k + ' โ expected ' + v.expect + ', got ' + v.actual)
|
|
93
|
+
.join('\n');
|
|
94
|
+
}
|
|
95
|
+
runCspChecks();
|
|
96
|
+
</script>
|
|
97
|
+
</body>
|
|
98
|
+
</html>
|