wrolpi/app/cypress/e2e/map-themes.cy.js
Roland bbe3cac9a8
test: act on review -- the sprite gap, and the branch no e2e can reach
Both comments were right, and the second named a real blind spot.

The e2e iterated a hardcoded theme list while the unit suite iterated
`themeNames`, so a fifth theme would have got mapping coverage for free
and no cold start.  It imports `themeNames` and `themeSessionKey` now.

The sprite was invisible to it.  A wrong sprite name 404s the icons and
MapLibre carries on, so the canvas exists and the suite passes -- exactly
the case the review described.  The spec now intercepts
`/map-assets/sprites/**` and asserts the requested set is `mapSprite()`.
Planted it by collapsing the sprite back into the flavor: night and amber
get PAST the canvas assertion and fail on the sprite, which is the point.

The rebuild branch cannot be reached from a browser at all.  MapViewer
builds a style twice -- at init, and in an effect keyed on
`[theme, mapReady, scaleUnit]` -- but the theme picker lives on Settings
and in the gallery, never in the nav, so there is no way to change the
theme with /map open; navigating to Settings unmounts the viewer.  A
Cypress case that switches themes would have nothing to click.

So that branch is guarded in the source instead: both `buildStyle` calls
must pass `mapFlavor(theme)` and `mapSprite(theme)`, and none may pass a
bare `theme`.  Crude, and the honest alternative to a browser test that
cannot reach the branch it claims to cover.  Planted by breaking ONLY the
rebuild call: the source guards go red and the e2e stays green, which is
the whole argument for keeping it.

The call-site scan counts parens rather than matching a regex.  The
arguments are themselves calls, so anything non-greedy stops at
`mapFlavor(theme)` and reports half an argument list as the whole of it --
which is how the first version of this test failed while the code was
correct.

1098 jest / 61 suites, 4 e2e themes, clean build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:05:48 -06:00

66 lines
3.2 KiB
JavaScript

import {mapSprite, themeNames, themeSessionKey} from '../../src/themes/names';
/*
* The map basemap has a "flavor" of its own, and MapViewer used to hand protomaps the WROLPi
* theme name. That worked while the only themes were `light` and `dark`, and broke when
* night and amber arrived: protomaps looks the flavor up in a record and dereferences the
* result without checking, so an unknown name throws
* `Cannot read properties of undefined (reading 'background')` before the first tile and the
* viewer renders nothing at all.
*
* Driven through the real page in every theme, because the unit test can only check that the
* mapping returns something valid -- it cannot see whether MapViewer USES it, which is the
* half that was broken.
*
* `themeNames` rather than a list written out here, so a fifth theme is cold-started by this
* suite the day it is added rather than the day someone remembers to edit it.
*/
describe('the map viewer builds a basemap in every theme', () => {
themeNames.forEach((theme) => {
it(`initialises MapLibre in ${theme}`, () => {
const errors = [];
/*
* The sprite is the half no canvas assertion can see: a wrong sprite name 404s
* the icons and MapLibre carries on, so the map still draws and this test would
* still pass. Watching the request is the only way to tell `sprites/dark` from
* `sprites/black` -- and `black` is exactly the name that would be requested if
* the flavor and the sprite were ever collapsed back into one variable.
*/
cy.intercept('GET', '**/map-assets/sprites/**').as('sprite');
cy.visit('/map', {
onBeforeLoad(win) {
// Set the theme before the app boots, so the viewer builds its style once
// with the theme under test rather than rebuilding after a switch.
win.localStorage.setItem(themeSessionKey, theme);
// MapViewer reports a failed init through console.error and then renders
// nothing, so the console is where the defect is visible.
const original = win.console.error;
win.console.error = (...args) => {
errors.push(args.map(String).join(' '));
original(...args);
};
},
});
// The canvas only exists once MapLibre constructed a style successfully.
cy.get('canvas.maplibregl-canvas', {timeout: 40000}).should('exist');
cy.wait('@sprite', {timeout: 40000}).then(({request}) => {
const name = request.url
.split('/map-assets/sprites/')[1]
.replace(/[?#].*$/, '')
.replace(/\.(json|png)$/, '')
.replace('@2x', '');
expect(name, `sprite set requested in ${theme}`).to.equal(mapSprite(theme));
});
cy.then(() => {
const failures = errors.filter(message =>
/MapLibre failed to initialize|reading 'background'/.test(message));
expect(failures.join('\n')).to.equal('');
});
});
});
});