@defra/forms-engine-plugin 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/.public/stylesheets/application.min.css +1 -1
  2. package/.public/stylesheets/application.min.css.map +1 -1
  3. package/.server/client/stylesheets/application.scss +10 -0
  4. package/.server/config/index.js +3 -14
  5. package/.server/config/index.js.map +1 -1
  6. package/.server/server/devserver/dxt-devtool-baselayout.html +71 -0
  7. package/.server/server/forms/register-as-a-unicorn-breeder.json +393 -0
  8. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +251 -0
  9. package/.server/server/index.js +11 -16
  10. package/.server/server/index.js.map +1 -1
  11. package/.server/server/plugins/engine/configureEnginePlugin.js +16 -2
  12. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  13. package/.server/server/plugins/engine/plugin.js +27 -16
  14. package/.server/server/plugins/engine/plugin.js.map +1 -1
  15. package/.server/server/plugins/engine/services/formsService.js +15 -29
  16. package/.server/server/plugins/engine/services/formsService.js.map +1 -1
  17. package/.server/server/plugins/engine/services/localFormsService.js +52 -0
  18. package/.server/server/plugins/engine/services/localFormsService.js.map +1 -0
  19. package/.server/server/plugins/engine/views/confirmation.html +1 -1
  20. package/.server/server/plugins/engine/views/file-upload.html +1 -1
  21. package/.server/server/plugins/engine/views/index.html +1 -1
  22. package/.server/server/plugins/engine/views/item-delete.html +1 -1
  23. package/.server/server/plugins/engine/views/repeat-list-summary.html +1 -1
  24. package/.server/server/plugins/engine/views/summary.html +1 -1
  25. package/.server/server/plugins/errorPages.js +4 -26
  26. package/.server/server/plugins/errorPages.js.map +1 -1
  27. package/.server/server/plugins/nunjucks/context.js +37 -28
  28. package/.server/server/plugins/nunjucks/context.js.map +1 -1
  29. package/.server/server/plugins/nunjucks/context.test.js +23 -28
  30. package/.server/server/plugins/nunjucks/context.test.js.map +1 -1
  31. package/.server/server/plugins/nunjucks/types.js +3 -4
  32. package/.server/server/plugins/nunjucks/types.js.map +1 -1
  33. package/.server/server/routes/index.js +0 -1
  34. package/.server/server/routes/index.js.map +1 -1
  35. package/.server/server/utils/file-form-service.js +134 -0
  36. package/.server/server/utils/file-form-service.js.map +1 -0
  37. package/.server/typings/hapi/index.d.js.map +1 -1
  38. package/README.md +3 -1
  39. package/package.json +7 -3
  40. package/src/client/stylesheets/application.scss +10 -0
  41. package/src/config/index.ts +4 -17
  42. package/src/server/devserver/dxt-devtool-baselayout.html +71 -0
  43. package/src/server/forms/register-as-a-unicorn-breeder.json +393 -0
  44. package/src/server/forms/register-as-a-unicorn-breeder.yaml +251 -0
  45. package/src/server/index.test.ts +4 -37
  46. package/src/server/index.ts +13 -16
  47. package/src/server/plugins/engine/components/AutocompleteField.test.ts +5 -5
  48. package/src/server/plugins/engine/components/CheckboxesField.test.ts +7 -7
  49. package/src/server/plugins/engine/components/List.test.ts +3 -0
  50. package/src/server/plugins/engine/components/RadiosField.test.ts +5 -5
  51. package/src/server/plugins/engine/components/SelectField.test.ts +5 -5
  52. package/src/server/plugins/engine/configureEnginePlugin.ts +19 -1
  53. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +4 -0
  54. package/src/server/plugins/engine/plugin.ts +43 -17
  55. package/src/server/plugins/engine/services/formsService.js +17 -35
  56. package/src/server/plugins/engine/services/localFormsService.js +49 -0
  57. package/src/server/plugins/engine/views/confirmation.html +1 -1
  58. package/src/server/plugins/engine/views/file-upload.html +1 -1
  59. package/src/server/plugins/engine/views/index.html +1 -1
  60. package/src/server/plugins/engine/views/item-delete.html +1 -1
  61. package/src/server/plugins/engine/views/repeat-list-summary.html +1 -1
  62. package/src/server/plugins/engine/views/summary.html +1 -1
  63. package/src/server/plugins/errorPages.ts +4 -26
  64. package/src/server/plugins/nunjucks/context.js +41 -31
  65. package/src/server/plugins/nunjucks/context.test.js +24 -27
  66. package/src/server/plugins/nunjucks/types.js +3 -4
  67. package/src/server/routes/index.ts +0 -1
  68. package/src/server/utils/file-form-service.js +144 -0
  69. package/src/typings/hapi/index.d.ts +3 -9
  70. package/.server/common/cookies.js +0 -55
  71. package/.server/common/cookies.js.map +0 -1
  72. package/.server/common/cookies.test.js +0 -15
  73. package/.server/common/cookies.test.js.map +0 -1
  74. package/.server/common/types.js +0 -6
  75. package/.server/common/types.js.map +0 -1
  76. package/.server/server/forms/README.md +0 -10
  77. package/.server/server/forms/report-a-terrorist.json +0 -270
  78. package/.server/server/forms/runner-components-test.json +0 -365
  79. package/.server/server/forms/test.json +0 -581
  80. package/.server/server/plugins/blankie.js +0 -29
  81. package/.server/server/plugins/blankie.js.map +0 -1
  82. package/.server/server/plugins/engine/services/formsService.test.js +0 -71
  83. package/.server/server/plugins/engine/services/formsService.test.js.map +0 -1
  84. package/.server/server/plugins/engine/views/layout.html +0 -199
  85. package/.server/server/plugins/router.js +0 -169
  86. package/.server/server/plugins/router.js.map +0 -1
  87. package/.server/server/routes/health.js +0 -15
  88. package/.server/server/routes/health.js.map +0 -1
  89. package/.server/server/routes/health.test.js +0 -32
  90. package/.server/server/routes/health.test.js.map +0 -1
  91. package/.server/server/views/404.html +0 -16
  92. package/.server/server/views/500.html +0 -19
  93. package/.server/server/views/help/accessibility-statement.html +0 -58
  94. package/.server/server/views/help/cookie-preferences.html +0 -57
  95. package/.server/server/views/help/cookies.html +0 -71
  96. package/.server/server/views/help/get-support.html +0 -37
  97. package/.server/server/views/help/privacy-notice.html +0 -68
  98. package/.server/server/views/help/terms-and-conditions.html +0 -83
  99. package/src/common/cookies.js +0 -58
  100. package/src/common/cookies.test.js +0 -23
  101. package/src/common/types.js +0 -5
  102. package/src/server/forms/README.md +0 -10
  103. package/src/server/forms/report-a-terrorist.json +0 -270
  104. package/src/server/forms/runner-components-test.json +0 -365
  105. package/src/server/forms/test.json +0 -581
  106. package/src/server/plugins/blankie.test.ts +0 -73
  107. package/src/server/plugins/blankie.ts +0 -48
  108. package/src/server/plugins/engine/services/formsService.test.js +0 -90
  109. package/src/server/plugins/engine/views/layout.html +0 -199
  110. package/src/server/plugins/router.ts +0 -201
  111. package/src/server/routes/health.js +0 -13
  112. package/src/server/routes/health.test.js +0 -35
  113. package/src/server/routes/index.test.ts +0 -125
  114. package/src/server/views/404.html +0 -16
  115. package/src/server/views/500.html +0 -19
  116. package/src/server/views/help/accessibility-statement.html +0 -58
  117. package/src/server/views/help/cookie-preferences.html +0 -57
  118. package/src/server/views/help/cookies.html +0 -71
  119. package/src/server/views/help/get-support.html +0 -37
  120. package/src/server/views/help/privacy-notice.html +0 -68
  121. package/src/server/views/help/terms-and-conditions.html +0 -83
