Skip to content

Commit

Permalink
Merge pull request #84 from DEFRA/599
Browse files Browse the repository at this point in the history
[599] Baic page model updates and general licence basic page
  • Loading branch information
DrogoNevets authored Dec 19, 2024
2 parents 51d424c + b8a4ff7 commit cad6b60
Show file tree
Hide file tree
Showing 18 changed files with 466 additions and 35 deletions.
74 changes: 74 additions & 0 deletions src/server/common/controller/page-controller/page-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { calculateNextPage } from '../../helpers/next-page.js'
import { ExitPage } from '../../model/page/exit-page-model.js'
/** @import { Server, ServerRegisterPluginObject, ServerRoute, ReqRefDefaults, RouteDefMethods } from '@hapi/hapi' */
/** @import { NextPage } from '../../helpers/next-page.js' */
/** @import { RawPayload } from '../../model/answer/answer-model.js' */
/** @import { Page } from '../../model/page/page-model.js' */

/**
* @typedef {{
* methods: RouteDefMethods[]
* }} ControllerOptions
*/

/** @type {ControllerOptions} */
const defaultControllerOptions = {
methods: ['GET', 'POST']
}

export class PageController {
options
/**
* @param {Page} page
* @param {ControllerOptions} opts
*/
constructor(page, opts = defaultControllerOptions) {
this.page = page

this.options = opts
}

/** @returns {ServerRegisterPluginObject<void>} */
plugin() {
/** @type {ServerRoute<ReqRefDefaults>[]} */
const handlers = this.options.methods.map((method) => {
return {
method,
path: this.page.urlPath,
handler: this[`${method.toLowerCase()}Handler`].bind(this)
}
})

return {
plugin: {
name: `${this.page.sectionKey}-${this.page.key}`,

/** @param {Server} server */
register: (server) => {
server.route(handlers)
}
}
}
}

getHandler(req, h) {
return h.view(this.page.view, {
nextPage: req.query.redirect_uri,
pageTitle: this.page.title,
heading: this.page.heading,
hideQuestion: true,
...this.page.viewProps
})
}

postHandler(req, h) {
const payload = /** @type {NextPage} */ (req.payload)
const nextPage = this.page.nextPage()

if (nextPage instanceof ExitPage) {
return h.redirect(nextPage.urlPath)
} else {
return h.redirect(calculateNextPage(payload.nextPage, nextPage.urlPath))
}
}
}
102 changes: 102 additions & 0 deletions src/server/common/controller/page-controller/page-controller.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Page } from '../../model/page/page-model.js'
import { PageController } from './page-controller.js'
import { createServer } from '~/src/server/index.js'
import { statusCodes } from '~/src/server/common/constants/status-codes.js'
import { parseDocument } from '~/src/server/common/test-helpers/dom.js'
import { withCsrfProtection } from '~/src/server/common/test-helpers/csrf.js'
import SessionTester from '../../test-helpers/session-helper.js'
import { ExitPage } from '../../model/page/exit-page-model.js'

/** @import { Server } from '@hapi/hapi' */

const title = 'page title'
const heading = 'page heading'
const key = 'pageKey'
const pageView = 'common/controller/page-controller/page-controller.test.njk'
const pageUrl = '/test/basic-page'
const nextPageUrl = '/next-question-url'

class TestNextPage extends ExitPage {
urlPath = nextPageUrl
}

class TestPage extends Page {
urlPath = pageUrl
view = pageView
key = key

pageTitle = title
pageHeading = heading

nextPage() {
return new TestNextPage()
}

get viewProps() {
return {
continueUrl: this.nextPage().urlPath
}
}
}

const controller = new PageController(new TestPage())

