@defra/forms-engine-plugin 4.0.6 → 4.0.8
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.
- package/.public/stylesheets/application.min.css +1 -1
- package/.public/stylesheets/application.min.css.map +1 -1
- package/.server/client/stylesheets/_location-input.scss +60 -0
- package/.server/client/stylesheets/application.scss +1 -0
- package/.server/client/stylesheets/shared.scss +1 -0
- package/.server/server/forms/components.json +7 -0
- package/.server/server/forms/register-as-a-unicorn-breeder.yaml +40 -1
- package/.server/server/plugins/engine/components/ComponentBase.d.ts +2 -2
- package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
- package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
- package/.server/server/plugins/engine/components/DeclarationField.d.ts +81 -0
- package/.server/server/plugins/engine/components/DeclarationField.js +123 -0
- package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -0
- package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +121 -0
- package/.server/server/plugins/engine/components/EastingNorthingField.js +166 -0
- package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -0
- package/.server/server/plugins/engine/components/LatLongField.d.ts +121 -0
- package/.server/server/plugins/engine/components/LatLongField.js +164 -0
- package/.server/server/plugins/engine/components/LatLongField.js.map +1 -0
- package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +134 -0
- package/.server/server/plugins/engine/components/LocationFieldBase.js +85 -0
- package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -0
- package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +108 -0
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js +96 -0
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -0
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +19 -0
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +40 -0
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -0
- package/.server/server/plugins/engine/components/OsGridRefField.d.ts +19 -0
- package/.server/server/plugins/engine/components/OsGridRefField.js +56 -0
- package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -0
- package/.server/server/plugins/engine/components/helpers/components.d.ts +3 -4
- package/.server/server/plugins/engine/components/helpers/components.js +24 -29
- package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
- package/.server/server/plugins/engine/components/index.d.ts +5 -0
- package/.server/server/plugins/engine/components/index.js +5 -0
- package/.server/server/plugins/engine/components/index.js.map +1 -1
- package/.server/server/plugins/engine/components/markdownParser.d.ts +2 -0
- package/.server/server/plugins/engine/components/markdownParser.js +28 -0
- package/.server/server/plugins/engine/components/markdownParser.js.map +1 -0
- package/.server/server/plugins/engine/components/types.d.ts +10 -0
- package/.server/server/plugins/engine/components/types.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/helpers/pages.js +7 -0
- package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/validationOptions.js +1 -0
- package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
- package/.server/server/plugins/engine/types/index.d.ts +1 -1
- package/.server/server/plugins/engine/types/index.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +2 -2
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/components/_location-field-base.html +53 -0
- package/.server/server/plugins/engine/views/components/declarationfield.html +14 -0
- package/.server/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
- package/.server/server/plugins/engine/views/components/latlongfield.html +5 -0
- package/.server/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
- package/.server/server/plugins/engine/views/components/osgridreffield.html +13 -0
- package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
- package/.server/server/plugins/nunjucks/filters/index.d.ts +1 -0
- package/.server/server/plugins/nunjucks/filters/index.js +1 -0
- package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
- package/.server/server/plugins/nunjucks/filters/merge.d.ts +7 -0
- package/.server/server/plugins/nunjucks/filters/merge.js +16 -0
- package/.server/server/plugins/nunjucks/filters/merge.js.map +1 -0
- package/.server/server/plugins/nunjucks/filters/merge.test.js +19 -0
- package/.server/server/plugins/nunjucks/filters/merge.test.js.map +1 -0
- package/package.json +3 -3
- package/src/client/stylesheets/_location-input.scss +60 -0
- package/src/client/stylesheets/application.scss +1 -0
- package/src/client/stylesheets/shared.scss +1 -0
- package/src/server/forms/components.json +7 -0
- package/src/server/forms/page-events.yaml +1 -1
- package/src/server/forms/register-as-a-unicorn-breeder.yaml +40 -1
- package/src/server/index.test.ts +1 -0
- package/src/server/plugins/engine/components/ComponentBase.ts +2 -1
- package/src/server/plugins/engine/components/ComponentCollection.ts +1 -0
- package/src/server/plugins/engine/components/DeclarationField.test.ts +426 -0
- package/src/server/plugins/engine/components/DeclarationField.ts +167 -0
- package/src/server/plugins/engine/components/EastingNorthingField.test.ts +665 -0
- package/src/server/plugins/engine/components/EastingNorthingField.ts +224 -0
- package/src/server/plugins/engine/components/LatLongField.test.ts +700 -0
- package/src/server/plugins/engine/components/LatLongField.ts +213 -0
- package/src/server/plugins/engine/components/LocationFieldBase.test.ts +253 -0
- package/src/server/plugins/engine/components/LocationFieldBase.ts +152 -0
- package/src/server/plugins/engine/components/LocationFieldHelpers.test.ts +338 -0
- package/src/server/plugins/engine/components/LocationFieldHelpers.ts +123 -0
- package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +438 -0
- package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +52 -0
- package/src/server/plugins/engine/components/OsGridRefField.test.ts +469 -0
- package/src/server/plugins/engine/components/OsGridRefField.ts +71 -0
- package/src/server/plugins/engine/components/helpers/components.test.ts +270 -0
- package/src/server/plugins/engine/components/helpers/components.ts +44 -47
- package/src/server/plugins/engine/components/helpers/helpers.test.ts +71 -1
- package/src/server/plugins/engine/components/index.ts +5 -0
- package/src/server/plugins/engine/components/markdownParser.ts +40 -0
- package/src/server/plugins/engine/components/types.ts +14 -0
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +76 -3
- package/src/server/plugins/engine/models/SummaryViewModel.ts +5 -1
- package/src/server/plugins/engine/outputFormatters/adapter/v1.location.test.ts +356 -0
- package/src/server/plugins/engine/pageControllers/helpers/helpers.test.ts +4 -0
- package/src/server/plugins/engine/pageControllers/helpers/pages.ts +8 -0
- package/src/server/plugins/engine/pageControllers/validationOptions.ts +4 -0
- package/src/server/plugins/engine/types/index.ts +2 -0
- package/src/server/plugins/engine/types.ts +4 -0
- package/src/server/plugins/engine/views/components/_location-field-base.html +53 -0
- package/src/server/plugins/engine/views/components/declarationfield.html +14 -0
- package/src/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
- package/src/server/plugins/engine/views/components/latlongfield.html +5 -0
- package/src/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
- package/src/server/plugins/engine/views/components/osgridreffield.html +13 -0
- package/src/server/plugins/nunjucks/filters/index.js +1 -0
- package/src/server/plugins/nunjucks/filters/merge.js +16 -0
- package/src/server/plugins/nunjucks/filters/merge.test.js +15 -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,15 @@ export type Field = InstanceType<
|
|
|
20
20
|
| typeof Components.YesNoField
|
|
21
21
|
| typeof Components.CheckboxesField
|
|
22
22
|
| typeof Components.DatePartsField
|
|
23
|
+
| typeof Components.DeclarationField
|
|
24
|
+
| typeof Components.EastingNorthingField
|
|
23
25
|
| typeof Components.EmailAddressField
|
|
26
|
+
| typeof Components.LatLongField
|
|
24
27
|
| typeof Components.MonthYearField
|
|
25
28
|
| typeof Components.MultilineTextField
|
|
29
|
+
| typeof Components.NationalGridFieldNumberField
|
|
26
30
|
| typeof Components.NumberField
|
|
31
|
+
| typeof Components.OsGridRefField
|
|
27
32
|
| typeof Components.SelectField
|
|
28
33
|
| typeof Components.TelephoneNumberField
|
|
29
34
|
| typeof Components.TextField
|
|
@@ -32,13 +37,12 @@ export type Field = InstanceType<
|
|
|
32
37
|
>
|
|
33
38
|
|
|
34
39
|
// Guidance component instances only
|
|
35
|
-
export type Guidance =
|
|
36
|
-
| typeof Components.Details
|
|
37
|
-
| typeof Components.Html
|
|
38
|
-
| typeof Components.Markdown
|
|
39
|
-
| typeof Components.InsetText
|
|
40
|
-
| typeof Components.List
|
|
41
|
-
>
|
|
40
|
+
export type Guidance =
|
|
41
|
+
| InstanceType<typeof Components.Details>
|
|
42
|
+
| InstanceType<typeof Components.Html>
|
|
43
|
+
| InstanceType<typeof Components.Markdown>
|
|
44
|
+
| InstanceType<typeof Components.InsetText>
|
|
45
|
+
| InstanceType<typeof Components.List>
|
|
42
46
|
|
|
43
47
|
// List component instances only
|
|
44
48
|
export type ListField = InstanceType<
|
|
@@ -51,43 +55,8 @@ export type ListField = InstanceType<
|
|
|
51
55
|
|
|
52
56
|
export const designerUrl = config.get('designerUrl')
|
|
53
57
|
|
|
54
|
-
export
|
|
55
|
-
|
|
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
|
-
})
|
|
58
|
+
// Re-export markdown from its own module to avoid circular dependencies
|
|
59
|
+
export { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'
|
|
91
60
|
|
|
92
61
|
/**
|
|
93
62
|
* Filter known components with lists
|
|
@@ -95,7 +64,7 @@ export const markdown = new Marked({
|
|
|
95
64
|
export function hasListFormField(
|
|
96
65
|
field?: Partial<Component>
|
|
97
66
|
): field is ListFormComponent {
|
|
98
|
-
return !!field && isListFieldType(field.type)
|
|
67
|
+
return !!field && field.type !== undefined && isListFieldType(field.type)
|
|
99
68
|
}
|
|
100
69
|
|
|
101
70
|
export function isListFieldType(
|
|
@@ -134,6 +103,10 @@ export function createComponent(
|
|
|
134
103
|
component = new Components.DatePartsField(def, options)
|
|
135
104
|
break
|
|
136
105
|
|
|
106
|
+
case ComponentType.DeclarationField:
|
|
107
|
+
component = new Components.DeclarationField(def, options)
|
|
108
|
+
break
|
|
109
|
+
|
|
137
110
|
case ComponentType.Details:
|
|
138
111
|
component = new Components.Details(def, options)
|
|
139
112
|
break
|
|
@@ -197,6 +170,22 @@ export function createComponent(
|
|
|
197
170
|
case ComponentType.FileUploadField:
|
|
198
171
|
component = new Components.FileUploadField(def, options)
|
|
199
172
|
break
|
|
173
|
+
|
|
174
|
+
case ComponentType.EastingNorthingField:
|
|
175
|
+
component = new Components.EastingNorthingField(def, options)
|
|
176
|
+
break
|
|
177
|
+
|
|
178
|
+
case ComponentType.OsGridRefField:
|
|
179
|
+
component = new Components.OsGridRefField(def, options)
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
case ComponentType.NationalGridFieldNumberField:
|
|
183
|
+
component = new Components.NationalGridFieldNumberField(def, options)
|
|
184
|
+
break
|
|
185
|
+
|
|
186
|
+
case ComponentType.LatLongField:
|
|
187
|
+
component = new Components.LatLongField(def, options)
|
|
188
|
+
break
|
|
200
189
|
}
|
|
201
190
|
|
|
202
191
|
if (typeof component === 'undefined') {
|
|
@@ -234,7 +223,9 @@ export function getAnswer(
|
|
|
234
223
|
if (
|
|
235
224
|
field instanceof ListFormComponent ||
|
|
236
225
|
field instanceof Components.MultilineTextField ||
|
|
237
|
-
field instanceof Components.UkAddressField
|
|
226
|
+
field instanceof Components.UkAddressField ||
|
|
227
|
+
field instanceof Components.EastingNorthingField ||
|
|
228
|
+
field instanceof Components.LatLongField
|
|
238
229
|
) {
|
|
239
230
|
return markdown
|
|
240
231
|
.parse(getAnswerMarkdown(field, state), { async: false })
|
|
@@ -325,6 +316,12 @@ export function getAnswerMarkdown(
|
|
|
325
316
|
.map(escapeMarkdown)
|
|
326
317
|
.join('\n')
|
|
327
318
|
.concat('\n')
|
|
319
|
+
} else if (
|
|
320
|
+
field instanceof Components.EastingNorthingField ||
|
|
321
|
+
field instanceof Components.LatLongField
|
|
322
|
+
) {
|
|
323
|
+
const contextValue = field.getContextValueFromState(state)
|
|
324
|
+
answerEscaped = contextValue ? `${contextValue}\n` : ''
|
|
328
325
|
}
|
|
329
326
|
|
|
330
327
|
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', () => {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
export { AutocompleteField } from '~/src/server/plugins/engine/components/AutocompleteField.js'
|
|
8
8
|
export { CheckboxesField } from '~/src/server/plugins/engine/components/CheckboxesField.js'
|
|
9
9
|
export { DatePartsField } from '~/src/server/plugins/engine/components/DatePartsField.js'
|
|
10
|
+
export { DeclarationField } from '~/src/server/plugins/engine/components/DeclarationField.js'
|
|
10
11
|
export { Details } from '~/src/server/plugins/engine/components/Details.js'
|
|
11
12
|
export { EmailAddressField } from '~/src/server/plugins/engine/components/EmailAddressField.js'
|
|
12
13
|
export { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'
|
|
@@ -23,3 +24,7 @@ export { TelephoneNumberField } from '~/src/server/plugins/engine/components/Tel
|
|
|
23
24
|
export { TextField } from '~/src/server/plugins/engine/components/TextField.js'
|
|
24
25
|
export { UkAddressField } from '~/src/server/plugins/engine/components/UkAddressField.js'
|
|
25
26
|
export { YesNoField } from '~/src/server/plugins/engine/components/YesNoField.js'
|
|
27
|
+
export { EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'
|
|
28
|
+
export { OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'
|
|
29
|
+
export { NationalGridFieldNumberField } from '~/src/server/plugins/engine/components/NationalGridFieldNumberField.js'
|
|
30
|
+
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
|
+
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type FormContextRequest,
|
|
16
16
|
type FormState
|
|
17
17
|
} from '~/src/server/plugins/engine/types.js'
|
|
18
|
+
import v2Definition from '~/test/form/definitions/conditions-relative-dates-v2.js'
|
|
18
19
|
import definition from '~/test/form/definitions/repeat-mixed.js'
|
|
19
20
|
const basePath = `${FORM_PREFIX}/test`
|
|
20
21
|
|
|
@@ -326,7 +327,7 @@ describe('SummaryPageController', () => {
|
|
|
326
327
|
expect(viewModel).toHaveProperty('allowSaveAndExit', true)
|
|
327
328
|
})
|
|
328
329
|
|
|
329
|
-
it('should display correct page title', () => {
|
|
330
|
+
it('should display correct page title for v1 form', () => {
|
|
330
331
|
const state: FormState = {
|
|
331
332
|
$$__referenceNumber: 'foobar',
|
|
332
333
|
orderType: 'collection',
|
|
@@ -334,9 +335,81 @@ describe('SummaryPageController', () => {
|
|
|
334
335
|
}
|
|
335
336
|
|
|
336
337
|
const context = model.getFormContext(request, state)
|
|
337
|
-
const viewModel = controller.
|
|
338
|
+
const viewModel = controller.getSummaryViewModel(request, context)
|
|
339
|
+
|
|
340
|
+
expect(viewModel.pageTitle).toBe(
|
|
341
|
+
'Check your answers before sending your form'
|
|
342
|
+
)
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('should display default page title for v2 form when title not supplied', () => {
|
|
346
|
+
const state: FormState = {
|
|
347
|
+
$$__referenceNumber: 'foobar',
|
|
348
|
+
orderType: 'collection',
|
|
349
|
+
pizza: []
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const titleModel = new FormModel(v2Definition, {
|
|
353
|
+
basePath: `${FORM_PREFIX}/test`
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
controller = new SummaryPageController(titleModel, v2Definition.pages[5])
|
|
357
|
+
|
|
358
|
+
request = {
|
|
359
|
+
method: 'get',
|
|
360
|
+
url: new URL('http://example.com/repeat/pizza-order/summary'),
|
|
361
|
+
path: '/test/summary',
|
|
362
|
+
params: {
|
|
363
|
+
path: 'summary',
|
|
364
|
+
slug: 'test'
|
|
365
|
+
},
|
|
366
|
+
query: {},
|
|
367
|
+
app: { model: titleModel },
|
|
368
|
+
server: serverWithSaveAndExit
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const context = titleModel.getFormContext(request, state)
|
|
372
|
+
const viewModel = controller.getSummaryViewModel(request, context)
|
|
373
|
+
|
|
374
|
+
expect(viewModel.pageTitle).toBe(
|
|
375
|
+
'Check your answers before sending your form'
|
|
376
|
+
)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('should display override page title for v2 form when title supplied', () => {
|
|
380
|
+
const state: FormState = {
|
|
381
|
+
$$__referenceNumber: 'foobar',
|
|
382
|
+
orderType: 'collection',
|
|
383
|
+
pizza: []
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const v2DefinitionWithSummaryTitle = structuredClone(v2Definition)
|
|
387
|
+
const summaryPage = v2DefinitionWithSummaryTitle.pages[5]
|
|
388
|
+
summaryPage.title = 'Override summary title'
|
|
389
|
+
|
|
390
|
+
const titleModel = new FormModel(v2DefinitionWithSummaryTitle, {
|
|
391
|
+
basePath: `${FORM_PREFIX}/test`
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
controller = new SummaryPageController(titleModel, summaryPage)
|
|
395
|
+
|
|
396
|
+
request = {
|
|
397
|
+
method: 'get',
|
|
398
|
+
url: new URL('http://example.com/repeat/pizza-order/summary'),
|
|
399
|
+
path: '/test/summary',
|
|
400
|
+
params: {
|
|
401
|
+
path: 'summary',
|
|
402
|
+
slug: 'test'
|
|
403
|
+
},
|
|
404
|
+
query: {},
|
|
405
|
+
app: { model: titleModel },
|
|
406
|
+
server: serverWithSaveAndExit
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const context = titleModel.getFormContext(request, state)
|
|
410
|
+
const viewModel = controller.getSummaryViewModel(request, context)
|
|
338
411
|
|
|
339
|
-
expect(viewModel.pageTitle).toBe('
|
|
412
|
+
expect(viewModel.pageTitle).toBe('Override summary title')
|
|
340
413
|
})
|
|
341
414
|
})
|
|
342
415
|
})
|