From 88a2dd887028d976ee199cc9f7e5ad60e6e42134 Mon Sep 17 00:00:00 2001 From: robin-dunn <58361313+robin-dunn@users.noreply.github.com> Date: Tue, 17 Mar 2026 20:17:04 +0000 Subject: [PATCH 1/4] Go to no-edp page --- src/server/quote/map/controller.js | 50 +++--------- src/server/quote/map/controller.test.js | 82 ++------------------ src/server/quote/map/form-validation.js | 11 --- src/server/quote/map/form-validation.test.js | 28 ------- src/server/quote/map/get-next-page.js | 9 --- src/server/quote/map/get-next-page.test.js | 19 ----- src/server/quote/map/index.njk | 29 +------ src/server/quote/map/page.test.js | 70 ++++++----------- src/server/quote/map/routes.js | 21 ----- src/server/quote/no-edp/get-view-model.js | 3 +- src/server/quote/no-edp/page.test.js | 2 +- 11 files changed, 44 insertions(+), 280 deletions(-) delete mode 100644 src/server/quote/map/form-validation.js delete mode 100644 src/server/quote/map/form-validation.test.js delete mode 100644 src/server/quote/map/get-next-page.js delete mode 100644 src/server/quote/map/get-next-page.test.js diff --git a/src/server/quote/map/controller.js b/src/server/quote/map/controller.js index 0737fa1..c99ec06 100644 --- a/src/server/quote/map/controller.js +++ b/src/server/quote/map/controller.js @@ -1,15 +1,9 @@ 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 { routePath as noEdpPath } from '../no-edp/routes.js' import getViewModel from './get-view-model.js' -const selfPath = '/quote/map' - const logger = createLogger() export function handler(request, h) { @@ -20,24 +14,14 @@ export function handler(request, h) { 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 + ...viewModel }) } export function postHandler(request, h) { - const { boundaryCorrect } = request.payload const boundaryGeojson = request.yar.get('boundaryGeojson') if (!boundaryGeojson) { @@ -46,30 +30,14 @@ export function postHandler(request, h) { 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') + if (intersectsEdp) { + logger.info('map - boundary intersects EDP, saved to quote data') + 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/map/controller.test.js index 0d5cb9c..274d265 100644 --- a/src/server/quote/map/controller.test.js +++ b/src/server/quote/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 = { @@ -54,7 +47,6 @@ describe('map controller', () => { beforeEach(() => { vi.clearAllMocks() - vi.mocked(getValidationFlashFromCache).mockReturnValue(null) }) describe('handler (GET)', () => { @@ -78,56 +70,25 @@ describe('map controller', () => { 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) @@ -135,43 +96,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/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/index.njk b/src/server/quote/map/index.njk index fe37582..3883a7e 100644 --- a/src/server/quote/map/index.njk +++ b/src/server/quote/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() }} @@ -15,13 +15,6 @@
@@ -73,30 +66,14 @@
- Intersects EDP: No + There are no EDPs within the red line boundary.
diff --git a/src/server/quote/map/page.test.js b/src/server/quote/map/page.test.js index 9f7a723..b0301ac 100644 --- a/src/server/quote/map/page.test.js +++ b/src/server/quote/map/page.test.js @@ -1,14 +1,8 @@ import { JSDOM } from 'jsdom' -import { - getByRole, - getByLabelText, - queryByLabelText -} from '@testing-library/dom' +import { getByRole, queryByLabelText } 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,30 @@ describe('Boundary map page', () => { 'href', '/quote/upload-boundary' ) - expect(getByLabelText(document, 'Yes, continue')).not.toBeChecked() + expect(queryByLabelText(document, 'Yes, continue')).toBeNull() expect( - getByLabelText(document, 'No, upload a different file') - ).not.toBeChecked() + queryByLabelText(document, 'No, upload a different file') + ).toBeNull() 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,50 +150,16 @@ 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 () => { - 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 () => { + 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: 'no' }, + formData: {}, cookie }) expect(response.statusCode).toBe(302) - expect(response.headers.location).toBe('/quote/upload-boundary') + expect(response.headers.location).toBe('/quote/no-edp') }) }) diff --git a/src/server/quote/map/routes.js b/src/server/quote/map/routes.js index 5c74f01..27be813 100644 --- a/src/server/quote/map/routes.js +++ b/src/server/quote/map/routes.js @@ -1,8 +1,4 @@ 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' @@ -15,23 +11,6 @@ export default [ { 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..452f2f9 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 '../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..7ffe603 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/map' ) }) }) From 0d2b90816c79a71d011127cc98c447b45518034b Mon Sep 17 00:00:00 2001 From: robin-dunn <58361313+robin-dunn@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:03:14 +0000 Subject: [PATCH 2/4] govukInsetText --- src/server/quote/map/index.njk | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/quote/map/index.njk b/src/server/quote/map/index.njk index 3883a7e..fbd9116 100644 --- a/src/server/quote/map/index.njk +++ b/src/server/quote/map/index.njk @@ -65,9 +65,9 @@ {% else %}- There are no EDPs within the red line boundary. -
+ {{ govukInsetText({ + text: "There are no EDPs within the red line boundary." + }) }}