diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index ed6b21b6b..194e0d448 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -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 @@ -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 }} + } } } }) diff --git a/src/server/plugins/engine/plugin.ts b/src/server/plugins/engine/plugin.ts index 8952b9be2..3ef047c49 100644 --- a/src/server/plugins/engine/plugin.ts +++ b/src/server/plugins/engine/plugin.ts @@ -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, @@ -95,9 +96,7 @@ export interface PluginOptions { baseLayoutPath: string paths: string[] } - viewContext: ( - request: FormRequest | FormRequestPayload | null - ) => Record + viewContext: PluginProperties['forms-engine-plugin']['viewContext'] } export const plugin = { diff --git a/src/server/plugins/engine/services/localFormsService.js b/src/server/plugins/engine/services/localFormsService.js index bdbce67d1..cb6e1a834 100644 --- a/src/server/plugins/engine/services/localFormsService.js +++ b/src/server/plugins/engine/services/localFormsService.js @@ -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() } diff --git a/src/server/plugins/nunjucks/context.js b/src/server/plugins/nunjucks/context.js index 59afac5ef..df1931364 100644 --- a/src/server/plugins/nunjucks/context.js +++ b/src/server/plugins/nunjucks/context.js @@ -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) @@ -43,7 +43,7 @@ export function context(request) { } if (typeof pluginStorage.viewContext === 'function') { - consumerViewContext = pluginStorage.viewContext(request) + consumerViewContext = await pluginStorage.viewContext(request) } /** @type {ViewContext} */ @@ -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 & { assetPath: string, getDxtAssetPath: (asset: string) => string }} */ -export function devtoolContext() { +export function devtoolContext(_request) { const manifestPath = join(config.get('publicDir'), 'assets-manifest.json') if (!webpackManifest) { diff --git a/src/server/plugins/nunjucks/context.test.js b/src/server/plugins/nunjucks/context.test.js index 91865cf4d..35fec85f8 100644 --- a/src/server/plugins/nunjucks/context.test.js +++ b/src/server/plugins/nunjucks/context.test.js @@ -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' @@ -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') @@ -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} */ ( @@ -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} */ ({ @@ -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 diff --git a/src/typings/hapi/index.d.ts b/src/typings/hapi/index.d.ts index 11f5c27f2..d6244e321 100644 --- a/src/typings/hapi/index.d.ts +++ b/src/typings/hapi/index.d.ts @@ -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 @@ -22,7 +21,9 @@ declare module '@hapi/hapi' { 'forms-engine-plugin': { baseLayoutPath: string cacheService: CacheService - viewContext: context + viewContext?: ( + request: FormRequest | FormRequestPayload | null + ) => Record | Promise> } }