@defra/forms-engine-plugin 4.0.5 → 4.0.7

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 (83) hide show
  1. package/.public/stylesheets/application.min.css +2 -2
  2. package/.public/stylesheets/application.min.css.map +1 -1
  3. package/.server/client/stylesheets/_location-input.scss +60 -0
  4. package/.server/client/stylesheets/application.scss +1 -6
  5. package/.server/client/stylesheets/shared.scss +8 -0
  6. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +28 -0
  7. package/.server/server/plugins/engine/components/ComponentBase.d.ts +1 -1
  8. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  9. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +121 -0
  10. package/.server/server/plugins/engine/components/EastingNorthingField.js +166 -0
  11. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -0
  12. package/.server/server/plugins/engine/components/LatLongField.d.ts +121 -0
  13. package/.server/server/plugins/engine/components/LatLongField.js +164 -0
  14. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -0
  15. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +134 -0
  16. package/.server/server/plugins/engine/components/LocationFieldBase.js +85 -0
  17. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -0
  18. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +108 -0
  19. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +96 -0
  20. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -0
  21. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +19 -0
  22. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +40 -0
  23. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -0
  24. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +19 -0
  25. package/.server/server/plugins/engine/components/OsGridRefField.js +56 -0
  26. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -0
  27. package/.server/server/plugins/engine/components/helpers/components.d.ts +3 -4
  28. package/.server/server/plugins/engine/components/helpers/components.js +21 -29
  29. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  30. package/.server/server/plugins/engine/components/index.d.ts +4 -0
  31. package/.server/server/plugins/engine/components/index.js +4 -0
  32. package/.server/server/plugins/engine/components/index.js.map +1 -1
  33. package/.server/server/plugins/engine/components/markdownParser.d.ts +2 -0
  34. package/.server/server/plugins/engine/components/markdownParser.js +28 -0
  35. package/.server/server/plugins/engine/components/markdownParser.js.map +1 -0
  36. package/.server/server/plugins/engine/components/types.d.ts +10 -0
  37. package/.server/server/plugins/engine/components/types.js.map +1 -1
  38. package/.server/server/plugins/engine/pageControllers/helpers/pages.js +7 -0
  39. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
  40. package/.server/server/plugins/engine/types/index.d.ts +1 -1
  41. package/.server/server/plugins/engine/types/index.js.map +1 -1
  42. package/.server/server/plugins/engine/types.d.ts +2 -2
  43. package/.server/server/plugins/engine/types.js.map +1 -1
  44. package/.server/server/plugins/engine/views/components/_location-field-base.html +53 -0
  45. package/.server/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
  46. package/.server/server/plugins/engine/views/components/latlongfield.html +5 -0
  47. package/.server/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
  48. package/.server/server/plugins/engine/views/components/osgridreffield.html +13 -0
  49. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  50. package/package.json +3 -3
  51. package/src/client/stylesheets/_location-input.scss +60 -0
  52. package/src/client/stylesheets/application.scss +1 -6
  53. package/src/client/stylesheets/shared.scss +8 -0
  54. package/src/server/forms/register-as-a-unicorn-breeder.yaml +28 -0
  55. package/src/server/plugins/engine/components/ComponentBase.ts +1 -1
  56. package/src/server/plugins/engine/components/EastingNorthingField.test.ts +665 -0
  57. package/src/server/plugins/engine/components/EastingNorthingField.ts +224 -0
  58. package/src/server/plugins/engine/components/LatLongField.test.ts +700 -0
  59. package/src/server/plugins/engine/components/LatLongField.ts +213 -0
  60. package/src/server/plugins/engine/components/LocationFieldBase.test.ts +253 -0
  61. package/src/server/plugins/engine/components/LocationFieldBase.ts +152 -0
  62. package/src/server/plugins/engine/components/LocationFieldHelpers.test.ts +338 -0
  63. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +123 -0
  64. package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +438 -0
  65. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +52 -0
  66. package/src/server/plugins/engine/components/OsGridRefField.test.ts +469 -0
  67. package/src/server/plugins/engine/components/OsGridRefField.ts +71 -0
  68. package/src/server/plugins/engine/components/helpers/components.test.ts +270 -0
  69. package/src/server/plugins/engine/components/helpers/components.ts +39 -47
  70. package/src/server/plugins/engine/components/helpers/helpers.test.ts +71 -1
  71. package/src/server/plugins/engine/components/index.ts +4 -0
  72. package/src/server/plugins/engine/components/markdownParser.ts +40 -0
  73. package/src/server/plugins/engine/components/types.ts +14 -0
  74. package/src/server/plugins/engine/outputFormatters/adapter/v1.location.test.ts +356 -0
  75. package/src/server/plugins/engine/pageControllers/helpers/helpers.test.ts +4 -0
  76. package/src/server/plugins/engine/pageControllers/helpers/pages.ts +8 -0
  77. package/src/server/plugins/engine/types/index.ts +2 -0
  78. package/src/server/plugins/engine/types.ts +4 -0
  79. package/src/server/plugins/engine/views/components/_location-field-base.html +53 -0
  80. package/src/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
  81. package/src/server/plugins/engine/views/components/latlongfield.html +5 -0
  82. package/src/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
  83. package/src/server/plugins/engine/views/components/osgridreffield.html +13 -0