@@ -15,14 +15,13 @@ import { config } from '~/src/config/index.js'
15
15
  import { requestLogger } from '~/src/server/common/helpers/logging/request-logger.js'
16
16
  import { requestTracing } from '~/src/server/common/helpers/logging/request-tracing.js'
17
17
  import { buildRedisClient } from '~/src/server/common/helpers/redis-client.js'
18
- import { configureBlankiePlugin } from '~/src/server/plugins/blankie.js'
19
18
  import { configureCrumbPlugin } from '~/src/server/plugins/crumb.js'
20
- import { configureEnginePlugin } from '~/src/server/plugins/engine/index.js'
19
+ import { configureEnginePlugin } from '~/src/server/plugins/engine/configureEnginePlugin.js'
21
20
  import pluginErrorPages from '~/src/server/plugins/errorPages.js'
22
21
  import { plugin as pluginViews } from '~/src/server/plugins/nunjucks/index.js'
23
22
  import pluginPulse from '~/src/server/plugins/pulse.js'
24
- import pluginRouter from '~/src/server/plugins/router.js'
25
23
  import pluginSession from '~/src/server/plugins/session.js'
24
+ import { publicRoutes } from '~/src/server/routes/index.js'
26
25
  import { prepareSecureContext } from '~/src/server/secure-context.js'
