todos-contra-el-fuego-web/e2e/support/app.js
vjrj bba2ad5523
All checks were successful
build-image / test (push) Successful in 2m22s
build-image / build (push) Successful in 11m25s
fix(fires): el mapa de /fires ya no se queda en gris
Causa raíz, encontrada reproduciéndolo con el bundle de producción en local
—con el servidor de desarrollo NO se ve, y por eso llevaba semanas dándose por
un artefacto del navegador sin pantalla:

    if ((centerStored !== [0, 0] || geolocation.get()) && geoInit) {
      center.set(centerStored || geolocation.get());
      geoInit = false;
    }

`centerStored !== [0, 0]` compara contra un array recién creado: es SIEMPRE
cierto. En un navegador nuevo —sin centro en localStorage y con la
geolocalización todavía sin resolver— eso hacía `center.set(undefined)` y dejaba
`geoInit` en false, o sea para siempre. Un `<MapContainer>` sin `center` no
recibe `setView`, y un mapa de Leaflet sin vista no carga capa base ni teselas
ni dispara `whenReady`, con lo que tampoco se creaba la suscripción por
viewport. En desarrollo no pasaba porque la geolocalización llegaba a tiempo.

Y de paso el "Actualizando…" que no se quitaba nunca: las suscripciones se
creaban dentro de un `Tracker.autorun` ANIDADO en el withTracker. La computación
de fuera no dependía de `mapSize`, así que `loading` se calculaba con
`subscription` a undefined y no volvía a recalcularse; además cada pasada del
withTracker dejaba otro autorun sin parar. Ahora las suscripciones se crean en
la propia computación reactiva, que es donde Meteor sabe pararlas al invalidarse.

MapReady, además, vigila el contenedor con un ResizeObserver: su comprobación de
tamaño solo podía reintentar con el evento `resize` de Leaflet, que no se emite
si nadie llama a invalidateSize(). No era la causa de esto, pero hacía que la
comprobación no sirviera de nada.

La suite e2e deja de usar selectores por id: TestUtils.testId() los devuelve solo
en desarrollo, así que contra el bundle de producción —lo que levanta el job
nocturno— no existían y la suite entera habría fallado esta noche.

Verificado contra bundle de producción: /fires pasa de 0 teselas y 0 capas a 12
teselas, capa base y las dos suscripciones por viewport, sin "Actualizando…".
90 tests de servidor, 16 e2e y smoke REST byte-idéntico.
2026-08-03 19:59:30 +02:00

71 lines
3.1 KiB
JavaScript

// Shared helpers. Everything here drives the UI the way a person would: the
// point of this suite is the wiring between browser, DDP and Mongo, so reaching
// into Meteor.call() from the page would defeat it.
//
// ⚠️ NADA de selectores por id. `imports/ui/components/Utils/TestUtils.js`
// devuelve el id SOLO en desarrollo, así que contra un bundle de producción
// —que es justo lo que levanta el job nocturno de CI— esos elementos no lo
// llevan. Aquí se va por href, rol y nombre de campo, que existen en los dos.
export const NAV = {
zonas: 'nav a[href="/zones"]',
fuegos: 'nav a[href="/fires"]',
perfil: 'nav a[href="/profile"]',
entrar: 'nav a[href="/login"]',
salir: 'nav a[href="/logout"]',
misZonas: 'nav a[href="/subscriptions"]'
};
export const botonRegistro = page => page.getByRole('button', { name: 'Registrarse', exact: true });
export const botonEntrar = page => page.getByRole('button', { name: 'Iniciar sesión', exact: true });
// A fresh address per test: the seed removes every e2e+* user, and reusing one
// across specs makes failures depend on the order they ran in.
export const newEmail = () => `e2e+${Date.now()}${Math.floor(Math.random() * 1000)}@test.com`;
export const signUp = async (page, email = newEmail()) => {
await page.goto('/signup');
await page.fill('input[name=firstName]', 'E2E');
await page.fill('input[name=lastName]', 'Tester');
await page.fill('input[name=emailAddress]', email);
await page.fill('input[name=password]', 'password');
await page.check('input[name=tos]');
await botonRegistro(page).click();
// Signing up lands on the zones page.
await page.waitForURL('**/subscriptions', { timeout: 30000 });
return email;
};
export const logIn = async (page, email, password = 'password') => {
await page.goto('/login');
await page.fill('input[name=emailAddress]', email);
await page.fill('input[name=password]', password);
await botonEntrar(page).click();
};
// The map only settles once leaflet has laid out its panes and the tiles for the
// current viewport are in.
export const waitForMap = async (page) => {
await page.locator('.leaflet-container').first().waitFor({ state: 'visible', timeout: 60000 });
await page.waitForTimeout(1500);
};
// Creates a zone through the real flow: the button on the zones map, the
// selection map, and the subscribe button. This is the flow that froze staging.
export const addZoneThroughTheMap = async (page) => {
await page.goto('/subscriptions');
await waitForMap(page);
await page.getByRole('button', { name: 'Añadir zona' }).click();
await page.waitForURL('**/subscriptions/new', { timeout: 30000 });
await waitForMap(page);
const map = page.locator('.leaflet-container').first();
const box = await map.boundingBox();
// Click slightly off-centre: the marker moves there, which also proves the
// map is interactive and not a dead tile layer.
await page.mouse.click(box.x + (box.width / 2) + 20, box.y + (box.height / 2) + 20);
await page.waitForTimeout(1000);
await page.getByRole('button', { name: /Suscribirme/ }).click();
await page.waitForURL('**/subscriptions', { timeout: 60000 });
};