@@ -0,0 +1,270 @@
1
+ import {
2
+ ComponentType,
3
+ type EastingNorthingFieldComponent,
4
+ type LatLongFieldComponent,
5
+ type NationalGridFieldNumberFieldComponent,
6
+ type OsGridRefFieldComponent
7
+ } from '@defra/forms-model'
8
+
9
+ import {
10
+ getAnswer,
11
+ getAnswerMarkdown
12
+ } from '~/src/server/plugins/engine/components/helpers/components.js'
13
+ import {
14
+ EastingNorthingField,
15
+ LatLongField,
16
+ NationalGridFieldNumberField,
17
+ OsGridRefField
18
+ } from '~/src/server/plugins/engine/components/index.js'
19
+ import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
20
+ import definition from '~/test/form/definitions/blank.js'
21
+
22
+ describe('Location field formatting', () => {
23
+ let model: FormModel
24
+
25
+ beforeEach(() => {
26
+ model = new FormModel(definition, {
27
+ basePath: 'test'
28
+ })
29
+ })
30
+
31
+ describe('EastingNorthingField', () => {
32
+ let field: EastingNorthingField
33
+
34
+ beforeEach(() => {
35
+ const def: EastingNorthingFieldComponent = {
36
+ type: ComponentType.EastingNorthingField,
37
+ name: 'locationEN',
38
+ title: 'Location',
39
+ options: {}
40
+ }
41
+ field = new EastingNorthingField(def, { model })
42
+ })
43
+
44
+ it('formats for email output with labels on separate lines', () => {
45
+ const state = {
46
+ locationEN__easting: 123456,
47
+ locationEN__northing: 654321
48
+ }
49
+
50
+ const answer = getAnswer(field, state, { format: 'email' })
51
+ expect(answer).toBe('Northing: 654321\nEasting: 123456\n')
52
+ })
53
+
54
+ it('formats for data output', () => {
55
+ const state = {
56
+ locationEN__easting: 123456,
57
+ locationEN__northing: 654321
58
+ }
59
+
60
+ const answer = getAnswer(field, state, { format: 'data' })
61
+ expect(answer).toBe('Northing: 654321\nEasting: 123456')
62
+ })
63
+
64
+ it('formats for summary display', () => {
65
+ const state = {
66
+ locationEN__easting: 123456,
67
+ locationEN__northing: 654321
68
+ }
69
+
70
+ const answer = getAnswer(field, state, { format: 'summary' })
71
+ // Should render as HTML from markdown
72
+ expect(answer).toContain('Northing: 654321')
73
+ expect(answer).toContain('Easting: 123456')
74
+ })
75
+
76
+ it('returns empty string when no values', () => {
77
+ const state = {}
78
+
79
+ const answer = getAnswer(field, state, { format: 'email' })
80
+ expect(answer).toBe('')
81
+ })
82
+ })
83
+
84
+ describe('LatLongField', () => {
85
+ let field: LatLongField
86
+
87
+ beforeEach(() => {
88
+ const def: LatLongFieldComponent = {
89
+ type: ComponentType.LatLongField,
90
+ name: 'locationLL',
91
+ title: 'Coordinates',
92
+ options: {}
93
+ }
94
+ field = new LatLongField(def, { model })
95
+ })
96
+
97
+ it('formats for email output with labels on separate lines', () => {
98
+ const state = {
99
+ locationLL__latitude: 51.51945,
100
+ locationLL__longitude: -0.127758
101
+ }
102
+
103
+ const answer = getAnswer(field, state, { format: 'email' })
104
+ expect(answer).toBe('Lat: 51.51945\nLong: -0.127758\n')
105
+ })
106
+
107
+ it('formats for data output', () => {
108
+ const state = {
109
+ locationLL__latitude: 51.51945,
110
+ locationLL__longitude: -0.127758
111
+ }
112
+
113
+ const answer = getAnswer(field, state, { format: 'data' })
114
+ expect(answer).toBe('Lat: 51.51945\nLong: -0.127758')
115
+ })
116
+
117
+ it('formats for summary display', () => {
118
+ const state = {
119
+ locationLL__latitude: 51.51945,
120
+ locationLL__longitude: -0.127758
121
+ }
122
+
123
+ const answer = getAnswer(field, state, { format: 'summary' })
124
+ // Should render as HTML from markdown
125
+ expect(answer).toContain('Lat: 51.51945')
126
+ expect(answer).toContain('Long: -0.127758')
127
+ })
128
+
129
+ it('returns empty string when no values', () => {
130
+ const state = {}
131
+
132
+ const answer = getAnswer(field, state, { format: 'email' })
133
+ expect(answer).toBe('')
134
+ })
135
+ })
136
+
137
+ describe('OsGridRefField', () => {
138
+ let field: OsGridRefField
139
+
140
+ beforeEach(() => {
141
+ const def: OsGridRefFieldComponent = {
142
+ type: ComponentType.OsGridRefField,
143
+ name: 'gridRef',
144
+ title: 'OS Grid Reference',
145
+ options: {}
146
+ }
147
+ field = new OsGridRefField(def, { model })
148
+ })
149
+
150
+ it('formats for email output as single value', () => {
151
+ const state = {
152
+ gridRef: 'TQ123456'
153
+ }
154
+
155
+ const answer = getAnswer(field, state, { format: 'email' })
156
+ expect(answer).toBe('TQ123456\n')
157
+ })
158
+
159
+ it('formats for data output', () => {
160
+ const state = {
161
+ gridRef: 'TQ123456'
162
+ }
163
+
164
+ const answer = getAnswer(field, state, { format: 'data' })
165
+ expect(answer).toBe('TQ123456')
166
+ })
167
+
168
+ it('formats for summary display', () => {
169
+ const state = {
170
+ gridRef: 'TQ123456'
171
+ }
172
+
173
+ const answer = getAnswer(field, state, { format: 'summary' })
174
+ expect(answer).toBe('TQ123456')
175
+ })
176
+ })
177
+
178
+ describe('NationalGridFieldNumberField', () => {
179
+ let field: NationalGridFieldNumberField
180
+
181
+ beforeEach(() => {
182
+ const def: NationalGridFieldNumberFieldComponent = {
183
+ type: ComponentType.NationalGridFieldNumberField,
184
+ name: 'ngField',
185
+ title: 'National Grid Field Number',
186
+ options: {}
187
+ }
188
+ field = new NationalGridFieldNumberField(def, { model })
189
+ })
190
+
191
+ it('formats for email output as single value', () => {
192
+ const state = {
193
+ ngField: 'NG12345678'
194
+ }
195
+
196
+ const answer = getAnswer(field, state, { format: 'email' })
197
+ expect(answer).toBe('NG12345678\n')
198
+ })
199
+
200
+ it('formats for data output', () => {
201
+ const state = {
202
+ ngField: 'NG12345678'
203
+ }
204
+
205
+ const answer = getAnswer(field, state, { format: 'data' })
206
+ expect(answer).toBe('NG12345678')
207
+ })
208
+
209
+ it('formats for summary display', () => {
210
+ const state = {
211
+ ngField: 'NG12345678'
212
+ }
213
+
214
+ const answer = getAnswer(field, state, { format: 'summary' })
215
+ expect(answer).toBe('NG12345678')
216
+ })
217
+ })
218
+
219
+ describe('getAnswerMarkdown', () => {
220
+ it('formats EastingNorthingField correctly', () => {
221
+ const def: EastingNorthingFieldComponent = {
222
+ type: ComponentType.EastingNorthingField,
223
+ name: 'locationEN',
224
+ title: 'Location',
225
+ options: {}
226
+ }
227
+ const field = new EastingNorthingField(def, { model })
228
+ const state = {
229
+ locationEN__easting: 123456,
230
+ locationEN__northing: 654321
231
+ }
232
+
233
+ const answer = getAnswerMarkdown(field, state, { format: 'email' })
234
+ expect(answer).toBe('Northing: 654321\nEasting: 123456\n')
235
+ })
236
+
237
+ it('formats LatLongField correctly', () => {
238
+ const def: LatLongFieldComponent = {
239
+ type: ComponentType.LatLongField,
240
+ name: 'locationLL',
241
+ title: 'Coordinates',
242
+ options: {}
243
+ }
244
+ const field = new LatLongField(def, { model })
245
+ const state = {
246
+ locationLL__latitude: 51.51945,
247
+ locationLL__longitude: -0.127758
248
+ }
249
+
250
+ const answer = getAnswerMarkdown(field, state, { format: 'email' })
251
+ expect(answer).toBe('Lat: 51.51945\nLong: -0.127758\n')
252
+ })
253
+
254
+ it('formats simple location fields correctly', () => {
255
+ const def: OsGridRefFieldComponent = {
256
+ type: ComponentType.OsGridRefField,
257
+ name: 'gridRef',
258
+ title: 'OS Grid Reference',
259
+ options: {}
260
+ }
261
+ const field = new OsGridRefField(def, { model })
262
+ const state = {
263
+ gridRef: 'TQ123456'
264
+ }
265
+
266
+ const answer = getAnswerMarkdown(field, state, { format: 'email' })
267
+ expect(answer).toBe('TQ123456\n')
268
+ })
269
+ })
270
+ })
@@ -1,11 +1,11 @@
1
1
  import { ComponentType, type ComponentDef } from '@defra/forms-model'
