Cover much more of the UI's JavaScript with tests - #2574
Conversation
Test the chart data cache and fetch, the replay, date range, data and chart data helpers, the page-free helpers in ui-utils.js, and the classic scripts flexmeasures.js and map-init.js. Move the asset tree's layout out of _macros.html into asset-tree.js, and the flex-context editor's logic out of assets/asset_context.html into flex-context-utils.js, so that they can be tested too. Both templates are touched by no open PR. Fix what the new tests found: - embed the asset tree, the sensors list and the flex-context on the context page with tojson, so a quote in a value no longer stops the flex-context editor from loading, and names are shown as text - import getSensor on the context page, so new commitment prices follow a price sensor's currency - highlight the current asset, and recognise the "Add asset" node, by id rather than by name - keep the plus sign of a UTC offset in encodeUrlQuery - translate Python representations properly in processResourceRawJSON, and accept objects Closes #2452 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: F.N. Claessen <felix@seita.nl>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: F.N. Claessen <felix@seita.nl>
Documentation build overview
4 files changed± changelog.html± genindex.html± _autosummary/flexmeasures.data.models.time_series.html± api/v3_0.html |
…page Embed data in scripts with tojson rather than as Python or JSON strings marked safe: the available units, the asset's flex-model on its properties page, and the chart options, whose download file name carries the asset's name. Escape names, units, graph titles and flex-model values wherever they are built into markup, with a shared escapeHtml in ui-utils.js, and drop Sensor._ui_unit, which handed the unit to templates as markup. Correct the UI guidelines, which recommended both patterns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: F.N. Claessen <felix@seita.nl>
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The changes are cohesive (test expansion + targeted refactors), include tests for the new modules and bugfixes, and the template safety adjustments are consistently applied in the touched areas.
Review effort: Lite
Findings: None
What changed in this PR
This PR substantially expands test coverage for the UI’s JavaScript (pytest-driven, headless-browser execution) and refactors previously inline template logic into importable JS modules so it can be unit-tested. It also includes several UI robustness/security fixes around embedding server data into scripts and rendering user-supplied strings as markup.
Changes:
- Add extensive JS test suite coverage for UI helpers, chart data logic, replay/date-range utilities, and classic scripts (
flexmeasures.js,map-init.js). - Refactor template-embedded JS into new modules (
asset-tree.js,flex-context-utils.js) and update templates to import/consume these modules. - Harden UI rendering and script embedding by using
tojson/HTML-safe JSON dumps and escaping user-controlled strings before inserting intoinnerHTML.
| File | Description |
|---|---|
| flexmeasures/ui/views/assets/views.py | Stop passing pre-serialized JSON/markup for flex-model and sensor unit; pass raw data for safer tojson usage in templates |
| flexmeasures/ui/utils/view_utils.py | Use HTML-safe JSON dumping for chart_options embedded into scripts (prevents script-breakout via asset names) |
| flexmeasures/ui/static/js/ui-utils.js | Add escapeHtml, unitHtml, robust pythonReprToJSON, and safer processResourceRawJSON; escape user-supplied values rendered via innerHTML |
| flexmeasures/ui/static/js/flex-context-utils.js | New testable module containing flex-context editor decision logic (field kinds, cleaning, currency inference, commitment parsing) |
| flexmeasures/ui/static/js/asset-tree.js | New testable module containing asset tree hierarchy + layout option construction for ECharts |
| flexmeasures/ui/static/js/daterange-utils.js | Fix encodeUrlQuery to preserve literal + (UTC offsets) when using URLSearchParams |
| flexmeasures/ui/templates/_macros.html | Move asset tree layout logic out of template into asset-tree.js; switch to tojson; highlight by asset id |
| flexmeasures/ui/templates/assets/asset_context.html | Embed sensors/flex-context safely with tojson; render sensor names/values via textContent; import flex-context utilities |
| flexmeasures/ui/templates/assets/asset_properties.html | Replace unsafe JSON parsing pattern with tojson; pass flex-model object; escape unit option HTML |
| flexmeasures/ui/templates/assets/asset_graph.html | Escape user-supplied names/labels; use unitHtml; avoid inserting raw values into markup |
| flexmeasures/ui/templates/includes/graphs.html | Escape KPI titles/values/units used in HTML templates |
| flexmeasures/ui/templates/sensors/index.html | Remove `_ui_unit |
| flexmeasures/data/models/time_series.py | Remove Sensor._ui_unit (UI now handles dimensionless rendering safely) |
| flexmeasures/ui/tests/js/* | Add many new JS test modules covering the refactored/new JS code paths |
| flexmeasures/ui/tests/js/README.md | Document how classic scripts are tested and when logic must be moved into modules |
| documentation/changelog.rst | Add changelog items for expanded JS testing and multiple UI bug/security fixes |
| .github/instructions/ui-terminology.instructions.md | Update UI/template safety guidance to prefer tojson and escaping/textContent patterns |
| .github/agents/ui-specialist.md | Align UI specialist checklist with the updated template safety guidance |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: F.N. Claessen <felix@seita.nl>
There was a problem hiding this comment.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Note
This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.
This branch added a module-local escapeHtml for the copy toast, and #2574 added an exported one to the same module. The merge kept both, so ui-utils.js declared the name twice and stopped parsing, taking every JavaScript test that imports it down with it: 17 failures, red on all three Python versions. This drops the copy added here and leaves main's exported one in its place, which escapes quotes as well and is the stricter of the two. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Description
#2435 made the UI's JavaScript testable. This puts that to use: the JavaScript tests go from 32 to 102, and two pieces of logic move out of templates into modules so they can be tested too.
ui/tests/js: cover the chart data cache and fetch, the replay, date range, data and chart data helpers, the page-free helpers inui-utils.js, and the classic scriptsflexmeasures.jsandmap-init.jsui/static: move the asset tree's layout from_macros.htmlintoasset-tree.js, and the flex-context editor's logic fromassets/asset_context.htmlintoflex-context-utils.js, and cover bothui: fix the bugs the new tests found (below)ui: embed data in scripts withtojson, and escape user-supplied text built into markup, on every asset and sensor pagedocumentation/changelog.rstCloses #2452
What is now covered
chart-data-cache.jsmissingRanges,effectiveResolutionMs,onSameResamplingGrid,clipToWindow,dedupeRecords, andcreateChartDataCacheagainst a stand-infetch(stepping, narrowing, widening, adjacent windows, expiry, off-grid windows)chart-data-fetch.jsreplay-utils.jsdaterange-utils.jsthisMonth,lastNMonths,getOffsetBetweenTimezonesForDate,computeSimulationRanges(incl. across DST),encodeUrlQuerydata-utils.jsconvertToCSV(timestamps, horizons, nested objects)chart-data-utils.jscheckDSTTransitions,checkSourceMasking,checkStrictYAxisRangesui-utils.jsmoveArrayItem,flattenErrorPayload,extractApiErrorMessage,createReactiveState,convertHtmlToElement,processResourceRawJSON, the cachedgetAsset/getSensor/getAccount,pollJobStatusflexmeasures.jsnumberWithCommas, currency symbols,getTimeAgo,getHumanFriendlyDeltaOrTimeStr(past moments),humanizeIsoDuration,unpackData,getLatestBeliefName,sourceIdFromKey, thetimezoneFormatVega formattermap-init.jscomputeCenteredTreeLayoutasset-tree.js(new)flex-context-utils.js(new)flexmeasures.jsandmap-init.jsare classic scripts, so their tests put stand-ins for jQuery, Vega or Leaflet onwindowand load them with a<script>element. The README says how.Refactoring kept away from open PRs
The two templates refactored here are touched by no open PR. The inline code moved nearly verbatim; the templates keep the DOM and ECharts wiring, and import the rest.
Everything else only gains tests, apart from two contained fixes in
daterange-utils.jsandui-utils.js, whose hunks are far from those of #2531 and #2554. The new test file forui-utils.jsis calledtest_ui_utils_helpers.py, because #2531 adds atest_ui_utils.py. There are no tests of howgetHumanFriendlyDeltaOrTimeStrphrases future moments, because #2554 rephrases them.Bugs found
The flex-context editor did not load when a flex-context value held a double quote, such as a commitment named
Big "flex" deal. The template embedded the flex-context as its Python representation inside a JavaScript string literal, which the quote ended early. It now embeds it withtojson. The tree and the sensors list on the same page are now embedded withtojsontoo, instead of as Python representations, and the sensors modal shows sensor names as text.New commitment prices always defaulted to EUR when the flex-context priced through sensors.
activeCurrency()looked up the price sensor's unit withgetSensor, which the page never imported. TheReferenceErrorwas caught by thetryaround the lookup, so the page fell back to EUR without a word. Checked in a browser against the rendered page: with aUSD/MWhprice sensor,mainoffers0 EUR/MWh, and this branch0 USD/MWh.The asset tree highlighted assets by name. A sub-asset named like the asset whose page it is was highlighted too. Asset names are only unique among siblings, so that can happen. An asset named "Add asset" was also drawn as the green button. Both are now decided by id.
encodeUrlQueryturned the+of a UTC offset into a space, contrary to its docstring:URLSearchParamsreads a literal+as an encoded space. Nothing visible broke, becauseAwareDateTimeFieldturns spaces back into plus signs on the server, but the function now does what it says.processResourceRawJSONmangled values. It turned Python's representation of a dict into JSON with a blanket replace.'Nonetheless'became'nulltheless', and an apostrophe in a value madeJSON.parsethrow. It now translates the representation properly, and also accepts an already-parsed object, whichasset_context.htmlnow passes.Text typed by users was embedded or rendered as markup elsewhere too. The same fixes now apply on the other asset and sensor pages.
available_unitsand the properties page's flex-model are embedded withtojson, where they were a Python or JSON string markedsafeinside a string literal.chart_options, whose download file name carries the asset's name, is now dumped HTML-safe. Names, units, graph titles and flex-model values built into markup go through a newescapeHtmlinui-utils.js, inrenderSensorandrenderSensorSearchResults, in the sensor cards and plot headers ofasset_graph.html, and in the KPI cards ofgraphs.html. The sensor page renders the "dimensionless" hint in the template, soSensor._ui_unitis gone. It was private and handed the unit to templates as markup.These are one-line changes in templates that open PRs also touch (#2490, #2267, #2570), so they may need a trivial rebase there.
The UI guidelines in
.github/recommended both patterns,sensor._ui_unit | safeand parsingavailable_units | safeafter swapping quotes. They now ask fortojsonin scripts, andtextContentorescapeHtmlin JavaScript.I checked each page in headless Chrome, rendered by the app with names, a unit, a graph title and flex-context and flex-model values seeded with quotes and markup. On
main, those values stop the scripts on the context and graphs pages, and are rendered as markup on all four pages. On this branch all four pages show them as typed, and none of them logs a new console error.Potential follow-ups
Inline JavaScript left in templates that open PRs touch, and so not moved here:
includes/graphs.htmlassets/asset_graph.htmlassets/asset_properties.htmlassets/asset_new.htmlassets/asset_automations.htmlsensors/index.htmlsensors/status.htmlaccounts/account.htmlusers/user.htmlThe untouched templates with inline JavaScript left (
dashboard.html,users/users.html,includes/toasts.htmland a few smaller ones) are mostly DataTables and DOM wiring, with little logic to test.How to test
102 tests. They skip without selenium or Chrome, as before.
To see the page-level fixes, open the context page of an asset whose flex-context has a commitment with a
"in its name. Onmainthe editor stays empty.🤖 Generated with Claude Code