diff --git a/src/server/quote/check-your-answers/index.njk b/src/server/quote/check-your-answers/index.njk index 31f3182..6122411 100644 --- a/src/server/quote/check-your-answers/index.njk +++ b/src/server/quote/check-your-answers/index.njk @@ -17,7 +17,7 @@ { key: { text: "Red line boundary" }, value: { text: 'Added' }, - actions: { items: [{ href: "/quote/upload-boundary" if formSubmitData.boundaryEntryType === 'upload' else "/quote/map", text: "Change", visuallyHiddenText: "red line boundary" }] } + actions: { items: [{ href: "/quote/upload-boundary" if formSubmitData.boundaryEntryType === 'upload' else "/quote/upload-preview-map", text: "Change", visuallyHiddenText: "red line boundary" }] } } if formSubmitData.boundaryEntryType, { key: { text: "Development types" }, diff --git a/src/server/quote/check-your-answers/page.test.js b/src/server/quote/check-your-answers/page.test.js index 4858b1b..8c3e293 100644 --- a/src/server/quote/check-your-answers/page.test.js +++ b/src/server/quote/check-your-answers/page.test.js @@ -128,7 +128,7 @@ describe('Check your answers page', () => { getByRole(document, 'link', { name: 'Changered line boundary' }) - ).toHaveAttribute('href', '/quote/map') + ).toHaveAttribute('href', '/quote/upload-preview-map') }) it('should redirect to the confirmation page if Submit is clicked', async () => { diff --git a/src/server/quote/index.js b/src/server/quote/index.js index f3126f1..70f3c0e 100644 --- a/src/server/quote/index.js +++ b/src/server/quote/index.js @@ -6,7 +6,7 @@ import routesDevelopmentType from './development-types/routes.js' import routesEmail from './email/routes.js' import routesUploadBoundary from './upload-boundary/routes.js' import routesUploadReceived from './upload-received/routes.js' -import routesMap from './map/routes.js' +import routesUploadPreviewMap from './upload-preview-map/routes.js' import routesCheckYourAnswers from './check-your-answers/routes.js' import routesConfirmation from './confirmation/routes.js' import routesPeopleCount from './people-count/routes.js' @@ -45,7 +45,7 @@ export const quote = { ...routesEmail, ...routesUploadBoundary, ...routesUploadReceived, - ...routesMap, + ...routesUploadPreviewMap, ...routesCheckYourAnswers, ...routesPeopleCount, ...routesConfirmation, diff --git a/src/server/quote/map/controller.js b/src/server/quote/map/controller.js deleted file mode 100644 index 0737fa1..0000000 --- a/src/server/quote/map/controller.js +++ /dev/null @@ -1,75 +0,0 @@ -import { createLogger } from '../../common/helpers/logging/logger.js' -import { saveQuoteDataToCache } from '../helpers/get-quote-session/index.js' -import { - getValidationFlashFromCache, - clearValidationFlashFromCache, - saveValidationFlashToCache -} from '../helpers/form-validation-session/index.js' -import { routePath as uploadBoundaryPath } from '../upload-boundary/routes.js' -import getViewModel from './get-view-model.js' - -const selfPath = '/quote/map' - -const logger = createLogger() - -export function handler(request, h) { - const boundaryGeojson = request.yar.get('boundaryGeojson') - - if (!boundaryGeojson) { - logger.info('map - no boundary data in session') - return h.redirect(uploadBoundaryPath) - } - - const flash = getValidationFlashFromCache(request) - let validationErrors - if (flash) { - validationErrors = flash.validationErrors - clearValidationFlashFromCache(request) - } - - const viewModel = getViewModel(boundaryGeojson) - - return h.view('quote/map/index', { - ...viewModel, - formSubmitData: flash?.formSubmitData ?? {}, - validationErrors - }) -} - -export function postHandler(request, h) { - const { boundaryCorrect } = request.payload - const boundaryGeojson = request.yar.get('boundaryGeojson') - - if (!boundaryGeojson) { - return h.redirect(uploadBoundaryPath) - } - - const intersectsEdp = boundaryGeojson?.intersects_edp ?? false - - if (!intersectsEdp && !boundaryCorrect) { - const validationErrors = { - summary: [ - { text: 'Select if the boundary is correct', href: '#boundaryCorrect' } - ], - messagesByFormField: { - boundaryCorrect: { text: 'Select if the boundary is correct' } - } - } - saveValidationFlashToCache(request, { - validationErrors, - formSubmitData: request.payload - }) - return h.redirect(selfPath) - } - - if (boundaryCorrect === 'no') { - request.yar.clear('boundaryGeojson') - return h.redirect(uploadBoundaryPath) - } - - saveQuoteDataToCache(request, { boundaryGeojson }) - request.yar.clear('boundaryGeojson') - logger.info('map - boundary confirmed, saved to quote data') - - return h.redirect('/quote/development-types') -} diff --git a/src/server/quote/map/form-validation.js b/src/server/quote/map/form-validation.js deleted file mode 100644 index 7305900..0000000 --- a/src/server/quote/map/form-validation.js +++ /dev/null @@ -1,11 +0,0 @@ -import joi from 'joi' - -const errorMessage = 'Select if the boundary is correct' - -export default function formValidation() { - return joi.object({ - boundaryCorrect: joi.string().valid('yes', 'no').optional().messages({ - 'any.only': errorMessage - }) - }) -} diff --git a/src/server/quote/map/form-validation.test.js b/src/server/quote/map/form-validation.test.js deleted file mode 100644 index 2d22c09..0000000 --- a/src/server/quote/map/form-validation.test.js +++ /dev/null @@ -1,28 +0,0 @@ -import { describe, it, expect } from 'vitest' -import getSchema from './form-validation.js' - -describe('map form validation', () => { - describe('boundaryCorrect', () => { - it('passes for "yes"', () => { - const { error } = getSchema().validate({ boundaryCorrect: 'yes' }) - expect(error).toBeUndefined() - }) - - it('passes for "no"', () => { - const { error } = getSchema().validate({ boundaryCorrect: 'no' }) - expect(error).toBeUndefined() - }) - - it('passes when absent (EDP flow submits without radio)', () => { - const { error } = getSchema().validate({}) - expect(error).toBeUndefined() - }) - - it('fails for an unrecognised value', () => { - const { error } = getSchema().validate({ - boundaryCorrect: 'something-else' - }) - expect(error.details[0].message).toBe('Select if the boundary is correct') - }) - }) -}) diff --git a/src/server/quote/map/get-next-page.js b/src/server/quote/map/get-next-page.js deleted file mode 100644 index 64f60b7..0000000 --- a/src/server/quote/map/get-next-page.js +++ /dev/null @@ -1,9 +0,0 @@ -import { routePath as routePathUploadBoundary } from '../upload-boundary/routes.js' - -export default function getNextPage({ boundaryCorrect }) { - if (boundaryCorrect === 'no') { - return routePathUploadBoundary - } - - return '/quote/development-types' -} diff --git a/src/server/quote/map/get-next-page.test.js b/src/server/quote/map/get-next-page.test.js deleted file mode 100644 index 77a4407..0000000 --- a/src/server/quote/map/get-next-page.test.js +++ /dev/null @@ -1,19 +0,0 @@ -import { describe, it, expect } from 'vitest' -import getNextPage from './get-next-page.js' -import { routePath as uploadBoundaryPath } from '../upload-boundary/routes.js' - -describe('getNextPage', () => { - it('should return upload-boundary path when boundaryCorrect is no', () => { - expect(getNextPage({ boundaryCorrect: 'no' })).toBe(uploadBoundaryPath) - }) - - it('should return development-types path when boundaryCorrect is yes', () => { - expect(getNextPage({ boundaryCorrect: 'yes' })).toBe( - '/quote/development-types' - ) - }) - - it('should return development-types path when boundaryCorrect is undefined', () => { - expect(getNextPage({})).toBe('/quote/development-types') - }) -}) diff --git a/src/server/quote/map/routes.js b/src/server/quote/map/routes.js deleted file mode 100644 index 5c74f01..0000000 --- a/src/server/quote/map/routes.js +++ /dev/null @@ -1,37 +0,0 @@ -import { handler, postHandler } from './controller.js' -import { mapValidationErrorsForDisplay } from '../../common/helpers/form-validation.js' -import { saveValidationFlashToCache } from '../helpers/form-validation-session/index.js' -import { statusCodes } from '../../common/constants/status-codes.js' -import formValidation from './form-validation.js' - -export const routePath = '/quote/map' - -export default [ - { - method: 'GET', - path: routePath, - handler - }, - { - method: 'POST', - path: routePath, - options: { - validate: { - payload: formValidation(), - failAction: (request, h, err) => { - const { payload } = request - const validationErrors = mapValidationErrorsForDisplay(err.details) - saveValidationFlashToCache(request, { - validationErrors, - formSubmitData: payload - }) - return h - .redirect(request.path) - .code(statusCodes.redirectAfterPost) - .takeover() - } - } - }, - handler: postHandler - } -] diff --git a/src/server/quote/no-edp/get-view-model.js b/src/server/quote/no-edp/get-view-model.js index 91180a5..103e084 100644 --- a/src/server/quote/no-edp/get-view-model.js +++ b/src/server/quote/no-edp/get-view-model.js @@ -1,4 +1,5 @@ import { getPageTitle } from '../../common/helpers/page-title.js' +import { routePath as mapPath } from '../upload-preview-map/routes.js' const title = 'Nature Restoration Fund levy is not available in this area' @@ -6,6 +7,6 @@ export default function getViewModel() { return { pageTitle: getPageTitle(title), pageHeading: title, - backLinkPath: '#' + backLinkPath: mapPath } } diff --git a/src/server/quote/no-edp/page.test.js b/src/server/quote/no-edp/page.test.js index 0a2db95..ddf5bb6 100644 --- a/src/server/quote/no-edp/page.test.js +++ b/src/server/quote/no-edp/page.test.js @@ -26,7 +26,7 @@ describe('No EDP page', () => { ) expect(getByRole(document, 'link', { name: 'Back' })).toHaveAttribute( 'href', - '#' + '/quote/upload-preview-map' ) }) }) diff --git a/src/server/quote/map/accessibility.test.js b/src/server/quote/upload-preview-map/accessibility.test.js similarity index 100% rename from src/server/quote/map/accessibility.test.js rename to src/server/quote/upload-preview-map/accessibility.test.js diff --git a/src/server/quote/upload-preview-map/controller.js b/src/server/quote/upload-preview-map/controller.js new file mode 100644 index 0000000..13d9fb5 --- /dev/null +++ b/src/server/quote/upload-preview-map/controller.js @@ -0,0 +1,44 @@ +import { createLogger } from '../../common/helpers/logging/logger.js' +import { saveQuoteDataToCache } from '../helpers/get-quote-session/index.js' +import { routePath as uploadBoundaryPath } from '../upload-boundary/routes.js' +import { routePath as noEdpPath } from '../no-edp/routes.js' +import getViewModel from './get-view-model.js' + +const logger = createLogger() + +export function handler(request, h) { + const boundaryGeojson = request.yar.get('boundaryGeojson') + + // Session may be missing if it expired or the user navigated here directly + if (!boundaryGeojson) { + logger.info('map - no boundary data in session') + return h.redirect(uploadBoundaryPath) + } + + const viewModel = getViewModel(boundaryGeojson) + + return h.view('quote/upload-preview-map/index', { + ...viewModel + }) +} + +export function postHandler(request, h) { + const boundaryGeojson = request.yar.get('boundaryGeojson') + + // Session may be missing if it expired or the user navigated here directly + if (!boundaryGeojson) { + return h.redirect(uploadBoundaryPath) + } + + const intersectsEdp = boundaryGeojson?.intersects_edp ?? false + + saveQuoteDataToCache(request, { boundaryGeojson }) + request.yar.clear('boundaryGeojson') + + if (intersectsEdp) { + return h.redirect('/quote/development-types') + } + + logger.info('map - boundary does not intersect EDP, saved to quote data') + return h.redirect(noEdpPath) +} diff --git a/src/server/quote/map/controller.test.js b/src/server/quote/upload-preview-map/controller.test.js similarity index 56% rename from src/server/quote/map/controller.test.js rename to src/server/quote/upload-preview-map/controller.test.js index 39ea17e..4e464b6 100644 --- a/src/server/quote/map/controller.test.js +++ b/src/server/quote/upload-preview-map/controller.test.js @@ -1,21 +1,14 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import { handler, postHandler } from './controller.js' import { routePath as uploadBoundaryPath } from '../upload-boundary/routes.js' +import { routePath as noEdpPath } from '../no-edp/routes.js' vi.mock('../helpers/get-quote-session/index.js', () => ({ saveQuoteDataToCache: vi.fn() })) -vi.mock('../helpers/form-validation-session/index.js', () => ({ - getValidationFlashFromCache: vi.fn(), - clearValidationFlashFromCache: vi.fn(), - saveValidationFlashToCache: vi.fn() -})) - const { saveQuoteDataToCache } = await import('../helpers/get-quote-session/index.js') -const { getValidationFlashFromCache, saveValidationFlashToCache } = - await import('../helpers/form-validation-session/index.js') describe('map controller', () => { const mockGeometry = { @@ -74,7 +67,6 @@ describe('map controller', () => { beforeEach(() => { vi.clearAllMocks() - vi.mocked(getValidationFlashFromCache).mockReturnValue(null) }) describe('handler (GET)', () => { @@ -94,60 +86,29 @@ describe('map controller', () => { handler(request, h) expect(h.view).toHaveBeenCalledWith( - 'quote/map/index', + 'quote/upload-preview-map/index', expect.objectContaining({ pageHeading: 'Boundary Map', featureCount: 1, - boundaryGeojson: JSON.stringify(mockGeometry), - formSubmitData: {}, - validationErrors: undefined - }) - ) - }) - - it('should include validation errors from flash', () => { - const mockErrors = { - validationErrors: { - summary: [{ text: 'Select if the boundary is correct' }], - messagesByFormField: { - boundaryCorrect: { text: 'Select if the boundary is correct' } - } - }, - formSubmitData: {} - } - vi.mocked(getValidationFlashFromCache).mockReturnValue(mockErrors) - - const h = createMockH() - const request = createMockRequest(mockGeojson) - - handler(request, h) - - expect(h.view).toHaveBeenCalledWith( - 'quote/map/index', - expect.objectContaining({ - validationErrors: mockErrors.validationErrors, - formSubmitData: {} + boundaryGeojson: JSON.stringify(mockGeometry) }) ) }) }) describe('postHandler (POST)', () => { - it('should redirect to upload-boundary when user says no', () => { + it('should redirect to upload-boundary when no geojson in session', () => { const h = createMockH() - const request = createMockRequest(mockGeojson) - request.payload = { boundaryCorrect: 'no' } + const request = createMockRequest(null) postHandler(request, h) - expect(request.yar.clear).toHaveBeenCalledWith('boundaryGeojson') expect(h.redirect).toHaveBeenCalledWith(uploadBoundaryPath) }) - it('should save to quote data and redirect when user confirms', () => { + it('should save and redirect to no-edp when boundary does not intersect EDP', () => { const h = createMockH() const request = createMockRequest(mockGeojson) - request.payload = { boundaryCorrect: 'yes' } postHandler(request, h) @@ -155,43 +116,12 @@ describe('map controller', () => { boundaryGeojson: mockGeojson }) expect(request.yar.clear).toHaveBeenCalledWith('boundaryGeojson') - expect(h.redirect).toHaveBeenCalledWith('/quote/development-types') - }) - - it('should redirect to upload-boundary when no geojson in session', () => { - const h = createMockH() - const request = createMockRequest(null) - request.payload = { boundaryCorrect: 'yes' } - - postHandler(request, h) - - expect(h.redirect).toHaveBeenCalledWith(uploadBoundaryPath) - }) - - it('should show validation error when no selection made and boundary does not intersect EDP', () => { - const h = createMockH() - const request = createMockRequest(mockGeojson) - request.payload = {} - - postHandler(request, h) - - expect(saveValidationFlashToCache).toHaveBeenCalledWith(request, { - validationErrors: expect.objectContaining({ - summary: [ - expect.objectContaining({ - text: 'Select if the boundary is correct' - }) - ] - }), - formSubmitData: {} - }) - expect(h.redirect).toHaveBeenCalledWith('/quote/map') + expect(h.redirect).toHaveBeenCalledWith(noEdpPath) }) it('should save and redirect to development-types when boundary intersects EDP', () => { const h = createMockH() const request = createMockRequest(mockEdpGeojson) - request.payload = {} postHandler(request, h) diff --git a/src/server/quote/map/get-view-model.js b/src/server/quote/upload-preview-map/get-view-model.js similarity index 100% rename from src/server/quote/map/get-view-model.js rename to src/server/quote/upload-preview-map/get-view-model.js diff --git a/src/server/quote/map/get-view-model.test.js b/src/server/quote/upload-preview-map/get-view-model.test.js similarity index 100% rename from src/server/quote/map/get-view-model.test.js rename to src/server/quote/upload-preview-map/get-view-model.test.js diff --git a/src/server/quote/map/index.njk b/src/server/quote/upload-preview-map/index.njk similarity index 73% rename from src/server/quote/map/index.njk rename to src/server/quote/upload-preview-map/index.njk index df16850..3359373 100644 --- a/src/server/quote/map/index.njk +++ b/src/server/quote/upload-preview-map/index.njk @@ -1,6 +1,6 @@ {% extends "layouts/page.njk" %} -{% block pageTitle %}{% if validationErrors %}Error: {% endif %}{{ pageTitle }}{% endblock %} +{% block pageTitle %}{{ pageTitle }}{% endblock %} {% block head %} {{ super() }} @@ -18,13 +18,6 @@
@@ -75,31 +68,15 @@ {% else %}
- Intersects EDP: No -
+ {{ govukInsetText({ + text: "There are no EDPs within the red line boundary." + }) }} diff --git a/src/server/quote/map/page.test.js b/src/server/quote/upload-preview-map/page.test.js similarity index 73% rename from src/server/quote/map/page.test.js rename to src/server/quote/upload-preview-map/page.test.js index 9f7a723..f89b228 100644 --- a/src/server/quote/map/page.test.js +++ b/src/server/quote/upload-preview-map/page.test.js @@ -1,14 +1,8 @@ import { JSDOM } from 'jsdom' -import { - getByRole, - getByLabelText, - queryByLabelText -} from '@testing-library/dom' +import { getByRole } from '@testing-library/dom' import { routePath } from './routes.js' import { setupTestServer } from '../../../test-utils/setup-test-server.js' -import { loadPage } from '../../../test-utils/load-page.js' import { submitForm } from '../../../test-utils/submit-form.js' -import { expectFieldsetError } from '../../../test-utils/assertions.js' import { withValidQuoteSession } from '../../../test-utils/with-valid-quote-session.js' import { checkBoundary } from '../../common/services/boundary.js' import { checkBoundaryPath } from '../upload-received/routes.js' @@ -107,14 +101,26 @@ describe('Boundary map page', () => { 'href', '/quote/upload-boundary' ) - expect(getByLabelText(document, 'Yes, continue')).not.toBeChecked() - expect( - getByLabelText(document, 'No, upload a different file') - ).not.toBeChecked() const csrfToken = document.querySelector('form input[name="csrfToken"]') expect(csrfToken).toBeInTheDocument() }) + it('should display no EDPs message', async () => { + const { document } = await loadPageWithSession(getServer()) + + expect(document.body.textContent).toContain( + 'There are no EDPs within the red line boundary.' + ) + }) + + it('should show save and continue button', async () => { + const { document } = await loadPageWithSession(getServer()) + + expect( + getByRole(document, 'button', { name: 'Save and continue' }) + ).toBeInTheDocument() + }) + it('should display feature count', async () => { const { document } = await loadPageWithSession(getServer()) @@ -140,54 +146,20 @@ describe('Boundary map page', () => { expect(mapScripts.length).toBeGreaterThanOrEqual(1) }) - it('should show a validation error after submitting without a selection', async () => { - const cookie = await setupSession(getServer()) - const { response, cookie: postCookie } = await submitForm({ - requestUrl: routePath, - server: getServer(), - formData: {}, - cookie - }) - expect(response.statusCode).toBe(302) - expect(response.headers.location).toBe(routePath) - - const document = await loadPage({ - requestUrl: routePath, - server: getServer(), - cookie: postCookie - }) - expectFieldsetError({ - document, - errorMessage: 'Select if the boundary is correct' - }) - }) - - it('should redirect to development-types when user confirms', async () => { + it('should redirect to no-edp page on save and continue', async () => { const cookie = await setupSession(getServer()) const { response } = await submitForm({ requestUrl: routePath, server: getServer(), - formData: { boundaryCorrect: 'yes' }, - cookie - }) - expect(response.statusCode).toBe(302) - expect(response.headers.location).toBe('/quote/development-types') - }) - - it('should redirect to upload-boundary when user selects no', async () => { - const cookie = await setupSession(getServer()) - const { response } = await submitForm({ - requestUrl: routePath, - server: getServer(), - formData: { boundaryCorrect: 'no' }, + formData: {}, cookie }) expect(response.statusCode).toBe(302) - expect(response.headers.location).toBe('/quote/upload-boundary') + expect(response.headers.location).toBe('/quote/no-edp') }) }) - it('should redirect to upload-boundary when no geojson in session', async () => { + it('should redirect to upload-boundary if the session cache does not contain boundary data', async () => { const sessionCookie = await withValidQuoteSession(getServer()) const response = await getServer().inject({ method: 'GET', @@ -219,18 +191,6 @@ describe('Boundary map page', () => { expect(editButton).toBeDisabled() }) - it('should not show the boundary correct radio buttons', async () => { - const { document } = await loadPageWithSession( - getServer(), - mockEdpGeojson - ) - - expect(queryByLabelText(document, 'Yes, continue')).toBeNull() - expect( - queryByLabelText(document, 'No, upload a different file') - ).toBeNull() - }) - it('should show save and continue button', async () => { const { document } = await loadPageWithSession( getServer(), diff --git a/src/server/quote/upload-preview-map/routes.js b/src/server/quote/upload-preview-map/routes.js new file mode 100644 index 0000000..0fe3eec --- /dev/null +++ b/src/server/quote/upload-preview-map/routes.js @@ -0,0 +1,16 @@ +import { handler, postHandler } from './controller.js' + +export const routePath = '/quote/upload-preview-map' + +export default [ + { + method: 'GET', + path: routePath, + handler + }, + { + method: 'POST', + path: routePath, + handler: postHandler + } +] diff --git a/src/server/quote/upload-received/controller.js b/src/server/quote/upload-received/controller.js index cf5d13c..0872c86 100644 --- a/src/server/quote/upload-received/controller.js +++ b/src/server/quote/upload-received/controller.js @@ -67,5 +67,5 @@ export async function checkBoundaryHandler(request, h) { request.yar.set('boundaryGeojson', result.geojson) request.yar.clear('pendingUploadId') - return h.redirect('/quote/map') + return h.redirect('/quote/upload-preview-map') } diff --git a/src/server/quote/upload-received/controller.test.js b/src/server/quote/upload-received/controller.test.js index d2efccf..0038fd6 100644 --- a/src/server/quote/upload-received/controller.test.js +++ b/src/server/quote/upload-received/controller.test.js @@ -149,7 +149,7 @@ describe('checkBoundaryHandler', () => { expect(checkBoundary).toHaveBeenCalledWith('test-upload-id') expect(request.yar.set).toHaveBeenCalledWith('boundaryGeojson', mockGeojson) expect(request.yar.clear).toHaveBeenCalledWith('pendingUploadId') - expect(h.redirect).toHaveBeenCalledWith('/quote/map') + expect(h.redirect).toHaveBeenCalledWith('/quote/upload-preview-map') }) it('should render error page when boundary check fails', async () => {