From 3195f9abbbd2cd106a44c86ea6c8c1742b1cd6d0 Mon Sep 17 00:00:00 2001 From: Daniel Kostro Date: Tue, 24 Feb 2026 18:22:59 +0100 Subject: [PATCH 1/5] fix: prefilter html but warn about invalid html Closes: https://github.com/NPellet/visualizer/issues/1210 --- .github/workflows/nodejs.yml | 1 - package-lock.json | 1 + package.json | 4 +- src/modules/module.js | 4 +- src/src/main/entrypoint.js | 15 ++ src/src/util/jquery_prefilter.js | 28 ++ testcase/data/twig-form/view.json | 423 ++++++++++++++++++++++++------ tests/jquery_prefilter.test.js | 76 ++++++ tests/require_amd.js | 18 ++ 9 files changed, 481 insertions(+), 89 deletions(-) create mode 100644 src/src/util/jquery_prefilter.js create mode 100644 tests/jquery_prefilter.test.js create mode 100644 tests/require_amd.js diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 184e90b895..974bcac988 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -12,5 +12,4 @@ jobs: uses: zakodium/workflows/.github/workflows/nodejs.yml@nodejs-v1 with: lint-check-types: false - disable-tests: true disable-test-package: true diff --git a/package-lock.json b/package-lock.json index 32c32b39a0..a58f2e5812 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,6 +70,7 @@ "lodash": "^4.17.23", "mkpath": "^1.0.0", "prettier": "^3.8.1", + "requirejs": "^2.3.8", "rollup": "^4.59.0", "rollup-plugin-polyfill-node": "^0.13.0", "tempfile": "^3.0.0", diff --git a/package.json b/package.json index 1dd5bfa6d0..0b03d658ce 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "prerelease": "grunt bump:prerelease", "prettier": "prettier --check src", "prettier-write": "prettier --write src", - "test": "npm run eslint && npm run prettier", + "test": "npm run eslint && npm run prettier && npm run test-only", + "test-only": "node --test tests/**", "release:minor": "npm run test && grunt bump:minor --release", "release:patch": "npm run test && grunt bump:patch --release" }, @@ -61,6 +62,7 @@ "lodash": "^4.17.23", "mkpath": "^1.0.0", "prettier": "^3.8.1", + "requirejs": "^2.3.8", "rollup": "^4.59.0", "rollup-plugin-polyfill-node": "^0.13.0", "tempfile": "^3.0.0", diff --git a/src/modules/module.js b/src/modules/module.js index 724fc1ff15..9721f66df2 100644 --- a/src/modules/module.js +++ b/src/modules/module.js @@ -221,10 +221,10 @@ define([ toolbar[i].title || '' }">`; if (toolbar[i].icon) { - html += `
`; + html += `
`; } if (toolbar[i].cssClass) { - html += ``; + html += ``; } html += ''; } diff --git a/src/src/main/entrypoint.js b/src/src/main/entrypoint.js index 5e8612178e..dac1e0ae1e 100644 --- a/src/src/main/entrypoint.js +++ b/src/src/main/entrypoint.js @@ -3,6 +3,7 @@ define([ 'jquery', 'lodash', + 'src/util/jquery_prefilter', 'src/header/header', 'src/util/repository', 'src/main/grid', @@ -25,6 +26,7 @@ define([ ], function ( $, _, + jqueryPrefilter, Header, Repository, Grid, @@ -42,6 +44,19 @@ define([ Config, Sandbox, ) { + jQuery.htmlPrefilter = (preHtml) => { + const { warn, html } = jqueryPrefilter(preHtml); + if (warn) { + console.warn( + 'jQuery.htmlPrefilter modified invalid html, make sure to update your html markup', + { + before: preHtml, + after: html, + }, + ); + } + return html; + }; var _viewLoaded, _dataLoaded, _modulesSet; var RepositoryData = new Repository(), diff --git a/src/src/util/jquery_prefilter.js b/src/src/util/jquery_prefilter.js new file mode 100644 index 0000000000..d1f4481cf5 --- /dev/null +++ b/src/src/util/jquery_prefilter.js @@ -0,0 +1,28 @@ +'use strict'; + +define(() => { + // https://jquery.com/upgrade-guide/3.5/ + const rxhtmlTag = + /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi; + + return function jqueryPrefilter(html) { + // Self-closing tags are invalid except for void elements like input + const filteredHtml = html.replace(rxhtmlTag, '<$1>'); + if (filteredHtml !== html) { + // Ignore svg content because it is actually XML + const cleanedHtml = html.replaceAll(/]*>(.*?)<\/svg>/g, '').trim(); + + const result = rxhtmlTag.exec(cleanedHtml); + if (cleanedHtml && result?.[0] !== cleanedHtml) { + return { + html: filteredHtml, + warn: true, + }; + } + } + return { + html, + warn: false, + }; + }; +}); diff --git a/testcase/data/twig-form/view.json b/testcase/data/twig-form/view.json index 8ced6829ff..bac92cd340 100644 --- a/testcase/data/twig-form/view.json +++ b/testcase/data/twig-form/view.json @@ -1,5 +1,5 @@ { - "version": "2.111.2-0", + "version": "3.1.6", "grid": { "layers": { "Default layer": { @@ -17,14 +17,36 @@ "groups": { "group": [ { - "mode": ["html"], - "outputType": [null], - "btnvalue": ["Send script"], - "iseditable": [["editable"]], - "hasButton": [["button"]], - "variable": [[]], - "storeOnChange": [["store"]], - "debouncing": [250], + "mode": [ + "html" + ], + "outputType": [ + null + ], + "btnvalue": [ + "Send script" + ], + "iseditable": [ + [ + "editable" + ] + ], + "hasButton": [ + [ + "button" + ] + ], + "variable": [ + [] + ], + "storeOnChange": [ + [ + "store" + ] + ], + "debouncing": [ + 250 + ], "script": [ "\n\n
\n

My book

\n \n Title: \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
KindFirstnameLastnameNationalities
\n \n \n \n \n \n \n \n \n \n \n
\n \n
\n
\n \n

Keywords

\n \n \n \n \n
\n \n
\n\n

Parameters

\n \n \n \n \n \n
\n \n \n \n
\n
\n\n\n\n" ] @@ -32,8 +54,14 @@ ], "ace": [ { - "useSoftTabs": [["yes"]], - "tabSize": [4] + "useSoftTabs": [ + [ + "yes" + ] + ], + "tabSize": [ + 4 + ] } ] } @@ -51,15 +79,24 @@ "zIndex": 0, "display": true, "title": "", - "bgColor": [255, 255, 255, 0], + "bgColor": [ + 255, + 255, + 255, + 0 + ], "wrapper": true, "created": true, "name": "Default layer" } }, "id": 1, - "vars_in": [{}], - "actions_in": [{}], + "vars_in": [ + {} + ], + "actions_in": [ + {} + ], "vars_out": [ { "event": "onButtonClick", @@ -81,20 +118,33 @@ "icon": "", "action": "", "position": "begin", - "color": [100, 100, 100, 1] + "color": [ + 100, + 100, + 100, + 1 + ] } ] ], "common": [ { - "toolbar": [["Open Preferences"]] + "toolbar": [ + [ + "Open Preferences" + ] + ] } ] }, "css": [ { - "fontSize": [""], - "fontFamily": [""] + "fontSize": [ + "" + ], + "fontFamily": [ + "" + ] } ], "title": "" @@ -106,14 +156,32 @@ "groups": { "group": [ { - "editable": ["text"], - "expanded": [[]], - "storeObject": [[]], - "displayValue": [[]], - "searchBox": [["search"]], - "sendButton": [[]], - "output": ["new"], - "storedObject": ["{}"] + "editable": [ + "text" + ], + "expanded": [ + [] + ], + "storeObject": [ + [] + ], + "displayValue": [ + [] + ], + "searchBox": [ + [ + "search" + ] + ], + "sendButton": [ + [] + ], + "output": [ + "new" + ], + "storedObject": [ + "{}" + ] } ] } @@ -131,7 +199,12 @@ "zIndex": 0, "display": true, "title": "", - "bgColor": [255, 255, 255, 0], + "bgColor": [ + 255, + 255, + 255, + 0 + ], "wrapper": true, "created": true, "name": "Default layer" @@ -144,7 +217,9 @@ "name": "data" } ], - "actions_in": [{}], + "actions_in": [ + {} + ], "vars_out": [ { "jpath": [] @@ -168,14 +243,22 @@ ], "common": [ { - "toolbar": [["Open Preferences"]] + "toolbar": [ + [ + "Open Preferences" + ] + ] } ] }, "css": [ { - "fontSize": [""], - "fontFamily": [""] + "fontSize": [ + "" + ], + "fontFamily": [ + "" + ] } ], "title": "" @@ -187,11 +270,23 @@ "groups": { "group": [ { - "selectable": [[]], - "template": [""], - "modifyInForm": [["yes"]], - "debouncing": [100], - "formOptions": [[]] + "selectable": [ + [] + ], + "template": [ + "" + ], + "modifyInForm": [ + [ + "yes" + ] + ], + "debouncing": [ + 100 + ], + "formOptions": [ + [] + ] } ] } @@ -209,7 +304,12 @@ "zIndex": 0, "display": true, "title": "", - "bgColor": [255, 255, 255, 0], + "bgColor": [ + 255, + 255, + 255, + 0 + ], "wrapper": true, "created": true, "name": "Default layer" @@ -230,7 +330,9 @@ "name": "kinds" } ], - "actions_in": [{}], + "actions_in": [ + {} + ], "vars_out": [ { "jpath": [] @@ -249,20 +351,33 @@ "icon": "", "action": "", "position": "begin", - "color": [100, 100, 100, 1] + "color": [ + 100, + 100, + 100, + 1 + ] } ] ], "common": [ { - "toolbar": [["Open Preferences"]] + "toolbar": [ + [ + "Open Preferences" + ] + ] } ] }, "css": [ { - "fontSize": [""], - "fontFamily": [""] + "fontSize": [ + "" + ], + "fontFamily": [ + "" + ] } ], "title": "" @@ -274,15 +389,32 @@ "groups": { "group": [ { - "display": [["editor", "buttons"]], - "execOnLoad": [["yes"]], - "asyncAwait": [["top"]], + "display": [ + [ + "editor", + "buttons" + ] + ], + "execOnLoad": [ + [ + "yes" + ] + ], + "asyncAwait": [ + [ + "top" + ] + ], "script": [ "API.createData('data', {\n title:'abc',\n authors:[\n {firstname:'ab', lastname:'cd', nationalities: ['belgian']},\n {firstname:'gh', lastname:'ij', nationalities: []},\n {firstname:'kl', lastname:'mn'},\n {firstname:'op', lastname:'qr', nationalities: ['belgian','swiss']},\n ],\n info: {\n keywords:['kwd1','kw2'],\n parameters: [\n {\n \"description\": \"asdf\",\n \"value\": \"asdf\"\n },\n {\n \"description\": \"fds\",\n \"value\": \"asdff\"\n }\n ]\n }\n});\nwindow.setTimeout( () => API.getData('template').triggerChange() );\n" ] } ], - "libs": [[{}]], + "libs": [ + [ + {} + ] + ], "buttons": [ [ { @@ -308,15 +440,24 @@ "zIndex": 0, "display": true, "title": "", - "bgColor": [255, 255, 255, 0], + "bgColor": [ + 255, + 255, + 255, + 0 + ], "wrapper": true, "created": true, "name": "Default layer" } }, "id": 4, - "vars_in": [{}], - "actions_in": [{}], + "vars_in": [ + {} + ], + "actions_in": [ + {} + ], "vars_out": [ { "jpath": [] @@ -335,20 +476,33 @@ "icon": "", "action": "", "position": "begin", - "color": [100, 100, 100, 1] + "color": [ + 100, + 100, + 100, + 1 + ] } ] ], "common": [ { - "toolbar": [["Open Preferences"]] + "toolbar": [ + [ + "Open Preferences" + ] + ] } ] }, "css": [ { - "fontSize": [""], - "fontFamily": [""] + "fontSize": [ + "" + ], + "fontFamily": [ + "" + ] } ], "title": "" @@ -360,15 +514,30 @@ "groups": { "group": [ { - "display": [["editor", "buttons"]], - "execOnLoad": [[]], - "asyncAwait": [["top"]], + "display": [ + [ + "editor", + "buttons" + ] + ], + "execOnLoad": [ + [] + ], + "asyncAwait": [ + [ + "top" + ] + ], "script": [ "let data=API.getData('data');\nfor (let key of Object.keys(data)) {\n data[key]=undefined;\n}\ndata.triggerChange();\nAPI.getData('kinds').triggerChange();\nwindow.setTimeout( () => API.getData('template').triggerChange() );\n" ] } ], - "libs": [[{}]], + "libs": [ + [ + {} + ] + ], "buttons": [ [ { @@ -393,15 +562,24 @@ "zIndex": 0, "display": true, "title": "", - "bgColor": [255, 255, 255, 0], + "bgColor": [ + 255, + 255, + 255, + 0 + ], "wrapper": true, "created": true, "name": "Default layer" } }, "id": 5, - "vars_in": [{}], - "actions_in": [{}], + "vars_in": [ + {} + ], + "actions_in": [ + {} + ], "vars_out": [ { "jpath": [] @@ -425,14 +603,22 @@ ], "common": [ { - "toolbar": [["Open Preferences"]] + "toolbar": [ + [ + "Open Preferences" + ] + ] } ] }, "css": [ { - "fontSize": [""], - "fontFamily": [""] + "fontSize": [ + "" + ], + "fontFamily": [ + "" + ] } ], "title": "" @@ -444,15 +630,32 @@ "groups": { "group": [ { - "display": [["editor", "buttons"]], - "execOnLoad": [["yes"]], - "asyncAwait": [["top"]], + "display": [ + [ + "editor", + "buttons" + ] + ], + "execOnLoad": [ + [ + "yes" + ] + ], + "asyncAwait": [ + [ + "top" + ] + ], "script": [ "let kinds=[\"Author\",\"Editor\",\"Test\"];\nAPI.createData('kinds', kinds);\n" ] } ], - "libs": [[{}]], + "libs": [ + [ + {} + ] + ], "buttons": [ [ { @@ -478,15 +681,24 @@ "zIndex": 0, "display": true, "title": "", - "bgColor": [255, 255, 255, 0], + "bgColor": [ + 255, + 255, + 255, + 0 + ], "wrapper": true, "created": true, "name": "Default layer" } }, "id": 6, - "vars_in": [{}], - "actions_in": [{}], + "vars_in": [ + {} + ], + "actions_in": [ + {} + ], "vars_out": [ { "jpath": [] @@ -505,20 +717,33 @@ "icon": "", "action": "", "position": "begin", - "color": [100, 100, 100, 1] + "color": [ + 100, + 100, + 100, + 1 + ] } ] ], "common": [ { - "toolbar": [["Open Preferences"]] + "toolbar": [ + [ + "Open Preferences" + ] + ] } ] }, "css": [ { - "fontSize": [""], - "fontFamily": [""] + "fontSize": [ + "" + ], + "fontFamily": [ + "" + ] } ], "title": "" @@ -526,12 +751,14 @@ ], "variables": [ { - "jpath": [""] + "jpath": [ + "" + ] } ], "aliases": [ { - "path": "https://www.lactame.com/github/cheminfo-js/visualizer-helper/fede69d611616d26d5f70a4fc053eceac6612b68", + "path": "https://www.lactame.com/github/cheminfo-js/visualizer-helper/37fdbb1f1de6c5153451f030cf9721e885752ce3", "alias": "vh" }, { @@ -548,8 +775,12 @@ "groups": { "action": [ { - "name": [null], - "script": [null] + "name": [ + null + ], + "script": [ + null + ] } ] } @@ -561,7 +792,9 @@ "groups": { "general": [ { - "script": ["API.createData('data', {});\n"] + "script": [ + "API.createData('data', {});\n" + ] } ] } @@ -574,7 +807,11 @@ { "sections": {}, "groups": { - "modules": [[{}]] + "modules": [ + [ + {} + ] + ] } } ], @@ -582,7 +819,11 @@ { "sections": {}, "groups": { - "filters": [[{}]] + "filters": [ + [ + {} + ] + ] } } ], @@ -592,11 +833,19 @@ "groups": { "filter": [ { - "name": [null], - "script": [null] + "name": [ + null + ], + "script": [ + null + ] } ], - "libs": [[{}]] + "libs": [ + [ + {} + ] + ] } } ] @@ -608,7 +857,11 @@ { "sections": {}, "groups": { - "action": [[{}]] + "action": [ + [ + {} + ] + ] } } ] diff --git a/tests/jquery_prefilter.test.js b/tests/jquery_prefilter.test.js new file mode 100644 index 0000000000..a6df0b7f78 --- /dev/null +++ b/tests/jquery_prefilter.test.js @@ -0,0 +1,76 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; +import { requireAmd } from './require_amd.js'; + +const jqueryPrefilter = await requireAmd('src/util/jquery_prefilter'); + +function assertFilter(before, after, shouldWarn) { + const { warn, html } = jqueryPrefilter(before); + assert.equal(html, after); + assert( + warn === shouldWarn, + `${before} is expected ${shouldWarn ? 'to' : 'not to'} produce a warning`, + ); +} +describe('jquery prefilter', async () => { + it('closes non-void elements but does not touch void elements', () => { + assertFilter( + `
`, + true, + ); + }); + + it('ignores single elements without children', () => { + assertFilter(`
`, `
`, false); + assertFilter(`
`, `
`, false); + assertFilter(``, ``, false); + assertFilter(` `, ` `, false); + }); + + it('ignores svg content when isolated', () => { + assertFilter(``, ``, false); + assertFilter( + ``, + ``, + false, + ); + assertFilter(`
`, `
`, false); + }); + + it('transforms svg content when other tags need to be transformed', () => { + assertFilter( + `
`, + `
`, + true, + ); + }); + + it('handles multi-line html', () => { + assertFilter( + ` +
+ + `, + ` +
+ + `, + true, + ); + + assertFilter( + ` +
+ + + `, + ` +
+ + + `, + true, + ); + }); +}); diff --git a/tests/require_amd.js b/tests/require_amd.js new file mode 100644 index 0000000000..fdd0ca2c1d --- /dev/null +++ b/tests/require_amd.js @@ -0,0 +1,18 @@ +import requirejs from 'requirejs'; +requirejs.config({ + baseUrl: new URL('../src/', import.meta.url).pathname, +}); + +export function requireAmd(deps) { + if (typeof deps === 'string') { + return new Promise((resolve, reject) => { + requirejs([deps], resolve, reject); + }); + } else if (Array.isArray(deps)) { + return new Promise((resolve, reject) => { + requirejs(deps, (...args) => resolve(args), reject); + }); + } else { + throw new Error(`Unexpected amd dependency: ${deps}`); + } +} From ee62ae5ca76805400a860e922686bd577977e0e9 Mon Sep 17 00:00:00 2001 From: Daniel Kostro Date: Wed, 25 Feb 2026 09:02:36 +0100 Subject: [PATCH 2/5] chore: fix eslint errors --- src/src/util/jquery_prefilter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src/util/jquery_prefilter.js b/src/src/util/jquery_prefilter.js index d1f4481cf5..6d95ad2965 100644 --- a/src/src/util/jquery_prefilter.js +++ b/src/src/util/jquery_prefilter.js @@ -3,11 +3,11 @@ define(() => { // https://jquery.com/upgrade-guide/3.5/ const rxhtmlTag = - /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi; + /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^/\0>\u0020\t\r\n\f]*)[^>]*)\/>/gi; return function jqueryPrefilter(html) { // Self-closing tags are invalid except for void elements like input - const filteredHtml = html.replace(rxhtmlTag, '<$1>'); + const filteredHtml = html.replaceAll(rxhtmlTag, '<$1>'); if (filteredHtml !== html) { // Ignore svg content because it is actually XML const cleanedHtml = html.replaceAll(/]*>(.*?)<\/svg>/g, '').trim(); From 85919495e857f282ef34eebae9eaec00105c3d07 Mon Sep 17 00:00:00 2001 From: Daniel Kostro Date: Wed, 25 Feb 2026 09:04:42 +0100 Subject: [PATCH 3/5] chore: update nodejs-ci config --- .github/workflows/nodejs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 974bcac988..0e09ee0143 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -13,3 +13,5 @@ jobs: with: lint-check-types: false disable-test-package: true + upload-coverage: false + node-version-matrix: '[24]' From 2672c44d18dd75551a5d3c31cd7d260021703b60 Mon Sep 17 00:00:00 2001 From: Daniel Kostro Date: Wed, 25 Feb 2026 09:09:19 +0100 Subject: [PATCH 4/5] chore: remove console.log eslint error --- src/src/main/entrypoint.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/src/main/entrypoint.js b/src/src/main/entrypoint.js index dac1e0ae1e..90891beea0 100644 --- a/src/src/main/entrypoint.js +++ b/src/src/main/entrypoint.js @@ -47,6 +47,7 @@ define([ jQuery.htmlPrefilter = (preHtml) => { const { warn, html } = jqueryPrefilter(preHtml); if (warn) { + // eslint-disable-next-line no-console console.warn( 'jQuery.htmlPrefilter modified invalid html, make sure to update your html markup', { From 2c0cd9490aeb5ec01a8e55ca39436375f27fc871 Mon Sep 17 00:00:00 2001 From: Daniel Kostro Date: Wed, 25 Feb 2026 15:42:39 +0100 Subject: [PATCH 5/5] fix: add testcase and fix edge case --- package-lock.json | 18 ++++++ package.json | 1 + src/src/util/jquery_prefilter.js | 2 +- tests/jquery_prefilter.test.js | 95 +++++++++++++++++--------------- 4 files changed, 71 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index a58f2e5812..a3de81cb76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,6 +53,7 @@ "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", + "@types/node": "^24.10.13", "add-stream": "^1.0.0", "bower": "^1.8.14", "conventional-changelog": "^6.0.0", @@ -2450,6 +2451,16 @@ "@types/lodash": "*" } }, + "node_modules/@types/node": { + "version": "24.10.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", + "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -11215,6 +11226,13 @@ "node": "*" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", diff --git a/package.json b/package.json index 0b03d658ce..967a17adc8 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", + "@types/node": "^24.10.13", "add-stream": "^1.0.0", "bower": "^1.8.14", "conventional-changelog": "^6.0.0", diff --git a/src/src/util/jquery_prefilter.js b/src/src/util/jquery_prefilter.js index 6d95ad2965..2ac5f1fd0d 100644 --- a/src/src/util/jquery_prefilter.js +++ b/src/src/util/jquery_prefilter.js @@ -13,7 +13,7 @@ define(() => { const cleanedHtml = html.replaceAll(/]*>(.*?)<\/svg>/g, '').trim(); const result = rxhtmlTag.exec(cleanedHtml); - if (cleanedHtml && result?.[0] !== cleanedHtml) { + if (cleanedHtml && result && result[0] !== cleanedHtml) { return { html: filteredHtml, warn: true, diff --git a/tests/jquery_prefilter.test.js b/tests/jquery_prefilter.test.js index a6df0b7f78..d460a693b6 100644 --- a/tests/jquery_prefilter.test.js +++ b/tests/jquery_prefilter.test.js @@ -1,5 +1,6 @@ import assert from 'node:assert'; -import { describe, it } from 'node:test'; +import { it } from 'node:test'; + import { requireAmd } from './require_amd.js'; const jqueryPrefilter = await requireAmd('src/util/jquery_prefilter'); @@ -7,70 +8,76 @@ const jqueryPrefilter = await requireAmd('src/util/jquery_prefilter'); function assertFilter(before, after, shouldWarn) { const { warn, html } = jqueryPrefilter(before); assert.equal(html, after); - assert( + assert.ok( warn === shouldWarn, `${before} is expected ${shouldWarn ? 'to' : 'not to'} produce a warning`, ); } -describe('jquery prefilter', async () => { - it('closes non-void elements but does not touch void elements', () => { - assertFilter( - `
`, - true, - ); - }); +it('closes non-void elements but does not touch void elements', () => { + assertFilter( + `
`, + true, + ); +}); - it('ignores single elements without children', () => { - assertFilter(`
`, `
`, false); - assertFilter(`
`, `
`, false); - assertFilter(``, ``, false); - assertFilter(` `, ` `, false); - }); +it('ignores single elements without children', () => { + assertFilter(`
`, `
`, false); + assertFilter(`
`, `
`, false); + assertFilter(``, ``, false); + assertFilter(` `, ` `, false); +}); - it('ignores svg content when isolated', () => { - assertFilter(``, ``, false); - assertFilter( - ``, - ``, - false, - ); - assertFilter(`
`, `
`, false); - }); +it('ignores svg content when isolated', () => { + assertFilter(``, ``, false); + assertFilter( + ``, + ``, + false, + ); + assertFilter(`
`, `
`, false); +}); - it('transforms svg content when other tags need to be transformed', () => { - assertFilter( - `
`, - `
`, - true, - ); - }); +it('ignores svg when there are no issues outside of it', () => { + assertFilter( + `
`, + `
`, + false, + ); +}); + +it('transforms svg content when other tags need to be transformed', () => { + assertFilter( + `
`, + `
`, + true, + ); +}); - it('handles multi-line html', () => { - assertFilter( - ` +it('handles multi-line html', () => { + assertFilter( + `
`, - ` + `
`, - true, - ); + true, + ); - assertFilter( - ` + assertFilter( + `
`, - ` + `
`, - true, - ); - }); + true, + ); });