2
- import { Marked, type Token } from 'marked'
3
2
 
4
3
  import { config } from '~/src/config/index.js'
5
4
  import { type ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'
6
5
  import { ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'
7
6
  import { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers/index.js'
8
7
  import * as Components from '~/src/server/plugins/engine/components/index.js'
8
+ import { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'
9
9
  import { type FormState } from '~/src/server/plugins/engine/types.js'
10
10
 
11
11
  // All component instances
@@ -20,10 +20,14 @@ export type Field = InstanceType<
20
20
  | typeof Components.YesNoField
21
21
  | typeof Components.CheckboxesField
22
22
  | typeof Components.DatePartsField
23
+ | typeof Components.EastingNorthingField
23
24
  | typeof Components.EmailAddressField
25
+ | typeof Components.LatLongField
24
26
  | typeof Components.MonthYearField
25
27
  | typeof Components.MultilineTextField
28
+ | typeof Components.NationalGridFieldNumberField
26
29
  | typeof Components.NumberField
30
+ | typeof Components.OsGridRefField
27
31
  | typeof Components.SelectField
28
32
  | typeof Components.TelephoneNumberField
29
33
  | typeof Components.TextField
@@ -32,13 +36,12 @@ export type Field = InstanceType<
32
36
  >
33
37
 
34
38
  // Guidance component instances only
35
- export type Guidance = InstanceType<
36
- | typeof Components.Details
37
- | typeof Components.Html
38
- | typeof Components.Markdown
39
- | typeof Components.InsetText
40
- | typeof Components.List
41
- >
39
+ export type Guidance =
40
+ | InstanceType<typeof Components.Details>
41
+ | InstanceType<typeof Components.Html>
42
+ | InstanceType<typeof Components.Markdown>
43
+ | InstanceType<typeof Components.InsetText>
44
+ | InstanceType<typeof Components.List>
42
45
 
43
46
  // List component instances only
44
47
  export type ListField = InstanceType<
@@ -51,43 +54,8 @@ export type ListField = InstanceType<
51
54
 
52
55
  export const designerUrl = config.get('designerUrl')
53
56
 
54
- export const markdown = new Marked({
55
- breaks: true,
56
- gfm: true,
57
-
58
- /**
59
- * Render paragraphs without `<p>` wrappers
60
- * for check answers summary list `<dd>`
61
- */
62
- extensions: [
63
- {
64
- name: 'paragraph',
65
- renderer({ tokens = [] }) {
66
- const text = this.parser.parseInline(tokens)
67
- return tokens.length > 1 ? `${text}<br>` : text
68
- }
69
- }
70
- ],
71
-
72
- /**
73
- * Restrict allowed Markdown tokens
74
- */
75
- walkTokens(token) {
76
- const tokens: Token['type'][] = [
77
- 'br',
78
- 'escape',
79
- 'list',
80
- 'list_item',
81
- 'paragraph',
82
- 'space',
83
- 'text'
84
- ]
85
-
86
- if (!tokens.includes(token.type)) {
87
- token.type = 'text'
88
- }
89
- }
90
- })
57
+ // Re-export markdown from its own module to avoid circular dependencies
58
+ export { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'
91
59
 
92
60
  /**
93
61
  * Filter known components with lists
@@ -95,7 +63,7 @@ export const markdown = new Marked({
95
63
  export function hasListFormField(
96
64
  field?: Partial<Component>
97
65
  ): field is ListFormComponent {
98
- return !!field && isListFieldType(field.type)
66
+ return !!field && field.type !== undefined && isListFieldType(field.type)
99
67
  }
100
68
 
101
69
  export function isListFieldType(
@@ -197,6 +165,22 @@ export function createComponent(
197
165
  case ComponentType.FileUploadField:
198
166
  component = new Components.FileUploadField(def, options)
199
167
  break
168
+
169
+ case ComponentType.EastingNorthingField:
170
+ component = new Components.EastingNorthingField(def, options)
171
+ break
172
+
173
+ case ComponentType.OsGridRefField:
174
+ component = new Components.OsGridRefField(def, options)
175
+ break
176
+
177
+ case ComponentType.NationalGridFieldNumberField:
178
+ component = new Components.NationalGridFieldNumberField(def, options)
179
+ break
180
+
181
+ case ComponentType.LatLongField:
182
+ component = new Components.LatLongField(def, options)
183
+ break
200
184
  }
201
185
 
202
186
  if (typeof component === 'undefined') {
@@ -234,7 +218,9 @@ export function getAnswer(
234
218
  if (
235
219
  field instanceof ListFormComponent ||
236
220
  field instanceof Components.MultilineTextField ||
237
- field instanceof Components.UkAddressField
221
+ field instanceof Components.UkAddressField ||
222
+ field instanceof Components.EastingNorthingField ||
223
+ field instanceof Components.LatLongField
238
224
  ) {
239
225
  return markdown
240
226
  .parse(getAnswerMarkdown(field, state), { async: false })
@@ -325,6 +311,12 @@ export function getAnswerMarkdown(
325
311
  .map(escapeMarkdown)
326
312
  .join('\n')
327
313
  .concat('\n')
314
+ } else if (
315
+ field instanceof Components.EastingNorthingField ||
316
+ field instanceof Components.LatLongField
317
+ ) {
318
+ const contextValue = field.getContextValueFromState(state)
319
+ answerEscaped = contextValue ? `${contextValue}\n` : ''
328
320
  }
329
321
 
330
322
  return answerEscaped
@@ -1,6 +1,10 @@
1
- import { type ComponentDef } from '@defra/forms-model'
1
+ import { ComponentType, type ComponentDef } from '@defra/forms-model'
2
2
 
3
3
  import { ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'
4
+ import { EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'
5
+ import { LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js'
6
+ import { NationalGridFieldNumberField } from '~/src/server/plugins/engine/components/NationalGridFieldNumberField.js'
7
+ import { OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'
4
8
  import { createComponent } from '~/src/server/plugins/engine/components/helpers/components.js'
5
9
  import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
6
10
  import definition from '~/test/form/definitions/basic.js'
@@ -22,6 +26,72 @@ describe('helpers tests', () => {
22
26
  )
23
27
  ).toThrow('Component type invalid-type does not exist')
24
28
  })
29
+
30
+ test('should create EastingNorthingField component', () => {
31
+ const component = createComponent(
32
+ {
33
+ type: ComponentType.EastingNorthingField,
34
+ name: 'testField',
35
+ title: 'Test Easting Northing',
36
+ options: {},
37
+ schema: {}
38
+ },
39
+ { model: formModel }
40
+ )
41
+
42
+ expect(component).toBeInstanceOf(EastingNorthingField)
43
+ expect(component.name).toBe('testField')
44
+ expect(component.title).toBe('Test Easting Northing')
45
+ })
46
+
47
+ test('should create LatLongField component', () => {
48
+ const component = createComponent(
49
+ {
50
+ type: ComponentType.LatLongField,
51
+ name: 'testField',
52
+ title: 'Test Lat Long',
53
+ options: {},
54
+ schema: {}
55
+ },
56
+ { model: formModel }
57
+ )
58
+
59
+ expect(component).toBeInstanceOf(LatLongField)
60
+ expect(component.name).toBe('testField')
61
+ expect(component.title).toBe('Test Lat Long')
62
+ })
63
+
64
+ test('should create OsGridRefField component', () => {
65
+ const component = createComponent(
66
+ {
67
+ type: ComponentType.OsGridRefField,
68
+ name: 'testField',
69
+ title: 'Test OS Grid Ref',
70
+ options: {}
71
+ },
72
+ { model: formModel }
73
+ )
74
+
75
+ expect(component).toBeInstanceOf(OsGridRefField)
76
+ expect(component.name).toBe('testField')
77
+ expect(component.title).toBe('Test OS Grid Ref')
78
+ })
79
+
80
+ test('should create NationalGridFieldNumberField component', () => {
81
+ const component = createComponent(
82
+ {
83
+ type: ComponentType.NationalGridFieldNumberField,
84
+ name: 'testField',
85
+ title: 'Test National Grid',
86
+ options: {}
87
+ },
88
+ { model: formModel }
89
+ )
90
+
91
+ expect(component).toBeInstanceOf(NationalGridFieldNumberField)
92
+ expect(component.name).toBe('testField')
93
+ expect(component.title).toBe('Test National Grid')
94
+ })
25
95
  })
26
96
 
27
97
  describe('ComponentBase tests', () => {
@@ -23,3 +23,7 @@ export { TelephoneNumberField } from '~/src/server/plugins/engine/components/Tel
23
23
  export { TextField } from '~/src/server/plugins/engine/components/TextField.js'
24
24
  export { UkAddressField } from '~/src/server/plugins/engine/components/UkAddressField.js'
25
25
  export { YesNoField } from '~/src/server/plugins/engine/components/YesNoField.js'
26
+ export { EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'
27
+ export { OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'
28
+ export { NationalGridFieldNumberField } from '~/src/server/plugins/engine/components/NationalGridFieldNumberField.js'
29
+ export { LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js'
@@ -0,0 +1,40 @@
1
+ import { Marked, type Token } from 'marked'
2
+
3
+ export const markdown = new Marked({
4
+ breaks: true,
5
+ gfm: true,
6
+
7
+ /**
8
+ * Render paragraphs without `<p>` wrappers
9
+ * for check answers summary list `<dd>`
10
+ */
11
+ extensions: [
12
+ {
13
+ name: 'paragraph',
14
+ renderer({ tokens = [] }) {
15
+ const text = this.parser.parseInline(tokens)
16
+ return tokens.length > 1 ? `${text}<br>` : text
17
+ }
18
+ }
19
+ ],
20
+
21
+ /**
22
+ * Restrict allowed Markdown tokens
23
+ */
24
+ walkTokens(token) {
25
+ const tokens: Token['type'][] = [
26
+ 'br',
27
+ 'escape',
28
+ 'link',
29
+ 'list',
30
+ 'list_item',
31
+ 'paragraph',
32
+ 'space',
33
+ 'text'
34
+ ]
35
+
36
+ if (!tokens.includes(token.type)) {
37
+ token.type = 'text'
38
+ }
39
+ }
40
+ })
@@ -60,6 +60,10 @@ export interface DateInputItem {
60
60
  name?: string
61
61
  value?: Item['value']
62
62
  classes?: string
63
+ // Prefix/suffix are used by location fields (e.g., LatLong, EastingNorthing) for units like "°"
64
+ // but not by date fields. This interface is reused by both component types.
65
+ prefix?: ComponentText
66
+ suffix?: ComponentText
63
67
  condition?: undefined
64
68
  }
65
69
 
@@ -126,3 +130,13 @@ export interface MonthYearState extends Record<string, number> {
126
130
  month: number
127
131
  year: number
128
132
  }
133
+
134
+ export interface EastingNorthingState extends Record<string, number> {
135
+ easting: number
136
+ northing: number
137
+ }
138
+
139
+ export interface LatLongState extends Record<string, number> {
140
+ latitude: number
141
+ longitude: number
142
+ }