diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index f722f9e..e6b6609 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -26,10 +26,10 @@ jobs: chmod +x back/shared/sitmun-shared/gradlew back/shared/sitmun-shared/gradlew -p back/shared/sitmun-shared publishToMavenLocal - - name: Set up Node 20 + - name: Set up Node 24.22.0 uses: actions/setup-node@v5 with: - node-version: '20' + node-version: '24.22.0' cache: npm cache-dependency-path: | package-lock.json @@ -81,10 +81,10 @@ jobs: chmod +x back/shared/sitmun-shared/gradlew back/shared/sitmun-shared/gradlew -p back/shared/sitmun-shared publishToMavenLocal - - name: Set up Node 20 + - name: Set up Node 24.22.0 uses: actions/setup-node@v5 with: - node-version: '20' + node-version: '24.22.0' cache: npm cache-dependency-path: | package-lock.json @@ -100,6 +100,28 @@ jobs: - name: Install Chromium run: npx playwright install --with-deps chromium + # e2e-viewer.mjs requires ~/.nvm/versions/node/v/bin (setup-node does not). + # dcodeIO/setup-node-nvm installs into $HOME/mynvm; link that tree to ~/.nvm. + - name: Read viewer .nvmrc + id: viewer-nvmrc + run: | + ver="$(tr -d '[:space:]' < front/viewer/sitmun-viewer-app/.nvmrc)" + echo "version=${ver#v}" >> "$GITHUB_OUTPUT" + - name: Install viewer Node into nvm + uses: dcodeIO/setup-node-nvm@v5 + with: + node-version: ${{ steps.viewer-nvmrc.outputs.version }} + - name: Point ~/.nvm at setup-node-nvm install + run: | + # Ubuntu runners often ship ~/.nvm as a real directory; ln -sfn into a + # directory nests the link. Replace it so e2e-viewer.mjs finds node. + echo "NVM_DIR=${HOME}/mynvm" >> "$GITHUB_ENV" + rm -rf "${HOME}/.nvm" + ln -sfn "${HOME}/mynvm" "${HOME}/.nvm" + ver="$(tr -d '[:space:]' < front/viewer/sitmun-viewer-app/.nvmrc)" + ver="${ver#v}" + test -x "${HOME}/.nvm/versions/node/v${ver}/bin/node" + - name: Run viewer E2E suite run: npm run e2e:viewer env: @@ -136,10 +158,10 @@ jobs: chmod +x back/shared/sitmun-shared/gradlew back/shared/sitmun-shared/gradlew -p back/shared/sitmun-shared publishToMavenLocal - - name: Set up Node 20 + - name: Set up Node 24.22.0 uses: actions/setup-node@v5 with: - node-version: '20' + node-version: '24.22.0' cache: npm cache-dependency-path: | package-lock.json @@ -160,6 +182,28 @@ jobs: - name: Install Chromium run: npx playwright install --with-deps chromium + # e2e-viewer.mjs requires ~/.nvm/versions/node/v/bin (setup-node does not). + # dcodeIO/setup-node-nvm installs into $HOME/mynvm; link that tree to ~/.nvm. + - name: Read viewer .nvmrc + id: viewer-nvmrc + run: | + ver="$(tr -d '[:space:]' < front/viewer/sitmun-viewer-app/.nvmrc)" + echo "version=${ver#v}" >> "$GITHUB_OUTPUT" + - name: Install viewer Node into nvm + uses: dcodeIO/setup-node-nvm@v5 + with: + node-version: ${{ steps.viewer-nvmrc.outputs.version }} + - name: Point ~/.nvm at setup-node-nvm install + run: | + # Ubuntu runners often ship ~/.nvm as a real directory; ln -sfn into a + # directory nests the link. Replace it so e2e-viewer.mjs finds node. + echo "NVM_DIR=${HOME}/mynvm" >> "$GITHUB_ENV" + rm -rf "${HOME}/.nvm" + ln -sfn "${HOME}/mynvm" "${HOME}/.nvm" + ver="$(tr -d '[:space:]' < front/viewer/sitmun-viewer-app/.nvmrc)" + ver="${ver#v}" + test -x "${HOME}/.nvm/versions/node/v${ver}/bin/node" + - name: Run application-contact E2E suite run: npx playwright test --config=playwright.application-contact.config.ts env: @@ -196,10 +240,10 @@ jobs: chmod +x back/shared/sitmun-shared/gradlew back/shared/sitmun-shared/gradlew -p back/shared/sitmun-shared publishToMavenLocal - - name: Set up Node 20 + - name: Set up Node 24.22.0 uses: actions/setup-node@v5 with: - node-version: '20' + node-version: '24.22.0' cache: npm cache-dependency-path: | package-lock.json @@ -220,6 +264,28 @@ jobs: - name: Install Chromium run: npx playwright install --with-deps chromium + # e2e-viewer.mjs requires ~/.nvm/versions/node/v/bin (setup-node does not). + # dcodeIO/setup-node-nvm installs into $HOME/mynvm; link that tree to ~/.nvm. + - name: Read viewer .nvmrc + id: viewer-nvmrc + run: | + ver="$(tr -d '[:space:]' < front/viewer/sitmun-viewer-app/.nvmrc)" + echo "version=${ver#v}" >> "$GITHUB_OUTPUT" + - name: Install viewer Node into nvm + uses: dcodeIO/setup-node-nvm@v5 + with: + node-version: ${{ steps.viewer-nvmrc.outputs.version }} + - name: Point ~/.nvm at setup-node-nvm install + run: | + # Ubuntu runners often ship ~/.nvm as a real directory; ln -sfn into a + # directory nests the link. Replace it so e2e-viewer.mjs finds node. + echo "NVM_DIR=${HOME}/mynvm" >> "$GITHUB_ENV" + rm -rf "${HOME}/.nvm" + ln -sfn "${HOME}/mynvm" "${HOME}/.nvm" + ver="$(tr -d '[:space:]' < front/viewer/sitmun-viewer-app/.nvmrc)" + ver="${ver#v}" + test -x "${HOME}/.nvm/versions/node/v${ver}/bin/node" + - name: Run MIA cross E2E suite run: npm run e2e:mia-cross env: @@ -256,10 +322,10 @@ jobs: chmod +x back/shared/sitmun-shared/gradlew back/shared/sitmun-shared/gradlew -p back/shared/sitmun-shared publishToMavenLocal - - name: Set up Node 20 + - name: Set up Node 24.22.0 uses: actions/setup-node@v5 with: - node-version: '20' + node-version: '24.22.0' cache: npm cache-dependency-path: package-lock.json @@ -305,10 +371,10 @@ jobs: chmod +x back/shared/sitmun-shared/gradlew back/shared/sitmun-shared/gradlew -p back/shared/sitmun-shared publishToMavenLocal - - name: Set up Node 20 + - name: Set up Node 22 uses: actions/setup-node@v5 with: - node-version: '20' + node-version: '22' cache: npm cache-dependency-path: | package-lock.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 62d2b8b..85fc7f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), #### Stack-level +- **Toolchain**: Submodule gitlinks point at Spring Boot `4.1.1` (shared, backend, proxy, mbtiles), Angular `22.2.3` / Node `24.22.0` (admin, viewer), and Gradle `8.14.6`. Root image and CI use Node `24.22.0`; mobile Android E2E stays on Node `22`. Published shared coordinate remains `1.2.9`. - **Viewer**: The stack points at viewer `dev`, which replaces filler control-spec expects, deletes the unused control loader, drops three local `any` casts, and removes comments that name the wrong type or narrate the next line ([sitmun-viewer-app#199](https://github.com/sitmun/sitmun-viewer-app/pull/199), [#200](https://github.com/sitmun/sitmun-viewer-app/pull/200), [#201](https://github.com/sitmun/sitmun-viewer-app/pull/201), [#202](https://github.com/sitmun/sitmun-viewer-app/pull/202), [#203](https://github.com/sitmun/sitmun-viewer-app/pull/203)). - **Admin**: The stack points at admin `dev`, including the service-access clock and the cleanup that replaces filler expects, deletes unused task fields, corrects comments, drops local `any` casts, and removes narrating comments ([sitmun-admin-app#475](https://github.com/sitmun/sitmun-admin-app/pull/475), [#476](https://github.com/sitmun/sitmun-admin-app/pull/476), [#477](https://github.com/sitmun/sitmun-admin-app/pull/477), [#478](https://github.com/sitmun/sitmun-admin-app/pull/478), [#479](https://github.com/sitmun/sitmun-admin-app/pull/479), [#480](https://github.com/sitmun/sitmun-admin-app/pull/480)). - **Backend**: The stack points at backend `dev`. CI publishes `sitmun-shared` before the build. An LDAP login fails when the role lookup throws. Cleanup replaces filler expects, deletes unused symbols, and removes comments that name the wrong type or narrate the next line ([sitmun-backend-core#274](https://github.com/sitmun/sitmun-backend-core/pull/274), [#275](https://github.com/sitmun/sitmun-backend-core/pull/275), [#276](https://github.com/sitmun/sitmun-backend-core/pull/276), [#277](https://github.com/sitmun/sitmun-backend-core/pull/277), [#278](https://github.com/sitmun/sitmun-backend-core/pull/278), [#279](https://github.com/sitmun/sitmun-backend-core/pull/279), [#280](https://github.com/sitmun/sitmun-backend-core/pull/280), [#281](https://github.com/sitmun/sitmun-backend-core/pull/281)). diff --git a/README.md b/README.md index e104d61..8cb9d5e 100644 --- a/README.md +++ b/README.md @@ -548,7 +548,7 @@ Admin App ───┘ Root Playwright tests exercise the admin and viewer apps against backend-core on in-memory H2 (no Docker Compose). Viewer suite also starts proxy middleware and a local secured WMS stub. Mobile web suite covers edition Bearer login, proxy-token exchange, and authenticated MBTiles through middleware (no public `/mbtiles`). Details: [e2e/README.md](e2e/README.md). ```bash -# prerequisites: Java 17, Node ≥ 20.19, submodules initialized +# prerequisites: Java 17, Node ≥ 24.22.0 (mobile Android E2E needs Node 22), submodules initialized # admin: cd front/admin/sitmun-admin-app && npm ci # viewer: cd front/viewer/sitmun-viewer-app && npm ci npm ci @@ -1036,7 +1036,7 @@ rm -rf node_modules package-lock.json npm ci # Check Node.js version -node --version # Should be 20.19.0 or higher +node --version # Should be 24.22.0 or higher (Node 22 for mobile Android E2E) ``` #### Backend Build Issues diff --git a/back/backend/sitmun-backend-core b/back/backend/sitmun-backend-core index 0cd1495..374e1bb 160000 --- a/back/backend/sitmun-backend-core +++ b/back/backend/sitmun-backend-core @@ -1 +1 @@ -Subproject commit 0cd149521910f5c3b87309b36cc76f524b4b7efa +Subproject commit 374e1bbea52cc3e9f5daec4692187fbcf5b1babb diff --git a/back/mbtiles/sitmun-mbtiles b/back/mbtiles/sitmun-mbtiles index 7417dca..609a807 160000 --- a/back/mbtiles/sitmun-mbtiles +++ b/back/mbtiles/sitmun-mbtiles @@ -1 +1 @@ -Subproject commit 7417dcaa7d705cf0f86b30e3a1c1bbc6f4bad489 +Subproject commit 609a807e98bc8f0bec5148ef6defba1a4fb86f28 diff --git a/back/proxy/sitmun-proxy-middleware b/back/proxy/sitmun-proxy-middleware index a05dd1b..05909bb 160000 --- a/back/proxy/sitmun-proxy-middleware +++ b/back/proxy/sitmun-proxy-middleware @@ -1 +1 @@ -Subproject commit a05dd1b5fdb8de67a9470bc576ede5e492e02a3e +Subproject commit 05909bb29e994ac6b69bed431320fddd97085172 diff --git a/back/shared/sitmun-shared b/back/shared/sitmun-shared index ca8d7e3..d900f42 160000 --- a/back/shared/sitmun-shared +++ b/back/shared/sitmun-shared @@ -1 +1 @@ -Subproject commit ca8d7e3ba9af05f30d1a3007808acffffe20cc7d +Subproject commit d900f42d8491aeff4cfb95da330a67d50bf52933 diff --git a/e2e/README.md b/e2e/README.md index e38edc3..ba9042e 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -10,7 +10,7 @@ Browser E2E against backend-core on in-memory H2. No Docker Compose. - Language chrome on login: cleared `lang` → closed BCP-47 ISO from `language.default`; open menu shows API endonyms; no language field on the login form (`e2e/admin/language-chrome.spec.ts`, project `login`) - Role form **Details**: validation, create, edit, reload persistence (relation tabs not exercised) - User form **Details**: validation, create, edit, reload persistence (relation tabs not exercised) -- User form **Positions**: `Valid from` immediately before `Valid until` (ES `Fecha de alta` / `Fecha de baja`); empty `createdDate` is Not set / No informada; empty `expirationDate` is Active; Alta edit persist; inclusive last-day tooltip; hidden for `public`; pre-2000 Alta cell; viewer cannot write Alta; 20-row open under 3s ([sitmun-admin-app#462](https://github.com/sitmun/sitmun-admin-app/issues/462); `e2e/admin/forms/user-positions.spec.ts`, project `admin-forms`) +- User form **Positions**: `Valid from` immediately before `Valid until` (ES `Fecha de alta` / `Fecha de baja`); empty `createdDate` is Not set / No informada; empty `expirationDate` is Active; Alta and Baja edit persist; Position name text edit persist; inclusive last-day tooltip; hidden for `public`; pre-2000 Alta cell; viewer cannot write Alta; 20-row open under 3s ([sitmun-admin-app#462](https://github.com/sitmun/sitmun-admin-app/issues/462); `e2e/admin/forms/user-positions.spec.ts`, project `admin-forms`) - Territory form **Details**: validation, create (with type), edit, reload persistence (relation tabs not exercised) - Tree form **Details** SVG local-file persist and touristic **Tree structure** node Appearance SVG persist: picker `accept` includes `.svg`; POST/PUT stores `data:image/svg+xml;base64,...` without raster scaling; reload preview stays SVG ([sitmun-admin-app#330](https://github.com/sitmun/sitmun-admin-app/issues/330); `e2e/admin/forms/tree-svg-image.spec.ts`, project `admin-forms`) - Layers form **Details**: validation, create, reload persistence; Feature Information character-count must not throw `raw.split` beside the queryable-layers CSV validator (`e2e/admin/forms/layers-form.spec.ts`, project `admin-forms`) @@ -24,6 +24,7 @@ Browser E2E against backend-core on in-memory H2. No Docker Compose. - Plantilla role and territory tabs: assigning a role and a territory on the task form persists after reload ([sitmun-admin-app#467](https://github.com/sitmun/sitmun-admin-app/issues/467); `e2e/admin/forms/template-task-relations.spec.ts`, project `admin-forms`) - More Info Advanced **Details**: validation, create with cartography + included query child 38, layout update persistence; cartography open-in-new; deep route `/#/tasks/42/16`; Parameters `app-relation-grid` add + reload persist; child parameter mapping UI round-trip + persists across a subsequent Parameters save; orphan `childTaskParameters` keys dropped on save; Add mapping disabled when included child has no parameters; ADMIN `POST /api/tasks/template/more-info-advanced/render` for seeded parent 42 (`e2e/admin/forms/mia-form.spec.ts`, project `admin-forms`) +- Cartography collection: ADMIN `GET /api/cartographies?size=10000` returns 200 (Boot 4.1.1 / Hibernate 7 EntityGraph+styles bag regression that blanked MIA cartography autocomplete) (`e2e/admin/forms/cartography-list.spec.ts`, project `admin-forms`) - Language default change: Set as Default preview dialog cancel leaves `language.default` unchanged; raw config PUT cannot freely replace it (`e2e/admin/forms/language-default.spec.ts`) - Language enabled/order: disable/reorder a non-default language, assert login chrome omits it, restore (`e2e/admin/forms/language-order.spec.ts`) - Literal translations grid CRUD (not `app-relation-grid`; no CSV): create row and reload persists (`e2e/admin/forms/literal-translation-form.spec.ts`) @@ -99,7 +100,7 @@ Shared H2 + admin + viewer + proxy + WMS stub. Do not run concurrently with admi ## Prerequisites - Java 17 -- Node ≥ 20.19 +- Node ≥ 24.22.0 (admin, viewer, and root web E2E). Mobile Android E2E needs Node 22. - Initialized submodules: `git submodule update --init --recursive` - Admin dependencies: `cd front/admin/sitmun-admin-app && npm ci` - Viewer dependencies: `cd front/viewer/sitmun-viewer-app && npm ci` diff --git a/e2e/admin/forms/cartography-list.spec.ts b/e2e/admin/forms/cartography-list.spec.ts new file mode 100644 index 0000000..b3d1a3f --- /dev/null +++ b/e2e/admin/forms/cartography-list.spec.ts @@ -0,0 +1,17 @@ +import { test, expect } from '../fixtures'; + +/** + * Boot 4.1.1 / Hibernate 7 regression: paged cartography list with EntityGraph+styles bag + * returned 500, which blanked MIA (and other) forms that call fetchAllRawItems(size=10000). + */ +test.describe('Cartography collection list', () => { + test('GET /api/cartographies?size=10000 returns 200 for admin', async ({ request }) => { + const response = await request.get('/backend/api/cartographies?size=10000', { + headers: { 'X-SITMUN-Client': 'admin' }, + }); + expect(response.status(), await response.text()).toBe(200); + const body = await response.json(); + const items = body?._embedded?.cartographies ?? body; + expect(Array.isArray(items) ? items.length : 0).toBeGreaterThan(0); + }); +}); diff --git a/e2e/admin/forms/user-positions.spec.ts b/e2e/admin/forms/user-positions.spec.ts index d7d1801..92cf216 100644 --- a/e2e/admin/forms/user-positions.spec.ts +++ b/e2e/admin/forms/user-positions.spec.ts @@ -11,6 +11,8 @@ import { POSITIONS_TAB, createUserViaForm, editAltaCell, + editBajaCell, + editPositionNameCell, expectCreatedThenExpiration, headerTexts, openPositions, @@ -69,6 +71,45 @@ test.describe('User Positions tab', () => { await saveShot(page, 'admin-edit-alta.png'); }); + // Sibling of Alta edit: same Instant ISO + datePicker path on expirationDate. + test('edits Baja and it persists after reload', async ({ page, request, createdResources }) => { + const { id } = await createUserViaForm(page, 'e2ebajaedt'); + createdResources.push({ collection: 'users', id }); + await postPosition(request, id, { expirationDate: '2030-12-31T00:00:00.000Z' }); + + await openPositions(page, id); + await editBajaCell(page, '2028-07-04'); + await savePositionUpdate(page); + + await page.reload(); + await openPositions(page, id); + await expect( + page.locator('app-relation-grid .ag-cell[col-id="expirationDate"]').first(), + ).toContainText(/2028/); + await saveShot(page, 'admin-edit-baja.png'); + }); + + // Sibling: CommunityFeaturesModule text editor on the same Positions grid. + test('edits Position name and it persists after reload', async ({ + page, + request, + createdResources, + }) => { + const { id } = await createUserViaForm(page, 'e2eposn'); + createdResources.push({ collection: 'users', id }); + await postPosition(request, id, { name: 'Cargo' }); + + await openPositions(page, id); + await editPositionNameCell(page, 'Cargo editado'); + await savePositionUpdate(page); + + await page.reload(); + await openPositions(page, id); + await expect(page.locator('app-relation-grid .ag-cell[col-id="name"]').first()).toContainText( + 'Cargo editado', + ); + }); + test('Baja header tooltip states the inclusive last civil day', async ({ page, request, diff --git a/e2e/admin/helpers/user-positions.ts b/e2e/admin/helpers/user-positions.ts index 7036acb..e543e9a 100644 --- a/e2e/admin/helpers/user-positions.ts +++ b/e2e/admin/helpers/user-positions.ts @@ -123,8 +123,12 @@ export async function saveShot(page: Page, name: string): Promise { await page.screenshot({ path: `test-results/${name}` }); } -export async function editAltaCell(page: Page, isoDate: string): Promise { - const cell = page.locator('app-relation-grid .ag-cell[col-id="createdDate"]').first(); +export async function editDateCell( + page: Page, + colId: 'createdDate' | 'expirationDate', + isoDate: string, +): Promise { + const cell = page.locator(`app-relation-grid .ag-cell[col-id="${colId}"]`).first(); await expect(cell).toBeVisible({ timeout: 15_000 }); await cell.dblclick(); const editor = page.locator('app-relation-grid .ag-cell-inline-editing input').first(); @@ -139,6 +143,31 @@ export async function editAltaCell(page: Page, isoDate: string): Promise { await page.keyboard.press('Enter'); } +export async function editAltaCell(page: Page, isoDate: string): Promise { + await editDateCell(page, 'createdDate', isoDate); +} + +export async function editBajaCell(page: Page, isoDate: string): Promise { + await editDateCell(page, 'expirationDate', isoDate); +} + +/** Inline text editor on Position name (CommunityFeaturesModule text path). */ +export async function editPositionNameCell(page: Page, name: string): Promise { + const cell = page.locator('app-relation-grid .ag-cell[col-id="name"]').first(); + await expect(cell).toBeVisible({ timeout: 15_000 }); + await cell.dblclick(); + const editor = page.locator('app-relation-grid .ag-cell-inline-editing input').first(); + if ((await editor.count()) === 0 || !(await editor.isVisible())) { + await cell.click(); + await page.keyboard.press('F2'); + } + await expect(page.locator('app-relation-grid .ag-cell-inline-editing input').first()).toBeVisible({ + timeout: 5_000, + }); + await page.locator('app-relation-grid .ag-cell-inline-editing input').first().fill(name); + await page.keyboard.press('Enter'); +} + export async function savePositionUpdate(page: Page): Promise { const putResponse = page.waitForResponse((response) => { try { diff --git a/front/Dockerfile b/front/Dockerfile index 9fc5e1c..11e626b 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -1,6 +1,6 @@ # Stage 0, "build-apps", based on Node.js, to build and compile both frontend apps sequentially # Each app's About-dialog version comes from its package.json at build time (not stack VERSION). -FROM node:20 AS build-apps +FROM node:24.22.0 AS build-apps ARG PUBLIC_BASE_PATH=/ ARG ENVIRONMENT=development ENV PUBLIC_BASE_PATH=${PUBLIC_BASE_PATH} @@ -49,7 +49,7 @@ COPY --chown=node:node front/viewer/index.html.template ./src/index.html.templat COPY --chown=node:node front/viewer/webpack.config.js.template ./webpack.config.js.template # Cap the viewer webpack heap so the build works when Docker VM RAM < 8 GiB. # Local npm run build uses 8192 MiB (the script default) when NODE_OPTIONS is unset. -ENV NODE_OPTIONS="--openssl-legacy-provider --max-old-space-size=4096" +ENV NODE_OPTIONS="--max-old-space-size=4096" RUN export BUILD_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z") && \ export APP_VERSION=$(node -p "require('./package.json').version") && \ if [ "${ENVIRONMENT}" = "production" ]; then export FRONT_ENV=Production; export FRONT_PRODUCTION=true; else export FRONT_ENV=Development; export FRONT_PRODUCTION=false; fi && \ diff --git a/front/admin/sitmun-admin-app b/front/admin/sitmun-admin-app index d3ea569..8bcd38f 160000 --- a/front/admin/sitmun-admin-app +++ b/front/admin/sitmun-admin-app @@ -1 +1 @@ -Subproject commit d3ea569aef1c5267a84021abbc0f9ef06eb232b9 +Subproject commit 8bcd38f468995f2430c9dc875633b3799e019b9a diff --git a/front/viewer/sitmun-viewer-app b/front/viewer/sitmun-viewer-app index 0f782d2..0b392f1 160000 --- a/front/viewer/sitmun-viewer-app +++ b/front/viewer/sitmun-viewer-app @@ -1 +1 @@ -Subproject commit 0f782d2467efc995e8bcbe66f8e96040bb9ca758 +Subproject commit 0b392f140869631027f3a6532ec8ea40944ae54a