diff --git a/src/api/__tests__/print.api.spec.js b/src/api/__tests__/print.api.spec.js index acf668fc75..201b3f2e80 100644 --- a/src/api/__tests__/print.api.spec.js +++ b/src/api/__tests__/print.api.spec.js @@ -1,8 +1,15 @@ +// eslint-disable-next-line simple-import-sort/imports import { expect } from 'chai' import { describe, it } from 'vitest' import { PrintLayout, PrintLayoutAttribute } from '@/api/print.api.js' import { MIN_PRINT_SCALE_SIZE, PRINT_DPI_COMPENSATION } from '@/config/print.config' +// We need to import the router here to avoid error when initializing router plugins, this is +// needed since some store plugins might require access to router to get the query parameters +// (e.g. topic management plugin) +import router from '@/router' // eslint-disable-line no-unused-vars +import store from '@/store' // eslint-disable-line no-unused-vars + import { adjustWidth } from '@/utils/styleUtils' describe('Print API unit tests', () => { diff --git a/src/api/print.api.js b/src/api/print.api.js index 1495bf22bd..d76a7de8fd 100644 --- a/src/api/print.api.js +++ b/src/api/print.api.js @@ -15,6 +15,7 @@ import { getWmsBaseUrl, } from '@/config/baseUrl.config' import i18n from '@/modules/i18n' +import store from '@/store' import log from '@/utils/logging' import { adjustWidth } from '@/utils/styleUtils' @@ -23,13 +24,24 @@ const PRINTING_DEFAULT_POLL_TIMEOUT = 600000 // ms (10 minutes) const SERVICE_PRINT_URL = `${getViewerDedicatedServicesBaseUrl()}print3/print/mapviewer` const MAX_PRINT_SPEC_SIZE = 1 * 1024 * 1024 // 1MB in bytes (should be in sync with the backend) +/** + * Customizes the printing behavior for GeoAdmin. + * + * @extends BaseCustomizer + */ class GeoAdminCustomizer extends BaseCustomizer { - /** @param {string[]} layerIDsToExclude List of layer names to exclude from the print */ - constructor(layerIDsToExclude, printResolution) { - super() + /** + * @param {number[]} printExtent - The extent of the area to be printed. + * @param {string[]} layerIDsToExclude - An array of layer IDs to exclude from the print. + * @param {number} printResolution - The resolution for the print. + */ + constructor(printExtent, layerIDsToExclude, printResolution) { + super(printExtent) this.layerIDsToExclude = layerIDsToExclude this.printResolution = printResolution + this.layerFilter = this.layerFilter.bind(this) + this.geometryFilter = this.geometryFilter.bind(this) this.line = this.line.bind(this) this.text = this.text.bind(this) this.point = this.point.bind(this) @@ -349,7 +361,8 @@ async function transformOlMapToPrintParams(olMap, config) { if (!dpi) { throw new PrintError('Missing DPI for printing') } - const customizer = new GeoAdminCustomizer(excludedLayerIDs, dpi) + const customizer = new GeoAdminCustomizer(store.state.print.printExtent, excludedLayerIDs, dpi) + const attributionsOneLine = attributions.length > 0 ? `© ${attributions.join(', ')}` : '' try { diff --git a/src/modules/map/components/openlayers/utils/usePrintAreaRenderer.composable.js b/src/modules/map/components/openlayers/utils/usePrintAreaRenderer.composable.js index 7f6bbd838e..24eed4f1d7 100644 --- a/src/modules/map/components/openlayers/utils/usePrintAreaRenderer.composable.js +++ b/src/modules/map/components/openlayers/utils/usePrintAreaRenderer.composable.js @@ -118,6 +118,21 @@ export default function usePrintAreaRenderer(map) { const width = size[0] const printRectangle = calculatePageBoundsPixels(selectedScale.value, printLayoutSize.value) + const topLeftCoordinate = map.getCoordinateFromPixel([printRectangle[0], printRectangle[1]]) + const rightBottomCoordinate = map.getCoordinateFromPixel([ + printRectangle[2], + printRectangle[3], + ]) + + store.commit('setPrintExtent', { + printExtent: [ + topLeftCoordinate[0], // minX + rightBottomCoordinate[1], // minY + rightBottomCoordinate[0], // maxX + topLeftCoordinate[1], // maxY + ], + dispatcher, + }) const minx = printRectangle[0] const miny = printRectangle[1] diff --git a/src/store/modules/print.store.js b/src/store/modules/print.store.js index a959914a15..3ba6e181ab 100644 --- a/src/store/modules/print.store.js +++ b/src/store/modules/print.store.js @@ -7,6 +7,7 @@ export default { selectedLayout: null, selectedScale: null, printSectionShown: false, + printExtent: [], }, getters: { printLayoutSize(state) { @@ -44,11 +45,15 @@ export default { setPrintSectionShown({ commit }, { show, dispatcher }) { commit('setPrintSectionShown', { show, dispatcher }) }, + setPrintExtent({ commit }, { printExtent, dispatcher }) { + commit('setPrintExtent', { printExtent, dispatcher }) + }, }, mutations: { setPrintLayouts: (state, { layouts }) => (state.layouts = layouts), setSelectedLayout: (state, { layout }) => (state.selectedLayout = layout), setSelectedScale: (state, { scale }) => (state.selectedScale = scale), setPrintSectionShown: (state, { show }) => (state.printSectionShown = show), + setPrintExtent: (state, { printExtent }) => (state.printExtent = printExtent), }, } diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index f234197699..b07d202f0d 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -897,6 +897,7 @@ Cypress.Commands.add('checkOlLayer', (args = null) => { description: `[${layer.id}] waitUntil layer.rendered`, errorMsg: `[${layer.id}] layer.rendered is not true`, }) + cy.log(`[${layer.id}] layer at index ${index} is rendered ${olLayer.rendered}`) }) }) invisibleLayers.forEach((layer) => { diff --git a/tests/cypress/tests-e2e/print.cy.js b/tests/cypress/tests-e2e/print.cy.js index bfd31fbb83..b3511274e2 100644 --- a/tests/cypress/tests-e2e/print.cy.js +++ b/tests/cypress/tests-e2e/print.cy.js @@ -7,6 +7,12 @@ import { formatThousand } from '@/utils/numberUtils.js' const printID = 'print-123456789' +function checkZoom(customZoom) { + cy.readStoreValue('state.position.zoom').should((zoom) => { + expect(zoom).to.equal(customZoom) + }) +} + describe('Testing print', () => { beforeEach(() => { cy.viewport(1920, 1080) @@ -178,16 +184,22 @@ describe('Testing print', () => { }) }) context('Send print request with layers', () => { - function startPrintWithKml(kmlFixture) { + function startPrintWithKml(kmlFixture, zoom = 9, center = '2655000,1203250') { + interceptPrintRequest() + interceptPrintStatus() + interceptDownloadReport() + cy.intercept('HEAD', '**/**.kml', { headers: { 'Content-Type': 'application/vnd.google-earth.kml+xml' }, }).as('kmlHeadRequest') cy.intercept('GET', '**/**.kml', { fixture: kmlFixture }).as('kmlGetRequest') + const kmlID = `${getServiceKmlBaseUrl()}some-kml-file.kml` cy.goToMapView( { - layers: `KML|${getServiceKmlBaseUrl()}some-kml-file.kml`, - z: 9, + layers: `KML|${kmlID}`, + z: zoom, + center: center, }, true ) @@ -202,6 +214,7 @@ describe('Testing print', () => { cy.get('[data-cy="print-map-button"]').should('be.visible').click() cy.get('[data-cy="abort-print-button"]').should('be.visible') + return kmlID } it('should send a print request to mapfishprint (with layers added)', () => { @@ -295,7 +308,10 @@ describe('Testing print', () => { }) }) it('should send a print request correctly to mapfishprint (with KML layer)', () => { - startPrintWithKml('import-tool/external-kml-file.kml') + const customZoom = 13 + const kmlID = startPrintWithKml('import-tool/external-kml-file.kml', customZoom) + checkZoom(customZoom) + cy.checkOlLayer(['test.background.layer2', kmlID]) cy.wait('@printRequest').then((interception) => { expect(interception.request.body).to.haveOwnProperty('layout') @@ -312,12 +328,109 @@ describe('Testing print', () => { ) const mapAttributes = attributes.map - expect(mapAttributes['scale']).to.equals(5000) + expect(mapAttributes['scale']).to.equals(2500000) expect(mapAttributes['dpi']).to.equals(254) expect(mapAttributes['projection']).to.equals('EPSG:2056') const layers = mapAttributes.layers expect(layers).to.be.an('array') + cy.log('Layers:', layers) + cy.log('Layer 0:', layers[0]['type']) + + expect(layers).to.have.length(2) + expect(layers[0]['type']).to.equals('geojson') + expect(layers[0]['geoJson']['features']).to.have.length(1) + expect(layers[0]['geoJson']['features'][0]['properties']).to.haveOwnProperty( + '_mfp_style' + ) + expect(layers[0]['geoJson']['features'][0]['properties']['_mfp_style']).to.equal( + '1' + ) + expect(layers[0]['style']).to.haveOwnProperty("[_mfp_style = '1']") + }) + }) + it('should send a print request correctly to mapfishprint (with KML layer) - with import layer', () => { + interceptPrintRequest() + interceptPrintStatus() + interceptDownloadReport() + + cy.goToMapView({}, true) + cy.readStoreValue('state.layers.activeLayers').should('be.empty') + cy.openMenuIfMobile() + cy.get('[data-cy="menu-tray-tool-section"]:visible').click() + cy.get('[data-cy="menu-advanced-tools-import-file"]:visible').click() + + cy.get('[data-cy="import-file-content"]').should('be.visible') + cy.get('[data-cy="import-file-online-content"]').should('be.visible') + + const localKmlFileName = 'external-kml-file.kml' + const localKmlFile = `/import-tool/${localKmlFileName}` + + // Test local import + cy.log('Switch to local import') + cy.get('[data-cy="import-file-local-btn"]:visible').click() + cy.get('[data-cy="import-file-local-content"]').should('be.visible') + + // Attach a local KML file + cy.log('Test add a local KML file') + cy.fixture(localKmlFile).as('kmlFile') + cy.get('[data-cy="file-input"]').selectFile( + { contents: '@kmlFile', fileName: localKmlFile }, + { force: true } + ) + cy.get('[data-cy="file-input"]').selectFile('@kmlFile', { + force: true, + }) + cy.get('[data-cy="import-file-load-button"]:visible').click() + + // Assertions for successful import + cy.get('[data-cy="file-input-text"]') + .should('have.class', 'is-valid') + .should('not.have.class', 'is-invalid') + cy.get('[data-cy="file-input-valid-feedback"]') + .should('be.visible') + .contains('File successfully imported') + cy.get('[data-cy="import-file-load-button"]').should('be.visible').contains('Import') + cy.get('[data-cy="import-file-online-content"]').should('not.be.visible') + cy.readStoreValue('state.layers.activeLayers').should('have.length', 1) + + // Close the import tool + cy.get('[data-cy="import-file-close-button"]:visible').click() + cy.get('[data-cy="import-file-content"]').should('not.exist') + + // Print + cy.get('[data-cy="menu-print-section"]').should('be.visible').click() + cy.get('[data-cy="menu-print-form"]').should('be.visible') + + cy.get('[data-cy="print-map-button"]').should('be.visible').click() + cy.get('[data-cy="abort-print-button"]').should('be.visible') + + cy.checkOlLayer(['test.background.layer2', localKmlFileName]) + + cy.wait('@printRequest').then((interception) => { + expect(interception.request.body).to.haveOwnProperty('layout') + expect(interception.request.body['layout']).to.equal('1. A4 landscape') + expect(interception.request.body).to.haveOwnProperty('format') + expect(interception.request.body['format']).to.equal('pdf') + + const attributes = interception.request.body.attributes + expect(attributes).to.haveOwnProperty('printLegend') + expect(attributes['printLegend']).to.equals(0) + expect(attributes).to.haveOwnProperty('qrimage') + expect(attributes['qrimage']).to.contains( + encodeURIComponent('https://s.geo.admin.ch/0000000') + ) + + const mapAttributes = attributes.map + expect(mapAttributes['scale']).to.equals(10000) + expect(mapAttributes['dpi']).to.equals(254) + expect(mapAttributes['projection']).to.equals('EPSG:2056') + + const layers = mapAttributes.layers + expect(layers).to.be.an('array') + cy.log('Layers:', layers) + cy.log('Layer 0:', layers[0]['type']) + expect(layers).to.have.length(2) expect(layers[0]['type']).to.equals('geojson') expect(layers[0]['geoJson']['features']).to.have.length(1) @@ -340,7 +453,7 @@ describe('Testing print', () => { cy.get('[data-cy="import-file-content"]').should('be.visible') cy.get('[data-cy="import-file-online-content"]').should('be.visible') - const localGpxlFile = 'print/line-and-marker.gpx' + const localGpxFile = '/print/line-and-marker.gpx' // Test local import cy.log('Switch to local import') @@ -349,8 +462,12 @@ describe('Testing print', () => { // Attach a local GPX file cy.log('Test add a local GPX file') - cy.fixture(localGpxlFile, null).as('gpxFixture') - cy.get('[data-cy="file-input"]').selectFile('@gpxFixture', { + cy.fixture(localGpxFile).as('gpxFile') + cy.get('[data-cy="file-input"]').selectFile( + { contents: '@gpxFile', fileName: localGpxFile }, + { force: true } + ) + cy.get('[data-cy="file-input"]').selectFile('@gpxFile', { force: true, }) cy.get('[data-cy="import-file-load-button"]:visible').click() @@ -398,6 +515,8 @@ describe('Testing print', () => { const layers = mapAttributes.layers expect(layers).to.be.an('array') + cy.log('Layers:', layers) + cy.log('Layer 0:', layers[0]['type']) expect(layers).to.have.length(2) // In this GPX layer, htere are two features (a line and a point). @@ -424,7 +543,10 @@ describe('Testing print', () => { }) /** We need to ensure the structure of the query sent is correct */ it('should send a print request correctly to mapfishprint (icon and label)', () => { - startPrintWithKml('print/label.kml') + const customZoom = 13 + const kmlID = startPrintWithKml('print/label.kml', customZoom) + checkZoom(customZoom) + cy.checkOlLayer(['test.background.layer2', kmlID]) cy.wait('@printRequest').then((interception) => { expect(interception.request.body).to.haveOwnProperty('layout') @@ -442,10 +564,15 @@ describe('Testing print', () => { expect(mapAttributes).to.haveOwnProperty('projection') expect(mapAttributes).to.haveOwnProperty('layers') + expect(mapAttributes['scale']).to.equals(2500000) + expect(mapAttributes['dpi']).to.equals(254) + expect(mapAttributes['projection']).to.equals('EPSG:2056') const layers = mapAttributes.layers expect(layers).to.be.an('array') + cy.log('Layers:', layers) + cy.log('Layer 0:', layers[0]['type']) expect(layers).to.have.length(2) const geoJsonLayer = layers[0] @@ -493,7 +620,22 @@ describe('Testing print', () => { }) }) it('should send a print request correctly to mapfishprint (KML from old geoadmin)', () => { - startPrintWithKml('print/old-geoadmin-label.kml') + const customZoom = 13 + const kmlID = startPrintWithKml('print/old-geoadmin-label.kml', customZoom) + + checkZoom(customZoom) + + cy.readStoreValue('getters.centerEpsg4326').should((center) => { + expect(center[0]).to.eq(8.161492) + expect(center[1]).to.eq(46.978042) + }) + + cy.readStoreValue('state.position.center').should((center) => { + expect(center[0]).to.eq(2655000) + expect(center[1]).to.eq(1203250) + }) + + cy.checkOlLayer(['test.background.layer2', kmlID]) cy.wait('@printRequest').then((interception) => { expect(interception.request.body).to.haveOwnProperty('layout') @@ -511,10 +653,15 @@ describe('Testing print', () => { expect(mapAttributes).to.haveOwnProperty('projection') expect(mapAttributes).to.haveOwnProperty('layers') + expect(mapAttributes['scale']).to.equals(2500000) + expect(mapAttributes['dpi']).to.equals(254) + expect(mapAttributes['projection']).to.equals('EPSG:2056') const layers = mapAttributes.layers expect(layers).to.be.an('array') + cy.log('Layers:', layers) + cy.log('Layer 0:', layers[0]['type']) expect(layers).to.have.length(2) const geoJsonLayer = layers[0]