Skip to content

Commit

Permalink
Merge pull request #12 from digital-land/yourEmailPage
Browse files Browse the repository at this point in the history
Your email page
  • Loading branch information
GeorgeGoodall authored Nov 21, 2023
2 parents a96fc6d + bfe1e35 commit 7f33a5c
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 59 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"mock:api": "node ./test/mock-api/index.js",
"scss": "sass --quiet-deps --load-path=./ src/assets/scss:public/stylesheets",
"scss:watch": "sass --quiet-deps --load-path=./ --watch src/assets/scss:public/stylesheets",
"mock:api": "node ./test/mock-api/index.js",
"test": "npm run test:unit && npm run test:integration && npm run test:contract && npm run test:acceptance",
"test:unit": "vitest run test/unit",
"test:integration": "vitest run test/integration",
Expand Down Expand Up @@ -43,6 +42,7 @@
"@x-govuk/govuk-prototype-components": "^2.0.3",
"@x-govuk/govuk-prototype-filters": "^1.2.0",
"axios": "^1.6.2",
"email-validator": "^2.0.4",
"express": "^4.18.2",
"govuk-frontend": "^4.7.0",
"hmpo-config": "^3.0.0",
Expand Down
2 changes: 1 addition & 1 deletion playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export default defineConfig({

/* Run your local dev server before starting the tests */
webServer: {
command: 'NODE_ENV=test npm run start', // 'concurrently "NODE_ENV=test npm run start" "NODE_ENV=test npm run mock:api"',
command: 'concurrently "NODE_ENV=test npm run start" "NODE_ENV=test npm run mock:api"',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI
}
Expand Down
20 changes: 20 additions & 0 deletions src/controllers/emailAddressController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict'

import MyController from './MyController.js'

import { validate } from 'email-validator'

class EmailAddressController extends MyController {
// perform some additional validation on the email address
validate (req, res, next) {
if (!validate(req.form.values['email-address'])) {
const errors = {}
errors['email-address'] = new this.Error('email-address', {}, req.form.values['email-address'], 'Email address is not valid')
next(errors)
} else {
super.validate(req, res, next)
}
}
}

export default EmailAddressController
8 changes: 7 additions & 1 deletion src/routes/form-wizard/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import datasetController from '../../controllers/datasetController.js'
import uploadController from '../../controllers/uploadController.js'
import errorsController from '../../controllers/errorsController.js'
import MyController from '../../controllers/MyController.js'
import emailAddressController from '../../controllers/emailAddressController.js'

export default {
'/': {
Expand Down Expand Up @@ -35,6 +36,11 @@ export default {
},
'/no-errors': {
controller: MyController,
next: 'transformations'
next: 'email-address'
},
'/email-address': {
controller: emailAddressController,
fields: ['email-address'],
next: 'name'
}
}
70 changes: 70 additions & 0 deletions src/views/email-address.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{% extends "layouts/main.html" %}

{% from 'govuk/components/button/macro.njk' import govukButton %}
{% from 'govuk/components/input/macro.njk' import govukInput %}
{% from 'govuk/components/error-message/macro.njk' import govukErrorMessage %}
{% from 'govuk/components/error-summary/macro.njk' import govukErrorSummary %}

{% set pageName = 'Your email address' %}

{% set errorMessage = 'Please enter a valid email address' %}


{% if 'email-address' in errors %}
{% set datasetError = true %}
{% endif %}

{% block pageTitle %}
{% if datasetError %}
Error: {{super()}}
{% else %}
{{super()}}
{% endif %}
{% endblock %}

{% block content %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
{% if datasetError %}
{{ govukErrorSummary({
titleText: "There is a problem",
errorList: [
{
text: errorMessage,
href: "#email-address"
}
]
}) }}
{% endif %}

<form novalidate method="post">

{{ govukInput({
id: "email-address",
name: "email-address",
label: {
text: pageName,
isPageHeading: true,
classes: 'govuk-label--l'
},
type: "email",
autocomplete: "email",
spellcheck: false,
value: data.check.emailAddress,
errorMessage: {
text: errorMessage
} if datasetError else undefined
}) }}






{{ govukButton({
text: "Continue"
}) }}
</form>
</div>
</div>
{% endblock %}
5 changes: 2 additions & 3 deletions src/views/no-errors.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ <h1 class="govuk-heading-l">

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<form novalidate method="post">
<form method="post">
{{ govukButton({
text: "Continue",
href: '/email-address'
text: "Continue"
}) }}
</form>

Expand Down
9 changes: 8 additions & 1 deletion test/acceptance/upload_data.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test('Enter form information', async ({ page }) => {
})

// currently skipping this test as im not sure how to go about providing the pipeline runner api
test.skip('Enter form information and upload a file with errors and without errors', async ({ page }) => {
test('Enter form information and upload a file with errors and without errors', async ({ page }) => {
await page.goto('/')
await page.getByRole('button', { name: 'Start now' }).click()

Expand Down Expand Up @@ -60,4 +60,11 @@ test.skip('Enter form information and upload a file with errors and without erro
await page.getByRole('button', { name: 'Continue' }).click()

await page.waitForURL('**/no-errors')
await page.getByRole('button', { name: 'Continue' }).click()

await page.waitForURL('**/email-address')
await page.getByLabel('Your email address').fill('[email protected]')
await page.getByRole('button', { name: 'Continue' }).click()

await page.waitForURL('**/name')
})
83 changes: 51 additions & 32 deletions test/acceptance/validation_errors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,7 @@ test('when the user clicks continue on the data subject page without entering a
await page.getByRole('button', { name: 'Start now' }).click()
await page.getByRole('button', { name: 'Continue' }).click()

await page.waitForSelector('input#data-subject.govuk-radios__input')

const errorLink = await page.getByRole('link', { name: 'Please select a data subject' })
const fieldError = await page.getByText('Error: Please select a data subject')
const errorSummary = await page.getByText('There is a problem')

expect(await errorSummary.isVisible(), 'Page should show the error summary').toBeTruthy()
expect(await errorLink.isVisible(), 'Page should the error message that is a link to the problem field').toBeTruthy()
expect(await fieldError.isVisible(), 'Page should show the error message next to the problem field').toBeTruthy()
await errorLink.click()
const problemFieldIsFocused = await page.$eval('input#data-subject.govuk-radios__input', (el) => el === document.activeElement)
expect(problemFieldIsFocused, 'The focus should be on the problem field').toBeTruthy()

expect(await page.title(), 'Page title should indicate there\'s an error').toMatch(/Error: .*/)
await testErrorMessage(page, 'input#data-subject.govuk-radios__input', 'Please select a data subject')
})

test('when the user clicks continue on the dataset page without entering a dataset, the page correctly indicates there\'s an error', async ({ page }) => {
Expand All @@ -33,23 +20,29 @@ test('when the user clicks continue on the dataset page without entering a datas
// dataset page
await page.getByRole('button', { name: 'Continue' }).click()

await page.waitForSelector('input#dataset.govuk-radios__input')
await testErrorMessage(page, 'input#dataset.govuk-radios__input', 'Please select a dataset')
})

const errorLink = await page.getByRole('link', { name: 'Please select a dataset' })
const fieldError = await page.getByText('Error: Please select a dataset')
const errorSummary = await page.getByText('There is a problem')
test('when the user clicks continue on the file upload page without selecting a file, the page correctly indicates there\'s an error', async ({ page }) => {
await page.goto('/')
// start page
await page.getByRole('button', { name: 'Start now' }).click()

expect(await errorSummary.isVisible(), 'Page should show the error summary').toBeTruthy()
expect(await errorLink.isVisible(), 'Page should the error message that is a link to the problem field').toBeTruthy()
expect(await fieldError.isVisible(), 'Page should show the error message next to the problem field').toBeTruthy()
await errorLink.click()
const problemFieldIsFocused = await page.$eval('input#dataset.govuk-radios__input', (el) => el === document.activeElement)
expect(problemFieldIsFocused, 'The focus should be on the problem field').toBeTruthy()
// data subject page
await page.getByLabel('Conservation area').check()
await page.getByRole('button', { name: 'Continue' }).click()

expect(await page.title(), 'Page title should indicate there\'s an error').toMatch(/Error: .*/)
// dataset page
await page.getByLabel('Conservation area dataset').check()
await page.getByRole('button', { name: 'Continue' }).click()

// file upload page
await page.getByRole('button', { name: 'Continue' }).click()

await testErrorMessage(page, 'input#datafile.govuk-file-upload', 'Please select a file')
})

test('when the user clicks continue on the file upload page without selecting a file, the page correctly indicates there\'s an error', async ({ page }) => {
test('when the user clicks continue on the email page without entering a valid email, the page correctly indicates there\'s an error', async ({ page }) => {
await page.goto('/')
// start page
await page.getByRole('button', { name: 'Start now' }).click()
Expand All @@ -66,17 +59,43 @@ test('when the user clicks continue on the file upload page without selecting a
await page.getByRole('button', { name: 'Continue' }).click()
await page.waitForSelector('input#datafile.govuk-file-upload')

const errorLink = await page.getByRole('link', { name: 'Please select a file' })
const fieldError = await page.getByText('Error: Please select a file')
const fileChooserPromise = page.waitForEvent('filechooser')
await page.getByText('Upload data').click()
const fileChooser = await fileChooserPromise
await fileChooser.setFiles('test/testData/conservation-area-ok.csv')

await page.getByRole('button', { name: 'Continue' }).click()

await page.waitForURL('**/no-errors')
await page.getByRole('button', { name: 'Continue' }).click()

await page.waitForURL('**/email-address')

await page.getByRole('button', { name: 'Continue' }).click()

await testErrorMessage(page, 'input#email-address.govuk-input', 'Please enter a valid email address')

await page.getByLabel('Your email address').fill('invalidEmail1')
await page.getByRole('button', { name: 'Continue' }).click()

await page.waitForSelector('input#email-address.govuk-input')

await testErrorMessage(page, 'input#email-address.govuk-input', 'Please enter a valid email address')
})

const testErrorMessage = async (page, fieldName, expectedErrorMessage) => {
await page.waitForSelector(fieldName)

const errorLink = await page.getByRole('link', { name: expectedErrorMessage })
const fieldError = await page.getByText(`Error: ${expectedErrorMessage}`)
const errorSummary = await page.getByText('There is a problem')

expect(await errorSummary.isVisible(), 'Page should show the error summary').toBeTruthy()
expect(await errorLink.isVisible(), 'Page should the error message that is a link to the problem field').toBeTruthy()
expect(await errorLink.isVisible(), 'Page should show an error summary that is a link to the problem field').toBeTruthy()
expect(await fieldError.isVisible(), 'Page should show the error message next to the problem field').toBeTruthy()
await errorLink.click()

const problemFieldIsFocused = await page.$eval('input#datafile.govuk-file-upload', (el) => el === document.activeElement)
const problemFieldIsFocused = await page.$eval(fieldName, (el) => el === document.activeElement)
expect(problemFieldIsFocused, 'The focus should be on the problem field').toBeTruthy()

expect(await page.title(), 'Page title should indicate there\'s an error').toMatch(/Error: .*/)
})
}
38 changes: 18 additions & 20 deletions test/mock-api/index.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
// a basic json express server with one endpoint that returns a json object
'use strict'
// this script runs a mock server that mimics the pipeline runner api
// it doesn't perform any validations but instead looks at the filename to determine if it should return errors or not

import express from 'express'

import config from '../../config/index.js'

import multer from 'multer'

import { readFileSync } from 'fs'
const upload = multer({ dest: 'uploads/' })

const APIResponse = JSON.parse(readFileSync('./test/testData/API_RUN_PIPELINE_RESPONSE.json'))

const app = express()

app.use(config.api.validationEndpoint, upload.single('upload_file'))

app.post(config.api.validationEndpoint, (req, res) => {
const filename = req.file.originalname
if (filename === 'conservation-area-errors.csv') {
res.json({
issueLog: [
{
row: 2,
column: 'name',
issue: 'Name is required'
},
{
row: 3,
column: 'location',
issue: 'Location is required'
}
]
})
} else {
res.json({
issueLog: []
})

const _toSend = { ...APIResponse }

if (filename !== 'conservation-area-errors.csv') {
_toSend['issue-log'] = []
}
res.json(_toSend)
})

app.listen(config.api.port, () => {
Expand Down

0 comments on commit 7f33a5c

Please sign in to comment.