Files
meshcore-analyzer/scripts/non-unit-tests.json
T
efitenandClaude Opus 5.5 d43f3c3777 feat(theme): ?theme=dark|light URL parameter for one page load (#2236) (#2251)
Fixes #2236.

A display that can only be given a URL (digital signage, an iframe
without cookies or login) had no way to choose dark or light. This adds
`theme=dark` or `theme=light` to the hash query, next to `embed=1`:

```
https://<instance>/#/map?embed=1&theme=dark
```

## What changes

**`public/index.html`, the pre-paint script**
- Reads `theme` from the hash query with `URLSearchParams`, as
`shouldEmbedRoute` does for `embed`. Only `dark` and `light` count;
anything else is ignored.
- A valid value wins over the saved choice and the OS preference, and is
set before first paint, so there is no light flash.
- The value is handed to `app.js` as `window.__themeOverride`.
- When `localStorage` throws (a sandboxed iframe), the URL value is
still applied.

**`public/app.js`, the dark-mode setup**
- On load, a URL value is applied through `applyTheme(theme, false)`,
which does not write `meshcore-theme`. The saved choice, and the account
settings that `settings-sync.js` syncs from that key, stay as they were.
- Theme changes from other tabs or from settings sync (the `storage`
listener) are ignored while the URL value holds. Otherwise a logged-in
display would flip within a minute.
- A click on the dark-mode toggle ends the URL value and is saved as
before.

It works on every page, not only on the two `embed=1` pages. The value
is read once per page load, so later hash changes (navigation, map
viewport) keep it.

## Tests

| File | Assertions | Covers |
|---|---|---|
| `tests/unit/test-theme-url-param-2236.js` | 8 | Runs the real
pre-paint script from `index.html`: `dark` and `light` win over a saved
choice and the OS, nothing is written to `localStorage`, any page and
position in the query, an unknown value, no param, `localStorage`
throwing |
| `tests/e2e/test-theme-url-param-2236-e2e.js` | 6 steps |
`#/map?embed=1&theme=dark` over a saved "light" (dark, embed, saved
still "light"), a reload without the param, `theme=light` over a saved
"dark", a storage event while the override holds, the toggle ending it,
`theme=blue` |

How I checked them:
- With `index.html` and `app.js` from master, 6 of the 8 unit assertions
fail, and the E2E fails at step 1 ("expected dark + embed + saved
light").
- With only the `app.js` change reverted, the E2E fails at step 1. With
only the `if (themeOverride) return;` line in the `storage` listener
removed, it fails at step 4.
- `sh test-all.sh` passes, except `test-issue-1956-release-routing.js`,
which I skipped: it needs the workflow's `GITHUB_REF_NAME` and does not
run in local git-bash. `npx eslint public/app.js` reports 0 errors (18
warnings, the same count as master).
- E2E on a fixture seeded as in CI:

  | Suite | Result |
  |---|---|
  | `test-e2e-playwright.js` | 148 passed, 3 skipped |
  | `test-customize-theme-e2e.js` | 8/8 |
  | the new #2236 suite | 6/6 |

- `test-logo-theme-e2e.js` fails locally with "wordmark `<text>`
overflows SVG viewBox", with the same numbers on master. It measures
font metrics, which differ on this Windows machine.

The new E2E is registered in `scripts/non-unit-tests.json` and
`deploy.yml` (shard 2, next to the logo theme suites); the unit test is
in `test-all.sh`.

## Not done

- No screenshot: the E2E checks `data-theme` in headless Chromium, not
how the page looks.
- `live.js` Matrix mode forces dark and, when switched off, saves the
theme it restores. With a URL value active, turning Matrix off saves
that value as the stored choice. I left this as is, because a display
that only shows a URL does not toggle Matrix mode.
- Only `theme=`; the `?darkMode=1` form from the issue text is not
supported, so there is one form to document.
- I placed the E2E in shard 2 without measuring suite times.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:40:25 +02:00

147 lines
7.5 KiB
JSON

{
"browser": {
"reason": "Requires Playwright and Chromium; most suites also require a running local fixture server via BASE_URL. Existing E2E workflow steps retain their separate setup and selection. Never point these tests at production.",
"files": [
"tests/e2e/test-1110-live-filter.js",
"tests/e2e/test-a11y-axe-1668.js",
"tests/e2e/test-analytics-fluid-charts.js",
"tests/e2e/test-audio-lab-1297-e2e.js",
"tests/e2e/test-audio-live-1297-e2e.js",
"tests/e2e/test-bottom-nav-1061-e2e.js",
"tests/e2e/test-channel-color-picker-e2e.js",
"tests/e2e/test-channel-decrypt-e2e.js",
"tests/e2e/test-channel-fluid-e2e.js",
"tests/e2e/test-channel-issue-1087-e2e.js",
"tests/e2e/test-channel-issue-1111-e2e.js",
"tests/e2e/test-channel-modal-e2e.js",
"tests/e2e/test-channel-qr-e2e.js",
"tests/e2e/test-channels-add-modal-e2e.js",
"tests/e2e/test-channels-list-render-e2e.js",
"tests/e2e/test-channels-selection-flow-e2e.js",
"tests/e2e/test-channels-share-color-e2e.js",
"tests/e2e/test-channels-ws-batch-e2e.js",
"tests/e2e/test-channels-ws-race-1498-e2e.js",
"tests/e2e/test-charts-fluid-1058-e2e.js",
"tests/e2e/test-customize-branding-e2e.js",
"tests/e2e/test-customize-display-e2e.js",
"tests/e2e/test-customize-export-e2e.js",
"tests/e2e/test-customize-theme-e2e.js",
"tests/e2e/test-drag-manager-e2e.js",
"tests/e2e/test-e2e-1267-mobile-vcr.js",
"tests/e2e/test-e2e-playwright.js",
"tests/e2e/test-filter-ux-e2e.js",
"tests/e2e/test-gesture-hints-1065-e2e.js",
"tests/e2e/test-gestures-1062-e2e.js",
"tests/e2e/test-gestures-1185-scroll-discriminator-e2e.js",
"tests/e2e/test-health-status-e2e.js",
"tests/e2e/test-home-coverage-e2e.js",
"tests/e2e/test-hop-path-inference-e2e.js",
"tests/e2e/test-issue-1109-hamburger-dropdown-visible-e2e.js",
"tests/e2e/test-issue-1122-packets-filter-ux-e2e.js",
"tests/e2e/test-issue-1128-multi-viewport-e2e.js",
"tests/e2e/test-issue-1128-packets-layout-e2e.js",
"tests/e2e/test-issue-1136-live-region-e2e.js",
"tests/e2e/test-issue-1146-path-link-contrast-e2e.js",
"tests/e2e/test-issue-1147-section-order-e2e.js",
"tests/e2e/test-issue-1150-404-state-e2e.js",
"tests/e2e/test-issue-1151-orphan-separators-e2e.js",
"tests/e2e/test-issue-1204-live-panel-structure-e2e.js",
"tests/e2e/test-issue-1205-live-controls-anchor-e2e.js",
"tests/e2e/test-issue-1206-resize-observer-leak-e2e.js",
"tests/e2e/test-issue-1206-vcr-overlap-e2e.js",
"tests/e2e/test-issue-1224-channels-mobile-ux-e2e.js",
"tests/e2e/test-issue-1234-live-chrome-pass2-e2e.js",
"tests/e2e/test-issue-1236-map-mobile-e2e.js",
"tests/e2e/test-issue-1244-live-vcr-row-hints-e2e.js",
"tests/e2e/test-issue-1273-qr-overlay-height-e2e.js",
"tests/e2e/test-issue-1274-legend-coverage-e2e.js",
"tests/e2e/test-issue-1279-legend-p2-e2e.js",
"tests/e2e/test-issue-1281-location-row-e2e.js",
"tests/e2e/test-issue-1306-collisions-terminology-e2e.js",
"tests/e2e/test-issue-1329-map-controls-accordion-e2e.js",
"tests/e2e/test-issue-1367-channels-chat-app-e2e.js",
"tests/e2e/test-issue-1374-route-map-a11y-e2e.js",
"tests/e2e/test-issue-1400-nav-vertical-clip.js",
"tests/e2e/test-issue-1413-nav-overlap-e2e.js",
"tests/e2e/test-issue-1486-collapse-reopens-detail-e2e.js",
"tests/e2e/test-issue-1487-byop-modal-layout-e2e.js",
"tests/e2e/test-issue-1510-live-nav-pin-e2e.js",
"tests/e2e/test-issue-1567-corner-clears-drag-e2e.js",
"tests/e2e/test-issue-1599-replay-freeze-e2e.js",
"tests/e2e/test-issue-1630-reach-mobile-e2e.js",
"tests/e2e/test-issue-1268-rf-params-e2e.js",
"tests/e2e/test-issue-1639-observers-sort-e2e.js",
"tests/e2e/test-issue-748-node-analytics-coverage-e2e.js",
"tests/e2e/test-issue-2218-node-analytics-stale-e2e.js",
"tests/e2e/test-issue-661-airtime-e2e.js",
"tests/e2e/test-issue-1640-compare-discovery-e2e.js",
"tests/e2e/test-issue-1648-m1-icons-e2e.js",
"tests/e2e/test-issue-1648-m2-icons-e2e.js",
"tests/e2e/test-issue-1648-m3-icons-e2e.js",
"tests/e2e/test-issue-1648-m4-icons-e2e.js",
"tests/e2e/test-issue-1648-m5-icons-e2e.js",
"tests/e2e/test-issue-1657-analytics-channels-group-sprites-e2e.js",
"tests/e2e/test-issue-1692-packets-init-parallel-e2e.js",
"tests/e2e/test-issue-1705-subpath-contrast-e2e.js",
"tests/e2e/test-issue-1758-ng-filter-rerenders-e2e.js",
"tests/e2e/test-issue-1799-label-vocab-e2e.js",
"tests/e2e/test-issue-1833-legend-toggle-clickable-e2e.js",
"tests/e2e/test-issue-1997-distance-building-e2e.js",
"tests/e2e/test-live-dedup.js",
"tests/e2e/test-live-fullscreen-1572-e2e.js",
"tests/e2e/test-live-layout-1178-1179-e2e.js",
"tests/e2e/test-live-mql-leak-1180-e2e.js",
"tests/e2e/test-live-multibyte-only-e2e.js",
"tests/e2e/test-logo-default-sage-teal-e2e.js",
"tests/e2e/test-logo-pulse-1173-e2e.js",
"tests/e2e/test-logo-rebrand-e2e.js",
"tests/e2e/test-logo-theme-e2e.js",
"tests/e2e/test-map-modal-fluid-e2e.js",
"tests/e2e/test-map-nodes-pagination-e2e.js",
"tests/e2e/test-nav-drawer-1064-e2e.js",
"tests/e2e/test-nav-fluid-1055-e2e.js",
"tests/e2e/test-nav-more-floor-1139-e2e.js",
"tests/e2e/test-nav-priority-1102-e2e.js",
"tests/e2e/test-nav-priority-1311-e2e.js",
"tests/e2e/test-nav-priority-1391-e2e.js",
"tests/e2e/test-neighbor-map-btn-clip-e2e.js",
"tests/e2e/test-node-reach-coverage-e2e.js",
"tests/e2e/test-node-reach-e2e.js",
"tests/e2e/test-nodes-export-e2e.js",
"tests/e2e/test-observer-iata-1188-e2e.js",
"tests/e2e/test-packets-compact-columns-e2e.js",
"tests/e2e/test-packets-node-filter-e2e.js",
"tests/e2e/test-packets-scope-column.js",
"tests/e2e/test-path-inspector-coverage-e2e.js",
"tests/e2e/test-rx-coverage-mobile-nav-e2e.js",
"tests/e2e/test-rx-coverage-gaps-e2e.js",
"tests/e2e/test-rx-coverage-noise-e2e.js",
"tests/e2e/test-rx-coverage-viewport-e2e.js",
"tests/e2e/test-show-neighbors.js",
"tests/e2e/test-slideover-1056-e2e.js",
"tests/e2e/test-slideover-1168-munger-e2e.js",
"tests/e2e/test-table-fluid-e2e.js",
"tests/e2e/test-theme-url-param-2236-e2e.js",
"tests/e2e/test-touch-gestures-coverage-e2e.js",
"tests/e2e/test-touch-targets.js",
"tests/e2e/test-user-management-e2e.js"
]
},
"domIntegration": {
"reason": "Requires jsdom. The JS unit gate runs with no node_modules at all (its job has no npm install), so a suite needing a third-party module cannot live in test-all.sh. It runs in the E2E job, which does npm ci, and is invoked by its own deploy.yml line.",
"files": [
"tests/e2e/test-region-quick-picks.js",
"tests/e2e/test-table-sort.js"
]
},
"playwrightRunner": {
"reason": "Requires the @playwright/test runner (not plain node), its configuration, Chromium, and a local server. @playwright/test is not currently declared in package.json; these suites are inventoried but not part of the standalone unit gate.",
"files": [
"tests/e2e/test-issue-1522-trace-url-sync-e2e.js",
"tests/e2e/test-marker-outline-weight.js",
"tests/e2e/test-path-inspector-e2e.js",
"tests/e2e/test-pr-1490-live-map-gpu-animations-e2e.js"
]
}
}