Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions docs/GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ await server.register({
* Options that DXT uses to render Nunjucks templates
*/
nunjucks: {
basePageLayout: 'your-base-layout.html', // the base page layout. Usually based off https://design-system.service.gov.uk/styles/page-template/
viewPaths // list of directories DXT should use to render your views. Must contain basePageLayout.
baseLayoutPath: 'your-base-layout.html', // the base page layout. Usually based off https://design-system.service.gov.uk/styles/page-template/
viewPaths // list of directories DXT should use to render your views. Must contain baseLayoutPath.
},
/**
* Services is what DXT uses to interact with external APIs
Expand All @@ -117,8 +117,13 @@ await server.register({
/**
* View context attributes made available to your pages. Returns an object containing an arbitrary set of key-value pairs.
*/
viewContext: (request) => {
"example": "hello world" // available to render on a nunjucks page as {{ example }}
viewContext: async (request) => { // async can be dropped if there's no async code within
const user = await userService.getUser(request.auth.credentials)

return {
"greeting": "Hello" // available to render on a nunjucks page as {{ greeting }}
"username": user.username // available to render on a nunjucks page as {{ username }}
}
}
}
})
Expand Down
5 changes: 2 additions & 3 deletions src/server/plugins/engine/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { hasFormComponents, slugSchema } from '@defra/forms-model'
import Boom from '@hapi/boom'
import {
type Plugin,
type PluginProperties,
type ResponseObject,
type ResponseToolkit,
type RouteOptions,
Expand Down Expand Up @@ -95,9 +96,7 @@ export interface PluginOptions {
baseLayoutPath: string
paths: string[]
}
viewContext: (
request: FormRequest | FormRequestPayload | null
) => Record<string, unknown>
viewContext: PluginProperties['forms-engine-plugin']['viewContext']
}

export const plugin = {
Expand Down
7 changes: 7 additions & 0 deletions src/server/plugins/engine/services/localFormsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,12 @@ export const formsService = async () => {
slug: 'register-as-a-unicorn-breeder-yaml' // if we needed to validate any JSON logic, make it available for convenience
})

await loader.addForm('src/server/forms/components.json', {
...metadata,
id: '6a872d3b-13f9e-804ce3e-4830-5c45fb32',
title: 'Components',
slug: 'components'
})

return loader.toFormsService()
}
8 changes: 5 additions & 3 deletions src/server/plugins/nunjucks/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let webpackManifest
/**
* @param {FormRequest | FormRequestPayload | null} request
*/
export function context(request) {
export async function context(request) {
const { params, response } = request ?? {}

const { isPreview: isPreviewMode, state: formState } = checkFormStatus(params)
Expand All @@ -43,7 +43,7 @@ export function context(request) {
}

if (typeof pluginStorage.viewContext === 'function') {
consumerViewContext = pluginStorage.viewContext(request)
consumerViewContext = await pluginStorage.viewContext(request)
}

/** @type {ViewContext} */
Expand Down Expand Up @@ -71,8 +71,10 @@ export function context(request) {

/**
* Returns the context for the devtool. Consumers won't have access to this.
* @param {FormRequest | FormRequestPayload | null} _request
* @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}
*/
export function devtoolContext() {
export function devtoolContext(_request) {
const manifestPath = join(config.get('publicDir'), 'assets-manifest.json')

if (!webpackManifest) {
Expand Down
22 changes: 11 additions & 11 deletions src/server/plugins/nunjucks/context.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ describe('Nunjucks context', () => {

describe('Asset path', () => {
it("should include 'assetPath' for GOV.UK Frontend icons", () => {
const { assetPath } = devtoolContext()
const { assetPath } = devtoolContext(null)
expect(assetPath).toBe('/assets')
})
})

describe('Asset helper', () => {
it("should locate 'assets-manifest.json' assets", () => {
const { getDxtAssetPath } = devtoolContext()
const { getDxtAssetPath } = devtoolContext(null)

expect(getDxtAssetPath('example.scss')).toBe(
'/stylesheets/example.xxxxxxx.min.css'
Expand All @@ -39,7 +39,7 @@ describe('Nunjucks context', () => {

// Update config for missing manifest
config.set('publicDir', tmpdir())
const { getDxtAssetPath } = devtoolContext()
const { getDxtAssetPath } = devtoolContext(null)

// Uses original paths when missing
expect(getDxtAssetPath('example.scss')).toBe('/example.scss')
Expand All @@ -48,24 +48,24 @@ describe('Nunjucks context', () => {
})

it('should return path to unknown assets', () => {
const { getDxtAssetPath } = devtoolContext()
const { getDxtAssetPath } = devtoolContext(null)

expect(getDxtAssetPath()).toBe('/')
expect(getDxtAssetPath('')).toBe('/')
expect(getDxtAssetPath('example.jpg')).toBe('/example.jpg')
expect(getDxtAssetPath('example.gif')).toBe('/example.gif')
})
})

describe('Config', () => {
it('should include environment, phase tag and service info', () => {
expect(() => context(null)).toThrow(
it('should include environment, phase tag and service info', async () => {
await expect(context(null)).rejects.toThrow(
'context called before plugin registered'
)
})
})

describe('Crumb', () => {
it('should handle malformed requests with missing state', () => {
it('should handle malformed requests with missing state', async () => {
// While state should always exist in a valid Hapi request (it holds cookies),
// we've seen malformed requests in production where it's missing
const malformedRequest = /** @type {FormRequest} */ (
Expand All @@ -92,14 +92,14 @@ describe('Nunjucks context', () => {
})
)

const { crumb } = context(malformedRequest)
const { crumb } = await context(malformedRequest)
expect(crumb).toBeUndefined()
expect(
malformedRequest.server.plugins.crumb.generate
).not.toHaveBeenCalled()
})

it('should generate crumb when state exists', () => {
it('should generate crumb when state exists', async () => {
const mockCrumb = 'generated-crumb-value'
const validRequest = /** @type {FormRequest} */ (
/** @type {unknown} */ ({
Expand All @@ -125,7 +125,7 @@ describe('Nunjucks context', () => {
})
)

const { crumb } = context(validRequest)
const { crumb } = await context(validRequest)
expect(crumb).toBe(mockCrumb)
expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith(
validRequest
Expand Down
5 changes: 3 additions & 2 deletions src/typings/hapi/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { type ServerYar, type Yar } from '@hapi/yar'
import { type Logger } from 'pino'

import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
import { type context } from '~/src/server/plugins/engine/nunjucks.js'
import {
type FormRequest,
type FormRequestPayload
Expand All @@ -22,7 +21,9 @@ declare module '@hapi/hapi' {
'forms-engine-plugin': {
baseLayoutPath: string
cacheService: CacheService
viewContext: context
viewContext?: (
request: FormRequest | FormRequestPayload | null
) => Record<string, unknown> | Promise<Record<string, unknown>>
}
}

Expand Down
Loading