27
26
  import { type RouteConfig } from '~/src/server/types.js'
28
27
 
@@ -82,15 +81,13 @@ export async function createServer(routeConfig?: RouteConfig) {
82
81
  prepareSecureContext(server)
83
82
  }
84
83
 
85
- const pluginEngine = await configureEnginePlugin(routeConfig)
86
84
  const pluginCrumb = configureCrumbPlugin(routeConfig)
87
- const pluginBlankie = configureBlankiePlugin()
85
+ const pluginEngine = await configureEnginePlugin(routeConfig)
88
86
 
89
87
  await server.register(pluginSession)
90
88
  await server.register(pluginPulse)
91
89
  await server.register(inert)
92
90
  await server.register(Scooter)
93
- await server.register(pluginBlankie)
94
91
  await server.register(pluginCrumb)
95
92
 
96
93
  server.ext('onPreResponse', (request: Request, h: ResponseToolkit) => {
@@ -117,19 +114,19 @@ export async function createServer(routeConfig?: RouteConfig) {
117
114
 
118
115
  await server.register(pluginViews)
119
116
  await server.register(pluginEngine)
120
- await server.register(pluginRouter)
117
+
118
+ await server.register({
119
+ plugin: {
120
+ name: 'router',
121
+ register: (server) => {
122
+ server.route(publicRoutes)
123
+ }
124
+ }
125
+ })
126
+
121
127
  await server.register(pluginErrorPages)
122
128
  await server.register(blipp)
123
129
  await server.register(requestTracing)
124
130
 
125
- server.state('cookieConsent', {
126
- ttl: 365 * 24 * 60 * 60 * 1000, // 1 year in ms
127
- clearInvalid: true,
128
- isHttpOnly: false,
129
- isSecure: config.get('isProduction'),
130
- path: '/',
131
- encoding: 'none' // handle this inside the application so we can share frontend/backend cookie modification
132
- })
133
-
134
131
  return server
135
132
  }
@@ -172,7 +172,7 @@ describe.each([
172
172
 
173
173
  describe('State', () => {
174
174
  it.each([...options.examples])('returns text from state', (item) => {
175
- const state1 = getFormState(item.state)
175
+ const state1 = getFormState(item.value)
176
176
  const state2 = getFormState(null)
177
177
 
178
178
  const answer1 = getAnswer(field, state1)
@@ -183,7 +183,7 @@ describe.each([
183
183
  })
184
184
 
185
185
  it.each([...options.examples])('returns payload from state', (item) => {
186
- const state1 = getFormState(item.state)
186
+ const state1 = getFormState(item.value)
187
187
  const state2 = getFormState(null)
188
188
 
189
189
  const payload1 = field.getFormDataFromState(state1)
@@ -194,7 +194,7 @@ describe.each([
194
194
  })
195
195
 
196
196
  it.each([...options.examples])('returns value from state', (item) => {
197
- const state1 = getFormState(item.state)
197
+ const state1 = getFormState(item.value)
198
198
  const state2 = getFormState(null)
199
199
 
200
200
  const value1 = field.getFormValueFromState(state1)
@@ -207,7 +207,7 @@ describe.each([
207
207
  it.each([...options.examples])(
208
208
  'returns context for conditions and form submission',
209
209
  (item) => {
210
- const state1 = getFormState(item.state)
210
+ const state1 = getFormState(item.value)
211
211
  const state2 = getFormState(null)
212
212
 
213
213
  const value1 = field.getContextValueFromState(state1)
@@ -225,7 +225,7 @@ describe.each([
225
225
  const value1 = field.getStateFromValidForm(payload1)
226
226
  const value2 = field.getStateFromValidForm(payload2)
227
227
 
228
- expect(value1).toEqual(getFormState(item.state))
228
+ expect(value1).toEqual(getFormState(item.value))
229
229
  expect(value2).toEqual(getFormState(null))
230
230
  })
231
231
  })
@@ -240,7 +240,7 @@ describe.each([
240
240
  it.each([...options.examples])(
241
241
  'returns text from state (single)',
242
242
  (item) => {
243
- const state1 = getFormState([item.state])
243
+ const state1 = getFormState([item.value])
244
244
  const state2 = getFormState(null)
245
245
 
246
246
  const answer1 = getAnswer(field, state1)
@@ -260,7 +260,7 @@ describe.each([
260
260
  const item1 = options.examples[0]
261
261
  const item2 = options.examples[2]
262
262
 
263
- const state = getFormState([item1.state, item2.state])
263
+ const state = getFormState([item1.value, item2.value])
264
264
  const answer = getAnswer(field, state)
265
265
 
266
266
  expect(answer).toBe(outdent`
@@ -272,7 +272,7 @@ describe.each([
272
272
  })
273
273
 
274
274
  it.each([...options.examples])('returns payload from state', (item) => {
275
- const state1 = getFormState([item.state])
275
+ const state1 = getFormState([item.value])
276
276
  const state2 = getFormState(null)
277
277
 
278
278
  const payload1 = field.getFormDataFromState(state1)
@@ -283,7 +283,7 @@ describe.each([
283
283
  })
284
284
 
285
285
  it.each([...options.examples])('returns value from state', (item) => {
286
- const state1 = getFormState([item.state])
286
+ const state1 = getFormState([item.value])
287
287
  const state2 = getFormState(null)
288
288
 
289
289
  const value1 = field.getFormValueFromState(state1)
@@ -296,13 +296,13 @@ describe.each([
296
296
  it.each([...options.examples])(
297
297
  'returns context for conditions and form submission',
298
298
  (item) => {
299
- const state1 = getFormState([item.state])
299
+ const state1 = getFormState([item.value])
300
300
  const state2 = getFormState(null)
301
301
 
302
302
  const value1 = field.getContextValueFromState(state1)
303
303
  const value2 = field.getContextValueFromState(state2)
304
304
 
305
- expect(value1).toEqual([item.state])
305
+ expect(value1).toEqual([item.value])
306
306
  expect(value2).toEqual([])
307
307
  }
308
308
  )
@@ -314,7 +314,7 @@ describe.each([
314
314
  const value1 = field.getStateFromValidForm(payload1)
315
315
  const value2 = field.getStateFromValidForm(payload2)
316
316
 
317
- expect(value1).toEqual(getFormState([item.state]))
317
+ expect(value1).toEqual(getFormState([item.value]))
318
318
  expect(value2).toEqual(getFormState(null))
319
319
  })
320
320
  })
@@ -52,18 +52,21 @@ describe('List', () => {
52
52
  it('returns list items', () => {
53
53
  expect(guidance).toHaveProperty('items', [
54
54
  {
55
+ id: '52fc51fc-c75a-4b08-9c9e-6bd99b9bc49b',
55
56
  text: '1 day',
56
57
  value: 1,
57
58
  description:
58
59
  'Valid for 24 hours from the start time that you select'
59
60
  },
60
61
  {
62
+ id: '56b7b34f-23b3-4446-ac8e-b2443d18588e',
61
63
  text: '8 day',
62
64
  value: 8,
63
65
  description:
64
66
  'Valid for 8 consecutive days from the start time that you select'
65
67
  },
66
68
  {
69
+ id: '1af54fbc-eec2-4e1e-bd53-2415abf62677',
67
70
  text: '12 months',
68
71
  value: 365,
69
72
  description:
@@ -170,7 +170,7 @@ describe.each([
170
170
 
171
171
  describe('State', () => {
172
172
  it.each([...options.examples])('returns text from state', (item) => {
173
- const state1 = getFormState(item.state)
173
+ const state1 = getFormState(item.value)
174
174
  const state2 = getFormState(null)
175
175
 
176
176
  const answer1 = getAnswer(field, state1)
@@ -181,7 +181,7 @@ describe.each([
181
181
  })
182
182
 
183
183
  it.each([...options.examples])('returns payload from state', (item) => {
184
- const state1 = getFormState(item.state)
184
+ const state1 = getFormState(item.value)
185
185
  const state2 = getFormState(null)
186
186
 
187
187
  const payload1 = field.getFormDataFromState(state1)
@@ -192,7 +192,7 @@ describe.each([
192
192
  })
193
193
 
194
194
  it.each([...options.examples])('returns value from state', (item) => {
195
- const state1 = getFormState(item.state)
195
+ const state1 = getFormState(item.value)
196
196
  const state2 = getFormState(null)
197
197
 
198
198
  const value1 = field.getFormValueFromState(state1)
@@ -205,7 +205,7 @@ describe.each([
205
205
  it.each([...options.examples])(
206
206
  'returns context for conditions and form submission',
207
207
  (item) => {
208
- const state1 = getFormState(item.state)
208
+ const state1 = getFormState(item.value)
209
209
  const state2 = getFormState(null)
210
210
 
211
211
  const value1 = field.getContextValueFromState(state1)
@@ -223,7 +223,7 @@ describe.each([
223
223
  const value1 = field.getStateFromValidForm(payload1)
224
224
  const value2 = field.getStateFromValidForm(payload2)
225
225
 
226
- expect(value1).toEqual(getFormState(item.state))
226
+ expect(value1).toEqual(getFormState(item.value))
227
227
  expect(value2).toEqual(getFormState(null))
228
228
  })
229
229
  })
@@ -171,7 +171,7 @@ describe.each([
171
171
 
172
172
  describe('State', () => {
173
173
  it.each([...options.examples])('returns text from state', (item) => {
174
- const state1 = getFormState(item.state)
174
+ const state1 = getFormState(item.value)
175
175
  const state2 = getFormState(null)
176
176
 
177
177
  const answer1 = getAnswer(field, state1)
@@ -182,7 +182,7 @@ describe.each([
182
182
  })
183
183
 
184
184
  it.each([...options.examples])('returns payload from state', (item) => {
185
- const state1 = getFormState(item.state)
185
+ const state1 = getFormState(item.value)
186
186
  const state2 = getFormState(null)
187
187
 
188
188
  const payload1 = field.getFormDataFromState(state1)
@@ -193,7 +193,7 @@ describe.each([
193
193
  })
194
194
 
195
195
  it.each([...options.examples])('returns value from state', (item) => {
196
- const state1 = getFormState(item.state)
196
+ const state1 = getFormState(item.value)
197
197
  const state2 = getFormState(null)
198
198
 
199
199
  const value1 = field.getFormValueFromState(state1)
@@ -206,7 +206,7 @@ describe.each([
206
206
  it.each([...options.examples])(
207
207
  'returns context for conditions and form submission',
208
208
  (item) => {
209
- const state1 = getFormState(item.state)
209
+ const state1 = getFormState(item.value)
210
210
  const state2 = getFormState(null)
211
211
 
212
212
  const value1 = field.getContextValueFromState(state1)
@@ -224,7 +224,7 @@ describe.each([
224
224
  const value1 = field.getStateFromValidForm(payload1)
225
225
  const value2 = field.getStateFromValidForm(payload2)
226
226
 
227
- expect(value1).toEqual(getFormState(item.state))
227
+ expect(value1).toEqual(getFormState(item.value))
228
228
  expect(value2).toEqual(getFormState(null))
229
229
  })
230
230
  })
@@ -8,6 +8,10 @@ import {
8
8
  plugin,
9
9
  type PluginOptions
10
10
  } from '~/src/server/plugins/engine/plugin.js'
11
+ import { findPackageRoot } from '~/src/server/plugins/engine/plugin.js'
12
+ import * as defaultServices from '~/src/server/plugins/engine/services/index.js'
13
+ import { formsService } from '~/src/server/plugins/engine/services/localFormsService.js'
14
+ import { devtoolContext } from '~/src/server/plugins/nunjucks/context.js'
11
15
  import { type RouteConfig } from '~/src/server/types.js'
12
16
 
13
17
  export const configureEnginePlugin = async ({
@@ -27,7 +31,21 @@ export const configureEnginePlugin = async ({
27
31
 
28
32
  return {
29
33
  plugin,
30
- options: { model, services, controllers }
34
+ options: {
35
+ model,
36
+ services: services ?? {
37
+ // services for testing, else use the disk loader option for running this service locally
38
+ ...defaultServices,
39
+ formsService: await formsService()
40
+ },
41
+ controllers,
42
+ cacheName: 'session',
43
+ nunjucks: {
44
+ baseLayoutPath: 'dxt-devtool-baselayout.html',
45
+ paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner
46
+ },
47
+ viewContext: devtoolContext
48
+ }
31
49
  }
32
50
  }
33
51
 
@@ -443,6 +443,7 @@ describe('QuestionPageController', () => {
443
443
  expect(filtered[1].model.label?.text).toBe('Select from the list')
444
444
  expect(filtered[1].model.items).toEqual([
445
445
  {
446
+ id: expect.any(String),
446
447
  checked: false,
447
448
  condition: 'isBarnOwl',
448
449
  selected: false,
@@ -450,6 +451,7 @@ describe('QuestionPageController', () => {
450
451
  value: '1'
451
452
  },
452
453
  {
454
+ id: expect.any(String),
453
455
  checked: false,
454
456
  condition: 'isBarnOwl',
455
457
  selected: false,
@@ -499,6 +501,7 @@ describe('QuestionPageController', () => {
499
501
  expect(filtered[1].model.label?.text).toBe('Select from the list')
500
502
  expect(filtered[1].model.items).toEqual([
501
503
  {
504
+ id: expect.any(String),
502
505
  checked: false,
503
506
  condition: 'notBarnOwl',
504
507
  selected: false,
@@ -506,6 +509,7 @@ describe('QuestionPageController', () => {
506
509
  value: '3'
507
510
  },
508
511
  {
512
+ id: expect.any(String),
509
513
  checked: false,
510
514
  condition: 'notBarnOwl',
511
515
  selected: false,
@@ -1,3 +1,7 @@
1
+ import { existsSync } from 'fs'
2
+ import { dirname, join } from 'path'
3
+ import { fileURLToPath } from 'url'
4
+
1
5
  import { hasFormComponents, slugSchema } from '@defra/forms-model'
2
6
  import Boom from '@hapi/boom'
3
7
  import {
@@ -11,6 +15,7 @@ import vision from '@hapi/vision'
11
15
  import { isEqual } from 'date-fns'
12
16
  import Joi from 'joi'
13
17
  import nunjucks, { type Environment } from 'nunjucks'
18
+ import resolvePkg from 'resolve'
14
19
 
15
20
  import { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'
16
21
  import {
@@ -25,7 +30,6 @@ import {
25
30
  redirectPath
26
31
  } from '~/src/server/plugins/engine/helpers.js'
27
32
  import {
28
- PLUGIN_PATH,
29
33
  VIEW_PATH,
30
34
  context,
31
35
  prepareNunjucksEnvironment
@@ -65,6 +69,20 @@ import * as httpService from '~/src/server/services/httpService.js'
65
69
  import { CacheService } from '~/src/server/services/index.js'
66
70
  import { type Services } from '~/src/server/types.js'
67
71
 
72
+ export function findPackageRoot() {
73
+ const currentFileName = fileURLToPath(import.meta.url)
74
+ const currentDirectoryName = dirname(currentFileName)
75
+
76
+ let dir = currentDirectoryName
77
+ while (dir !== '/') {
78
+ if (existsSync(join(dir, 'package.json'))) {
79
+ return dir
80
+ }
81
+ dir = dirname(dir)
82
+ }
83
+
84
+ throw new Error('package.json not found in parent directories')
85
+ }
68
86
  export interface PluginOptions {
69
87
  model?: FormModel
70
88
  services?: Services
@@ -73,6 +91,13 @@ export interface PluginOptions {
73
91
  viewPaths?: string[]
74
92
  filters?: Record<string, FilterFunction>
75
93
  pluginPath?: string
94
+ nunjucks: {
95
+ baseLayoutPath: string
96
+ paths: string[]
97
+ }
98
+ viewContext: (
99
+ request: FormRequest | FormRequestPayload | null
100
+ ) => Record<string, unknown>
76
101
  }
77
102
 
78
103
  export const plugin = {
@@ -85,23 +110,25 @@ export const plugin = {
85
110
  services = defaultServices,
86
111
  controllers,
87
112
  cacheName,
88
- viewPaths,
89
113
  filters,
90
- pluginPath = PLUGIN_PATH
114
+ nunjucks: nunjucksOptions,
115
+ viewContext
91
116
  } = options
92
117
  const { formsService } = services
93
118
  const cacheService = new CacheService(server, cacheName)
94
119
 
95
- // Paths array to tell `vision` and `nunjucks` where template files are stored.
96
- // We need to include `VIEW_PATH` in addition the runtime path (node_modules)
97
- // to keep the local tests working
98
- const path = [`${pluginPath}/${VIEW_PATH}`, VIEW_PATH]
120
+ const packageRoot = findPackageRoot()
121
+ const govukFrontendPath = dirname(
122
+ resolvePkg.sync('govuk-frontend/package.json')
123
+ )
99
124
 
100
- // Include any additional user provided view paths so our internal views engine
101
- // can find any files they provide from the consumer side if using custom `page.view`s
102
- if (Array.isArray(viewPaths) && viewPaths.length) {
103
- path.push(...viewPaths)
104
- }
125
+ const viewPathResolved = join(packageRoot, VIEW_PATH)
126
+
127
+ const paths = [
128
+ ...nunjucksOptions.paths,
129
+ viewPathResolved,
130
+ join(govukFrontendPath, 'dist')
131
+ ]
105
132
 
106
133
  await server.register({
107
134
  plugin: vision,
@@ -127,10 +154,7 @@ export const plugin = {
127
154
  ) => {
128
155
  // Nunjucks also needs an additional path configuration
129
156
  // to use the templates and macros from `govuk-frontend`
130
- const environment = nunjucks.configure([
131
- ...path,
132
- 'node_modules/govuk-frontend/dist'
133
- ])
157
+ const environment = nunjucks.configure(paths)
134
158
 
135
159
  // Applies custom filters and globals for nunjucks
136
160
  // that are required by the `forms-engine-plugin`
@@ -142,12 +166,14 @@ export const plugin = {
142
166
  }
143
167
  }
144
168
  },
145
- path,
169
+ path: paths,
146
170
  // Provides global context used with all templates
147
171
  context
148
172
  }
149
173
  })
150
174
 
175
+ server.expose('baseLayoutPath', nunjucksOptions.baseLayoutPath)
176
+ server.expose('viewContext', viewContext)
151
177
  server.expose('cacheService', cacheService)
152
178
 
153
179
  server.app.model = model
@@ -1,46 +1,28 @@
1
- import { formMetadataSchema } from '@defra/forms-model'
2
-
3
- import { config } from '~/src/config/index.js'
4
- import { FormStatus } from '~/src/server/routes/types.js'
5
- import { getJson } from '~/src/server/services/httpService.js'
1
+ const error = Error(
2
+ 'Not implemented. Consider setting up a form loader or providing a service implementation.'
3
+ )
6
4
 
5
+ // eslint-disable-next-line jsdoc/require-returns-check
7
6
  /**
8
- * Retrieves a form definition from the form manager for a given slug
9
- * @param {string} slug - the slug of the form
7
+ * Dummy function to get form metadata.
8
+ * @param {string} _slug - the slug of the form
9
+ * @returns {Promise<FormMetadata>}
10
10
  */
11
- export async function getFormMetadata(slug) {
12
- const getJsonByType = /** @type {typeof getJson<FormMetadata>} */ (getJson)
13
-
14
- const { payload: metadata } = await getJsonByType(
15
- `${config.get('managerUrl')}/forms/slug/${slug}`
16
- )
17
-
18
- // Run it through the schema to coerce dates
19
- const result = formMetadataSchema.validate(metadata)
20
-
21
- if (result.error) {
22
- throw result.error
23
- }
24
-
25
- return result.value
11
+ export function getFormMetadata(_slug) {
12
+ throw error
26
13
  }
27
14
 
15
+ // eslint-disable-next-line jsdoc/require-returns-check
28
16
  /**
29
- * Retrieves a form definition from the form manager for a given id
30
- * @param {string} id - the id of the form
31
- * @param {FormStatus} state - the state of the form
17
+ * Dummy function to get form metadata.
18
+ * @param {string} _id - the id of the form
19
+ * @param {FormStatus} _state - the state of the form
20
+ * @returns {Promise<FormDefinition | undefined>}
32
21
  */
33
- export async function getFormDefinition(id, state) {
34
- const getJsonByType = /** @type {typeof getJson<FormDefinition>} */ (getJson)
35
-
36
- const suffix = state === FormStatus.Draft ? `/${state}` : ''
37
- const { payload: definition } = await getJsonByType(
38
- `${config.get('managerUrl')}/forms/${id}/definition${suffix}`
39
- )
40
-
41
- return definition
22
+ export function getFormDefinition(_id, _state) {
23
+ throw error
42
24
  }
43
25
 
44
26
  /**
45
- * @import { FormDefinition, FormMetadata } from '@defra/forms-model'
27
+ * @import { FormStatus, FormDefinition, FormMetadata } from '@defra/forms-model'
46
28
  */
@@ -0,0 +1,49 @@
1
+ import { config } from '~/src/config/index.js'
2
+ import { FileFormService } from '~/src/server/utils/file-form-service.js'
3
+
4
+ // Create shared form metadata
5
+ const now = new Date()
6
+ const user = { id: 'user', displayName: 'Username' }
7
+ const author = {
8
+ createdAt: now,
9
+ createdBy: user,
10
+ updatedAt: now,
11
+ updatedBy: user
12
+ }
13
+ const metadata = {
14
+ organisation: 'Defra',
15
+ teamName: 'Team name',
16
+ teamEmail: 'team@defra.gov.uk',
17
+ submissionGuidance: "Thanks for your submission, we'll be in touch",
18
+ notificationEmail: config.get('submissionEmailAddress'),
19
+ ...author,
20
+ live: author
21
+ }
22
+
23
+ /**
24
+ * Return an function rather than the service directly. This is to prevent consumer applications
25
+ * blowing up as they won't have these files on disk. We can defer the execution until when it's
26
+ * needed, i.e. the createServer function of the devtool.
27
+ */
28
+ export const formsService = async () => {
29
+ // Instantiate the file loader form service
30
+ const loader = new FileFormService()
31
+
32
+ // Add a Json form
33
+ await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.json', {
34
+ ...metadata,
35
+ id: '95e92559-968d-44ae-8666-2b1ad3dffd31',
36
+ title: 'Register as a unicorn breeder',
37
+ slug: 'register-as-a-unicorn-breeder'
38
+ })
39
+
40
+ // Add a Yaml form
41
+ await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.yaml', {
42
+ ...metadata,
43
+ id: '641aeafd-13dd-40fa-9186-001703800efb',
44
+ title: 'Register as a unicorn breeder (yaml)',
45
+ slug: 'register-as-a-unicorn-breeder-yaml' // if we needed to validate any JSON logic, make it available for convenience
46
+ })
47
+
48
+ return loader.toFormsService()
49
+ }
@@ -1,4 +1,4 @@
1
- {% extends "layout.html" %}
1
+ {% extends baseLayoutPath %}
2
2
 
3
3
  {% from "govuk/components/panel/macro.njk" import govukPanel %}
4
4
 
@@ -35,7 +35,7 @@
35
35
  {% block bodyEnd %}
36
36
  {{ super() }}
37
37
  <script type="module" nonce="{{ cspNonce }}">
38
- import { initFileUpload } from '{{ getAssetPath("file-upload.js") }}';
38
+ import { initFileUpload } from '{{ getDxtAssetPath("file-upload.js") }}';
39
39
  if (document.readyState === 'loading') {
40
40
  document.addEventListener('DOMContentLoaded', initFileUpload);
41
41
  } else {
@@ -1,4 +1,4 @@
1
- {% extends 'layout.html' %}
1
+ {% extends baseLayoutPath %}
2
2
 
3
3
  {% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
4
4
  {% from "partials/components.html" import componentList with context %}
@@ -1,4 +1,4 @@
1
- {% extends "layout.html" %}
1
+ {% extends baseLayoutPath %}
2
2
 
3
3
  {% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
4
4
  {% from "govuk/components/button/macro.njk" import govukButton %}
@@ -1,4 +1,4 @@
1
- {% extends "layout.html" %}
1
+ {% extends baseLayoutPath %}
2
2
 
3
3
  {% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
4
4
  {% from "govuk/components/button/macro.njk" import govukButton %}
@@ -1,4 +1,4 @@
1
- {% extends "layout.html" %}
1
+ {% extends baseLayoutPath %}
2
2
 
3
3
  {% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
4
4
  {% from "govuk/components/summary-list/macro.njk" import govukSummaryList %}