Skip to content

Commit 4437af5

Browse files
authored
Support async viewcontext (#60)
* Support for async view contexts * Support async viewcontext (required for FCP) * Fix documentation inconsistency * Fix types for view context * update viewcontext example with promise * remove unnecessary return annotation * drop Promise.resolve as async is not neccessary any more
1 parent 752967e commit 4437af5

6 files changed

Lines changed: 37 additions & 23 deletions

File tree

docs/GETTING_STARTED.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ await server.register({
103103
* Options that DXT uses to render Nunjucks templates
104104
*/
105105
nunjucks: {
106-
basePageLayout: 'your-base-layout.html', // the base page layout. Usually based off https://design-system.service.gov.uk/styles/page-template/
107-
viewPaths // list of directories DXT should use to render your views. Must contain basePageLayout.
106+
baseLayoutPath: 'your-base-layout.html', // the base page layout. Usually based off https://design-system.service.gov.uk/styles/page-template/
107+
viewPaths // list of directories DXT should use to render your views. Must contain baseLayoutPath.
108108
},
109109
/**
110110
* Services is what DXT uses to interact with external APIs
@@ -117,8 +117,13 @@ await server.register({
117117
/**
118118
* View context attributes made available to your pages. Returns an object containing an arbitrary set of key-value pairs.
119119
*/
120-
viewContext: (request) => {
121-
"example": "hello world" // available to render on a nunjucks page as {{ example }}
120+
viewContext: async (request) => { // async can be dropped if there's no async code within
121+
const user = await userService.getUser(request.auth.credentials)
122+
123+
return {
124+
"greeting": "Hello" // available to render on a nunjucks page as {{ greeting }}
125+
"username": user.username // available to render on a nunjucks page as {{ username }}
126+
}
122127
}
123128
}
124129
})

src/server/plugins/engine/plugin.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { hasFormComponents, slugSchema } from '@defra/forms-model'
66
import Boom from '@hapi/boom'
77
import {
88
type Plugin,
9+
type PluginProperties,
910
type ResponseObject,
1011
type ResponseToolkit,
1112
type RouteOptions,
@@ -95,9 +96,7 @@ export interface PluginOptions {
9596
baseLayoutPath: string
9697
paths: string[]
9798
}
98-
viewContext: (
99-
request: FormRequest | FormRequestPayload | null
100-
) => Record<string, unknown>
99+
viewContext: PluginProperties['forms-engine-plugin']['viewContext']
101100
}
102101

103102
export const plugin = {

src/server/plugins/engine/services/localFormsService.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,12 @@ export const formsService = async () => {
4545
slug: 'register-as-a-unicorn-breeder-yaml' // if we needed to validate any JSON logic, make it available for convenience
4646
})
4747

48+
await loader.addForm('src/server/forms/components.json', {
49+
...metadata,
50+
id: '6a872d3b-13f9e-804ce3e-4830-5c45fb32',
51+
title: 'Components',
52+
slug: 'components'
53+
})
54+
4855
return loader.toFormsService()
4956
}

src/server/plugins/nunjucks/context.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let webpackManifest
2121
/**
2222
* @param {FormRequest | FormRequestPayload | null} request
2323
*/
24-
export function context(request) {
24+
export async function context(request) {
2525
const { params, response } = request ?? {}
2626

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

4545
if (typeof pluginStorage.viewContext === 'function') {
46-
consumerViewContext = pluginStorage.viewContext(request)
46+
consumerViewContext = await pluginStorage.viewContext(request)
4747
}
4848

4949
/** @type {ViewContext} */
@@ -71,8 +71,10 @@ export function context(request) {
7171

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

7880
if (!webpackManifest) {

src/server/plugins/nunjucks/context.test.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ describe('Nunjucks context', () => {
1010

1111
describe('Asset path', () => {
1212
it("should include 'assetPath' for GOV.UK Frontend icons", () => {
13-
const { assetPath } = devtoolContext()
13+
const { assetPath } = devtoolContext(null)
1414
expect(assetPath).toBe('/assets')
1515
})
1616
})
1717

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

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

4040
// Update config for missing manifest
4141
config.set('publicDir', tmpdir())
42-
const { getDxtAssetPath } = devtoolContext()
42+
const { getDxtAssetPath } = devtoolContext(null)
4343

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

5050
it('should return path to unknown assets', () => {
51-
const { getDxtAssetPath } = devtoolContext()
51+
const { getDxtAssetPath } = devtoolContext(null)
5252

53-
expect(getDxtAssetPath()).toBe('/')
53+
expect(getDxtAssetPath('')).toBe('/')
5454
expect(getDxtAssetPath('example.jpg')).toBe('/example.jpg')
5555
expect(getDxtAssetPath('example.gif')).toBe('/example.gif')
5656
})
5757
})
5858

5959
describe('Config', () => {
60-
it('should include environment, phase tag and service info', () => {
61-
expect(() => context(null)).toThrow(
60+
it('should include environment, phase tag and service info', async () => {
61+
await expect(context(null)).rejects.toThrow(
6262
'context called before plugin registered'
6363
)
6464
})
6565
})
6666

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

95-
const { crumb } = context(malformedRequest)
95+
const { crumb } = await context(malformedRequest)
9696
expect(crumb).toBeUndefined()
9797
expect(
9898
malformedRequest.server.plugins.crumb.generate
9999
).not.toHaveBeenCalled()
100100
})
101101

102-
it('should generate crumb when state exists', () => {
102+
it('should generate crumb when state exists', async () => {
103103
const mockCrumb = 'generated-crumb-value'
104104
const validRequest = /** @type {FormRequest} */ (
105105
/** @type {unknown} */ ({
@@ -125,7 +125,7 @@ describe('Nunjucks context', () => {
125125
})
126126
)
127127

128-
const { crumb } = context(validRequest)
128+
const { crumb } = await context(validRequest)
129129
expect(crumb).toBe(mockCrumb)
130130
expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith(
131131
validRequest

src/typings/hapi/index.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { type ServerYar, type Yar } from '@hapi/yar'
55
import { type Logger } from 'pino'
66

77
import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
8-
import { type context } from '~/src/server/plugins/engine/nunjucks.js'
98
import {
109
type FormRequest,
1110
type FormRequestPayload
@@ -22,7 +21,9 @@ declare module '@hapi/hapi' {
2221
'forms-engine-plugin': {
2322
baseLayoutPath: string
2423
cacheService: CacheService
25-
viewContext: context
24+
viewContext?: (
25+
request: FormRequest | FormRequestPayload | null
26+
) => Record<string, unknown> | Promise<Record<string, unknown>>
2627
}
2728
}
2829

0 commit comments

Comments
 (0)