describe('PageController', () => {
/** @type {Server} */
let server

/** @type {SessionTester} */
let session

beforeAll(async () => {
server = await createServer()
await server.register(controller.plugin())
await server.initialize()
})

beforeEach(async () => {
session = await SessionTester.create(server)
})

afterAll(async () => {
await server.stop({ timeout: 0 })
})

describe('Should process the result and provide expected response', () => {
it('should display the page', async () => {
const { statusCode, payload } = await server.inject(
withCsrfProtection(
{
method: 'GET',
url: pageUrl
},
{
Cookie: session.sessionID
}
)
)

const document = parseDocument(payload)
expect(statusCode).toBe(statusCodes.ok)
expect(document.title).toBe(title)
expect(document.querySelector('h1')?.textContent).toBe(heading)
})

it('should redirect to next page', async () => {
const { headers, statusCode } = await server.inject(
withCsrfProtection(
{
method: 'POST',
url: pageUrl
},
{
Cookie: session.sessionID
}
)
)

expect(statusCode).toBe(statusCodes.redirect)
expect(headers.location).toBe(nextPageUrl)
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{% from "govuk/components/fieldset/macro.njk" import govukFieldset %}
{% from "govuk/components/input/macro.njk" import govukInput %}
{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
{% extends 'layouts/questions.njk' %}

{% block questions %}
{% if (errorMessages) %}
{{
govukErrorSummary({
titleText: "There is a problem",
errorList: errorMessages
})
}}
{% endif %}

{%
call govukFieldset({
legend: {
text: heading,
classes: "govuk-fieldset__legend--l",
isPageHeading: true
}
})
%}

{{
govukInput({
id: "questionId",
name: "questionName",
type: "text",
autocomplete: "question",
classes: "govuk-input--width-20",
value: value,
errorMessage: errors.question
})
}}
{% endcall %}
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class SummaryPageController {

return res.view(this.indexView, {
pageTitle: this.page.pageTitle,
heading: this.page.heading,
heading: this.page.pageHeading,
summary: sectionToSummary(
section,
`/${this.page.urlKey ?? this.page.sectionKey}/check-answers`
Expand Down
39 changes: 39 additions & 0 deletions src/server/common/model/page/page-model.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { NotImplementedError } from '../../helpers/not-implemented-error.js'
/** @import { AnswerModel } from '../answer/answer-model.js' */

export class Page {
/** @type {string} */
urlPath
Expand All @@ -7,4 +10,40 @@ export class Page {

/** @type {string} */
sectionKey

/** @type {string} */
key

/** @type {string} */
view

/** @type {string} */
pageHeading

/** @type {string} */
pageTitle

/** @returns {string} */
get heading() {
return this.pageHeading
}

/** @returns {string} */
get title() {
return this.pageTitle
}

/** @returns {Record<string, unknown>} */
get viewProps() {
return {}
}

/**
* @param {AnswerModel} [_answer]
* @returns {Page }
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
nextPage(_answer) {
throw new NotImplementedError()
}
}
47 changes: 46 additions & 1 deletion src/server/common/model/page/page-model.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,53 @@
import { Page } from './page-model.js'

describe('Page', () => {
let page

beforeEach(() => {
page = new Page()
})

it('should have a path', () => {
const page = new Page()
expect(page).toHaveProperty('urlPath')
})

it('should have a urlKey', () => {
expect(page).toHaveProperty('urlKey')
})

it('should have a sectionKey', () => {
expect(page).toHaveProperty('sectionKey')
})

it('should have a key', () => {
expect(page).toHaveProperty('key')
})

it('should have a view', () => {
expect(page).toHaveProperty('view')
})

it('should have a pageHeading', () => {
expect(page).toHaveProperty('pageHeading')
})

it('should have a pageTitle', () => {
expect(page).toHaveProperty('pageTitle')
})

it('should return pageHeading as heading', () => {
expect(page.heading).toBe(page.pageHeading)
})

it('should return pageTitle as title', () => {
expect(page.title).toBe(page.pageTitle)
})

it('should throw an error on next page by default', () => {
expect(() => page.nextPage()).toThrow()
})

it('should return an empty object for viewProps', () => {
expect(page.viewProps).toEqual({})
})
})
3 changes: 0 additions & 3 deletions src/server/common/model/page/question-page-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ export class QuestionPage extends Page {
/** @type {string} */
questionKey

/** @type {string} */
view

/** @type {AnswerModelClass<AnswerPayload>} */
Answer

Expand Down
6 changes: 0 additions & 6 deletions src/server/common/model/page/summary-page/SummaryPageModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ import { Page } from '../page-model.js'
class SummaryPage extends Page {
/** @type {(_data) => SectionModel} */
sectionFactory

/** @type {string} */
heading

/** @type {string} */
pageTitle
}

export default SummaryPage
Loading

0 comments on commit cad6b60

Please sign in to comment.