@defra/forms-engine-plugin 3.0.0 → 3.0.2
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/.server/server/plugins/engine/models/FormModel.d.ts +2 -0
- package/.server/server/plugins/engine/models/FormModel.js +4 -1
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.d.ts +4 -0
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +25 -0
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js +7 -6
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.d.ts +1 -0
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.js +8 -1
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
- package/.server/server/plugins/engine/routes/index.js +3 -1
- package/.server/server/plugins/engine/routes/index.js.map +1 -1
- package/.server/server/plugins/engine/types/schema.js +3 -2
- package/.server/server/plugins/engine/types/schema.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +4 -1
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/repeat-list-summary.html +15 -5
- package/package.json +2 -2
- package/src/server/plugins/engine/models/FormModel.test.ts +64 -0
- package/src/server/plugins/engine/models/FormModel.ts +5 -2
- package/src/server/plugins/engine/outputFormatters/adapter/v1.test.ts +446 -13
- package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +37 -0
- package/src/server/plugins/engine/outputFormatters/machine/v2.ts +8 -6
- package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +8 -1
- package/src/server/plugins/engine/routes/index.ts +3 -1
- package/src/server/plugins/engine/types/schema.test.ts +40 -0
- package/src/server/plugins/engine/types/schema.ts +3 -1
- package/src/server/plugins/engine/types.ts +4 -0
- package/src/server/plugins/engine/views/repeat-list-summary.html +15 -5
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
type DetailItemField,
|
|
12
12
|
type DetailItemRepeat
|
|
13
13
|
} from '~/src/server/plugins/engine/models/types.js'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
format,
|
|
16
|
+
getVersionMetadata
|
|
17
|
+
} from '~/src/server/plugins/engine/outputFormatters/adapter/v1.js'
|
|
15
18
|
import { buildFormContextRequest } from '~/src/server/plugins/engine/pageControllers/__stubs__/request.js'
|
|
16
19
|
import { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/index.js'
|
|
17
20
|
import {
|
|
@@ -207,7 +210,7 @@ describe('Adapter v1 formatter', () => {
|
|
|
207
210
|
})
|
|
208
211
|
|
|
209
212
|
it('should return the adapter v1 output with complete formMetadata', () => {
|
|
210
|
-
const formMetadata: FormMetadata = {
|
|
213
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
211
214
|
id: 'form-123',
|
|
212
215
|
slug: 'test-form',
|
|
213
216
|
title: 'Test Form',
|
|
@@ -225,7 +228,7 @@ describe('Adapter v1 formatter', () => {
|
|
|
225
228
|
model,
|
|
226
229
|
submitResponse,
|
|
227
230
|
formStatus,
|
|
228
|
-
formMetadata
|
|
231
|
+
formMetadata as FormMetadata
|
|
229
232
|
)
|
|
230
233
|
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
231
234
|
|
|
@@ -284,7 +287,7 @@ describe('Adapter v1 formatter', () => {
|
|
|
284
287
|
})
|
|
285
288
|
|
|
286
289
|
it('should handle preview form status correctly', () => {
|
|
287
|
-
const formMetadata: FormMetadata = {
|
|
290
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
288
291
|
id: 'form-123',
|
|
289
292
|
slug: 'test-form',
|
|
290
293
|
title: 'Test Form',
|
|
@@ -302,7 +305,7 @@ describe('Adapter v1 formatter', () => {
|
|
|
302
305
|
model,
|
|
303
306
|
submitResponse,
|
|
304
307
|
formStatus,
|
|
305
|
-
formMetadata
|
|
308
|
+
formMetadata as FormMetadata
|
|
306
309
|
)
|
|
307
310
|
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
308
311
|
|
|
@@ -327,7 +330,7 @@ describe('Adapter v1 formatter', () => {
|
|
|
327
330
|
})
|
|
328
331
|
|
|
329
332
|
it('should handle partial formMetadata', () => {
|
|
330
|
-
const formMetadata: FormMetadata = {
|
|
333
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
331
334
|
id: 'form-456',
|
|
332
335
|
slug: 'partial-form',
|
|
333
336
|
title: 'Partial Form'
|
|
@@ -344,7 +347,7 @@ describe('Adapter v1 formatter', () => {
|
|
|
344
347
|
model,
|
|
345
348
|
submitResponse,
|
|
346
349
|
formStatus,
|
|
347
|
-
formMetadata
|
|
350
|
+
formMetadata as FormMetadata
|
|
348
351
|
)
|
|
349
352
|
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
350
353
|
|
|
@@ -452,7 +455,7 @@ describe('Adapter v1 formatter', () => {
|
|
|
452
455
|
})
|
|
453
456
|
|
|
454
457
|
it('should handle formMetadata with only id', () => {
|
|
455
|
-
const formMetadata: FormMetadata = {
|
|
458
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
456
459
|
id: 'only-id-form'
|
|
457
460
|
} as FormMetadata
|
|
458
461
|
|
|
@@ -467,7 +470,7 @@ describe('Adapter v1 formatter', () => {
|
|
|
467
470
|
model,
|
|
468
471
|
submitResponse,
|
|
469
472
|
formStatus,
|
|
470
|
-
formMetadata
|
|
473
|
+
formMetadata as FormMetadata
|
|
471
474
|
)
|
|
472
475
|
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
473
476
|
|
|
@@ -477,7 +480,7 @@ describe('Adapter v1 formatter', () => {
|
|
|
477
480
|
})
|
|
478
481
|
|
|
479
482
|
it('should handle formMetadata with only slug', () => {
|
|
480
|
-
const formMetadata: FormMetadata = {
|
|
483
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
481
484
|
slug: 'only-slug-form'
|
|
482
485
|
} as FormMetadata
|
|
483
486
|
|
|
@@ -492,7 +495,7 @@ describe('Adapter v1 formatter', () => {
|
|
|
492
495
|
model,
|
|
493
496
|
submitResponse,
|
|
494
497
|
formStatus,
|
|
495
|
-
formMetadata
|
|
498
|
+
formMetadata as FormMetadata
|
|
496
499
|
)
|
|
497
500
|
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
498
501
|
|
|
@@ -502,7 +505,7 @@ describe('Adapter v1 formatter', () => {
|
|
|
502
505
|
})
|
|
503
506
|
|
|
504
507
|
it('should handle formMetadata with only notificationEmail', () => {
|
|
505
|
-
const formMetadata: FormMetadata = {
|
|
508
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
506
509
|
notificationEmail: 'only-email@example.com'
|
|
507
510
|
} as FormMetadata
|
|
508
511
|
|
|
@@ -517,7 +520,7 @@ describe('Adapter v1 formatter', () => {
|
|
|
517
520
|
model,
|
|
518
521
|
submitResponse,
|
|
519
522
|
formStatus,
|
|
520
|
-
formMetadata
|
|
523
|
+
formMetadata as FormMetadata
|
|
521
524
|
)
|
|
522
525
|
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
523
526
|
|
|
@@ -693,4 +696,434 @@ describe('Adapter v1 formatter', () => {
|
|
|
693
696
|
}
|
|
694
697
|
})
|
|
695
698
|
})
|
|
699
|
+
|
|
700
|
+
it('should handle missing versionMetadata gracefully', () => {
|
|
701
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
702
|
+
id: 'form-123',
|
|
703
|
+
slug: 'test-form',
|
|
704
|
+
title: 'Test Form',
|
|
705
|
+
notificationEmail: 'test@example.com'
|
|
706
|
+
} as FormMetadata
|
|
707
|
+
|
|
708
|
+
const formStatus = {
|
|
709
|
+
isPreview: false,
|
|
710
|
+
state: FormStatus.Live
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const body = format(
|
|
714
|
+
context,
|
|
715
|
+
items,
|
|
716
|
+
model,
|
|
717
|
+
submitResponse,
|
|
718
|
+
formStatus,
|
|
719
|
+
formMetadata as FormMetadata
|
|
720
|
+
)
|
|
721
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
722
|
+
|
|
723
|
+
expect(parsedBody.meta.versionMetadata).toBeUndefined()
|
|
724
|
+
})
|
|
725
|
+
|
|
726
|
+
describe('version metadata handling', () => {
|
|
727
|
+
it('should include versionMetadata when context has submittedVersionNumber and formMetadata has versions', () => {
|
|
728
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
729
|
+
id: 'form-123',
|
|
730
|
+
slug: 'test-form',
|
|
731
|
+
title: 'Test Form',
|
|
732
|
+
notificationEmail: 'test@example.com',
|
|
733
|
+
versions: [
|
|
734
|
+
{
|
|
735
|
+
versionNumber: 1,
|
|
736
|
+
createdAt: new Date('2024-01-01T00:00:00.000Z')
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
versionNumber: 2,
|
|
740
|
+
createdAt: new Date('2024-01-15T00:00:00.000Z')
|
|
741
|
+
}
|
|
742
|
+
]
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const modelWithVersion = new FormModel(definition, {
|
|
746
|
+
basePath: 'test',
|
|
747
|
+
versionNumber: 2
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
const contextWithVersion = modelWithVersion.getFormContext(request, state)
|
|
751
|
+
|
|
752
|
+
const formStatus = {
|
|
753
|
+
isPreview: false,
|
|
754
|
+
state: FormStatus.Live
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const body = format(
|
|
758
|
+
contextWithVersion,
|
|
759
|
+
items,
|
|
760
|
+
modelWithVersion,
|
|
761
|
+
submitResponse,
|
|
762
|
+
formStatus,
|
|
763
|
+
formMetadata as FormMetadata
|
|
764
|
+
)
|
|
765
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
766
|
+
|
|
767
|
+
expect(parsedBody.meta.versionMetadata).toEqual({
|
|
768
|
+
versionNumber: 2,
|
|
769
|
+
createdAt: '2024-01-15T00:00:00.000Z'
|
|
770
|
+
})
|
|
771
|
+
})
|
|
772
|
+
|
|
773
|
+
it('should use first version as fallback when submittedVersionNumber is undefined', () => {
|
|
774
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
775
|
+
id: 'form-123',
|
|
776
|
+
slug: 'test-form',
|
|
777
|
+
title: 'Test Form',
|
|
778
|
+
notificationEmail: 'test@example.com',
|
|
779
|
+
versions: [
|
|
780
|
+
{
|
|
781
|
+
versionNumber: 1,
|
|
782
|
+
createdAt: new Date('2024-01-01T00:00:00.000Z')
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
versionNumber: 2,
|
|
786
|
+
createdAt: new Date('2024-01-15T00:00:00.000Z')
|
|
787
|
+
}
|
|
788
|
+
]
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const formStatus = {
|
|
792
|
+
isPreview: false,
|
|
793
|
+
state: FormStatus.Live
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const body = format(
|
|
797
|
+
context,
|
|
798
|
+
items,
|
|
799
|
+
model,
|
|
800
|
+
submitResponse,
|
|
801
|
+
formStatus,
|
|
802
|
+
formMetadata as FormMetadata
|
|
803
|
+
)
|
|
804
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
805
|
+
|
|
806
|
+
expect(parsedBody.meta.versionMetadata).toEqual({
|
|
807
|
+
versionNumber: 1,
|
|
808
|
+
createdAt: '2024-01-01T00:00:00.000Z'
|
|
809
|
+
})
|
|
810
|
+
})
|
|
811
|
+
|
|
812
|
+
it('should not include versionMetadata when submittedVersionNumber is undefined and no versions exist', () => {
|
|
813
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
814
|
+
id: 'form-123',
|
|
815
|
+
slug: 'test-form',
|
|
816
|
+
title: 'Test Form',
|
|
817
|
+
notificationEmail: 'test@example.com'
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const formStatus = {
|
|
821
|
+
isPreview: false,
|
|
822
|
+
state: FormStatus.Live
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const body = format(
|
|
826
|
+
context,
|
|
827
|
+
items,
|
|
828
|
+
model,
|
|
829
|
+
submitResponse,
|
|
830
|
+
formStatus,
|
|
831
|
+
formMetadata as FormMetadata
|
|
832
|
+
)
|
|
833
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
834
|
+
|
|
835
|
+
expect(parsedBody.meta.versionMetadata).toBeUndefined()
|
|
836
|
+
})
|
|
837
|
+
|
|
838
|
+
it('should not include versionMetadata when submittedVersionNumber is undefined and versions array is empty', () => {
|
|
839
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
840
|
+
id: 'form-123',
|
|
841
|
+
slug: 'test-form',
|
|
842
|
+
title: 'Test Form',
|
|
843
|
+
notificationEmail: 'test@example.com',
|
|
844
|
+
versions: []
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const formStatus = {
|
|
848
|
+
isPreview: false,
|
|
849
|
+
state: FormStatus.Live
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const body = format(
|
|
853
|
+
context,
|
|
854
|
+
items,
|
|
855
|
+
model,
|
|
856
|
+
submitResponse,
|
|
857
|
+
formStatus,
|
|
858
|
+
formMetadata as FormMetadata
|
|
859
|
+
)
|
|
860
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
861
|
+
|
|
862
|
+
expect(parsedBody.meta.versionMetadata).toBeUndefined()
|
|
863
|
+
})
|
|
864
|
+
|
|
865
|
+
it('should not include versionMetadata when submittedVersionNumber does not match any version', () => {
|
|
866
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
867
|
+
id: 'form-123',
|
|
868
|
+
slug: 'test-form',
|
|
869
|
+
title: 'Test Form',
|
|
870
|
+
notificationEmail: 'test@example.com',
|
|
871
|
+
versions: [
|
|
872
|
+
{
|
|
873
|
+
versionNumber: 1,
|
|
874
|
+
createdAt: new Date('2024-01-01T00:00:00.000Z')
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
versionNumber: 2,
|
|
878
|
+
createdAt: new Date('2024-01-15T00:00:00.000Z')
|
|
879
|
+
}
|
|
880
|
+
]
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const modelWithVersion = new FormModel(definition, {
|
|
884
|
+
basePath: 'test',
|
|
885
|
+
versionNumber: 99 // Non-existent version
|
|
886
|
+
})
|
|
887
|
+
|
|
888
|
+
const contextWithVersion = modelWithVersion.getFormContext(request, state)
|
|
889
|
+
|
|
890
|
+
const formStatus = {
|
|
891
|
+
isPreview: false,
|
|
892
|
+
state: FormStatus.Live
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const body = format(
|
|
896
|
+
contextWithVersion,
|
|
897
|
+
items,
|
|
898
|
+
modelWithVersion,
|
|
899
|
+
submitResponse,
|
|
900
|
+
formStatus,
|
|
901
|
+
formMetadata as FormMetadata
|
|
902
|
+
)
|
|
903
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
904
|
+
|
|
905
|
+
// Should fall back to first version since submittedVersionNumber doesn't match
|
|
906
|
+
expect(parsedBody.meta.versionMetadata).toEqual({
|
|
907
|
+
versionNumber: 1,
|
|
908
|
+
createdAt: '2024-01-01T00:00:00.000Z'
|
|
909
|
+
})
|
|
910
|
+
})
|
|
911
|
+
|
|
912
|
+
it('should use first version as fallback when submittedVersionNumber does not match any version', () => {
|
|
913
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
914
|
+
id: 'form-123',
|
|
915
|
+
slug: 'test-form',
|
|
916
|
+
title: 'Test Form',
|
|
917
|
+
notificationEmail: 'test@example.com',
|
|
918
|
+
versions: [
|
|
919
|
+
{
|
|
920
|
+
versionNumber: 1,
|
|
921
|
+
createdAt: new Date('2024-01-01T00:00:00.000Z')
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
versionNumber: 2,
|
|
925
|
+
createdAt: new Date('2024-01-15T00:00:00.000Z')
|
|
926
|
+
}
|
|
927
|
+
]
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const modelWithVersion = new FormModel(definition, {
|
|
931
|
+
basePath: 'test',
|
|
932
|
+
versionNumber: 99 // Non-existent version
|
|
933
|
+
})
|
|
934
|
+
|
|
935
|
+
const contextWithVersion = modelWithVersion.getFormContext(request, state)
|
|
936
|
+
|
|
937
|
+
const formStatus = {
|
|
938
|
+
isPreview: false,
|
|
939
|
+
state: FormStatus.Live
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const body = format(
|
|
943
|
+
contextWithVersion,
|
|
944
|
+
items,
|
|
945
|
+
modelWithVersion,
|
|
946
|
+
submitResponse,
|
|
947
|
+
formStatus,
|
|
948
|
+
formMetadata as FormMetadata
|
|
949
|
+
)
|
|
950
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
951
|
+
|
|
952
|
+
// Should fall back to first version since submittedVersionNumber doesn't match
|
|
953
|
+
expect(parsedBody.meta.versionMetadata).toEqual({
|
|
954
|
+
versionNumber: 1,
|
|
955
|
+
createdAt: '2024-01-01T00:00:00.000Z'
|
|
956
|
+
})
|
|
957
|
+
})
|
|
958
|
+
|
|
959
|
+
it('should handle single version in versions array', () => {
|
|
960
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
961
|
+
id: 'form-123',
|
|
962
|
+
slug: 'test-form',
|
|
963
|
+
title: 'Test Form',
|
|
964
|
+
notificationEmail: 'test@example.com',
|
|
965
|
+
versions: [
|
|
966
|
+
{
|
|
967
|
+
versionNumber: 5,
|
|
968
|
+
createdAt: new Date('2024-02-01T00:00:00.000Z')
|
|
969
|
+
}
|
|
970
|
+
]
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const formStatus = {
|
|
974
|
+
isPreview: false,
|
|
975
|
+
state: FormStatus.Live
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const body = format(
|
|
979
|
+
context,
|
|
980
|
+
items,
|
|
981
|
+
model,
|
|
982
|
+
submitResponse,
|
|
983
|
+
formStatus,
|
|
984
|
+
formMetadata as FormMetadata
|
|
985
|
+
)
|
|
986
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
987
|
+
|
|
988
|
+
expect(parsedBody.meta.versionMetadata).toEqual({
|
|
989
|
+
versionNumber: 5,
|
|
990
|
+
createdAt: '2024-02-01T00:00:00.000Z'
|
|
991
|
+
})
|
|
992
|
+
})
|
|
993
|
+
})
|
|
994
|
+
|
|
995
|
+
describe('getVersionMetadata', () => {
|
|
996
|
+
const mockFormMetadata: Partial<FormMetadata> = {
|
|
997
|
+
id: 'form-123',
|
|
998
|
+
slug: 'test-form',
|
|
999
|
+
title: 'Test Form',
|
|
1000
|
+
notificationEmail: 'test@example.com',
|
|
1001
|
+
versions: [
|
|
1002
|
+
{
|
|
1003
|
+
versionNumber: 1,
|
|
1004
|
+
createdAt: new Date('2024-01-01T00:00:00.000Z')
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
versionNumber: 2,
|
|
1008
|
+
createdAt: new Date('2024-01-02T00:00:00.000Z')
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
versionNumber: 3,
|
|
1012
|
+
createdAt: new Date('2024-01-03T00:00:00.000Z')
|
|
1013
|
+
}
|
|
1014
|
+
]
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
it('should return undefined when no form metadata provided', () => {
|
|
1018
|
+
const result = getVersionMetadata(1, undefined)
|
|
1019
|
+
expect(result).toBeUndefined()
|
|
1020
|
+
})
|
|
1021
|
+
|
|
1022
|
+
it('should return undefined when form metadata has no versions', () => {
|
|
1023
|
+
const formMetadataWithoutVersions: Partial<FormMetadata> = {
|
|
1024
|
+
...mockFormMetadata,
|
|
1025
|
+
versions: undefined
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
const result = getVersionMetadata(
|
|
1029
|
+
1,
|
|
1030
|
+
formMetadataWithoutVersions as FormMetadata
|
|
1031
|
+
)
|
|
1032
|
+
expect(result).toBeUndefined()
|
|
1033
|
+
})
|
|
1034
|
+
|
|
1035
|
+
it('should return undefined when versions array is empty', () => {
|
|
1036
|
+
const formMetadataWithEmptyVersions: Partial<FormMetadata> = {
|
|
1037
|
+
...mockFormMetadata,
|
|
1038
|
+
versions: []
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const result = getVersionMetadata(
|
|
1042
|
+
1,
|
|
1043
|
+
formMetadataWithEmptyVersions as FormMetadata
|
|
1044
|
+
)
|
|
1045
|
+
expect(result).toBeUndefined()
|
|
1046
|
+
})
|
|
1047
|
+
|
|
1048
|
+
it('should return specific version when submittedVersionNumber matches', () => {
|
|
1049
|
+
const result = getVersionMetadata(2, mockFormMetadata as FormMetadata)
|
|
1050
|
+
expect(result).toEqual({
|
|
1051
|
+
versionNumber: 2,
|
|
1052
|
+
createdAt: new Date('2024-01-02T00:00:00.000Z')
|
|
1053
|
+
})
|
|
1054
|
+
})
|
|
1055
|
+
|
|
1056
|
+
it('should return first version when submittedVersionNumber not found', () => {
|
|
1057
|
+
const result = getVersionMetadata(999, mockFormMetadata as FormMetadata)
|
|
1058
|
+
expect(result).toEqual({
|
|
1059
|
+
versionNumber: 1,
|
|
1060
|
+
createdAt: new Date('2024-01-01T00:00:00.000Z')
|
|
1061
|
+
})
|
|
1062
|
+
})
|
|
1063
|
+
|
|
1064
|
+
it('should return first version when no submittedVersionNumber provided', () => {
|
|
1065
|
+
const result = getVersionMetadata(
|
|
1066
|
+
undefined,
|
|
1067
|
+
mockFormMetadata as FormMetadata
|
|
1068
|
+
)
|
|
1069
|
+
expect(result).toEqual({
|
|
1070
|
+
versionNumber: 1,
|
|
1071
|
+
createdAt: new Date('2024-01-01T00:00:00.000Z')
|
|
1072
|
+
})
|
|
1073
|
+
})
|
|
1074
|
+
|
|
1075
|
+
it('should handle single version in versions array', () => {
|
|
1076
|
+
const singleVersionMetadata: Partial<FormMetadata> = {
|
|
1077
|
+
...mockFormMetadata,
|
|
1078
|
+
versions: [
|
|
1079
|
+
{
|
|
1080
|
+
versionNumber: 5,
|
|
1081
|
+
createdAt: new Date('2024-02-01T00:00:00.000Z')
|
|
1082
|
+
}
|
|
1083
|
+
]
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
const result = getVersionMetadata(
|
|
1087
|
+
undefined,
|
|
1088
|
+
singleVersionMetadata as FormMetadata
|
|
1089
|
+
)
|
|
1090
|
+
expect(result).toEqual({
|
|
1091
|
+
versionNumber: 5,
|
|
1092
|
+
createdAt: new Date('2024-02-01T00:00:00.000Z')
|
|
1093
|
+
})
|
|
1094
|
+
})
|
|
1095
|
+
|
|
1096
|
+
it('should return correct version when submittedVersionNumber is 0', () => {
|
|
1097
|
+
const metadataWithVersionZero: Partial<FormMetadata> = {
|
|
1098
|
+
...mockFormMetadata,
|
|
1099
|
+
versions: [
|
|
1100
|
+
{
|
|
1101
|
+
versionNumber: 0,
|
|
1102
|
+
createdAt: new Date('2024-01-01T00:00:00.000Z')
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
versionNumber: 1,
|
|
1106
|
+
createdAt: new Date('2024-01-02T00:00:00.000Z')
|
|
1107
|
+
}
|
|
1108
|
+
]
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const result = getVersionMetadata(
|
|
1112
|
+
0,
|
|
1113
|
+
metadataWithVersionZero as FormMetadata
|
|
1114
|
+
)
|
|
1115
|
+
expect(result).toEqual({
|
|
1116
|
+
versionNumber: 0,
|
|
1117
|
+
createdAt: new Date('2024-01-01T00:00:00.000Z')
|
|
1118
|
+
})
|
|
1119
|
+
})
|
|
1120
|
+
|
|
1121
|
+
it('should handle negative submittedVersionNumber by falling back to first version', () => {
|
|
1122
|
+
const result = getVersionMetadata(-1, mockFormMetadata as FormMetadata)
|
|
1123
|
+
expect(result).toEqual({
|
|
1124
|
+
versionNumber: 1,
|
|
1125
|
+
createdAt: new Date('2024-01-01T00:00:00.000Z')
|
|
1126
|
+
})
|
|
1127
|
+
})
|
|
1128
|
+
})
|
|
696
1129
|
})
|
|
@@ -40,6 +40,11 @@ export function format(
|
|
|
40
40
|
|
|
41
41
|
const transformedData = v2DataParsed.data
|
|
42
42
|
|
|
43
|
+
const versionMetadata = getVersionMetadata(
|
|
44
|
+
context.submittedVersionNumber,
|
|
45
|
+
formMetadata
|
|
46
|
+
)
|
|
47
|
+
|
|
43
48
|
const meta: FormAdapterSubmissionMessageMeta = {
|
|
44
49
|
schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
|
|
45
50
|
timestamp: new Date(),
|
|
@@ -51,6 +56,10 @@ export function format(
|
|
|
51
56
|
isPreview: formStatus.isPreview,
|
|
52
57
|
notificationEmail: formMetadata?.notificationEmail ?? ''
|
|
53
58
|
}
|
|
59
|
+
|
|
60
|
+
if (versionMetadata) {
|
|
61
|
+
meta.versionMetadata = versionMetadata
|
|
62
|
+
}
|
|
54
63
|
const data: FormAdapterSubmissionMessageData = transformedData
|
|
55
64
|
|
|
56
65
|
const result: FormAdapterSubmissionMessageResult = {
|
|
@@ -66,6 +75,34 @@ export function format(
|
|
|
66
75
|
return JSON.stringify(payload)
|
|
67
76
|
}
|
|
68
77
|
|
|
78
|
+
export function getVersionMetadata(
|
|
79
|
+
submittedVersionNumber: number | undefined,
|
|
80
|
+
formMetadata?: FormMetadata
|
|
81
|
+
): { versionNumber: number; createdAt: Date } | undefined {
|
|
82
|
+
if (!formMetadata?.versions?.length) {
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (submittedVersionNumber !== undefined) {
|
|
87
|
+
const submittedVersion = formMetadata.versions.find(
|
|
88
|
+
(v) => v.versionNumber === submittedVersionNumber
|
|
89
|
+
)
|
|
90
|
+
if (submittedVersion) {
|
|
91
|
+
return {
|
|
92
|
+
versionNumber: submittedVersion.versionNumber,
|
|
93
|
+
createdAt: submittedVersion.createdAt
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// fallback to first available version
|
|
99
|
+
const firstVersion = formMetadata.versions[0]
|
|
100
|
+
return {
|
|
101
|
+
versionNumber: firstVersion.versionNumber,
|
|
102
|
+
createdAt: firstVersion.createdAt
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
69
106
|
function extractCsvFiles(
|
|
70
107
|
submitResponse: SubmitResponsePayload
|
|
71
108
|
): FormAdapterSubmissionMessageResult['files'] {
|
|
@@ -29,13 +29,15 @@ export function format(
|
|
|
29
29
|
|
|
30
30
|
const categorisedData = categoriseData(items)
|
|
31
31
|
|
|
32
|
+
const meta: Record<string, unknown> = {
|
|
33
|
+
schemaVersion: '2',
|
|
34
|
+
timestamp: now.toISOString(),
|
|
35
|
+
definition: model.def,
|
|
36
|
+
referenceNumber: context.referenceNumber
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
const data = {
|
|
33
|
-
meta
|
|
34
|
-
schemaVersion: '2',
|
|
35
|
-
timestamp: now.toISOString(),
|
|
36
|
-
definition: model.def,
|
|
37
|
-
referenceNumber: context.referenceNumber
|
|
38
|
-
},
|
|
40
|
+
meta,
|
|
39
41
|
data: categorisedData
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -34,6 +34,7 @@ export class RepeatPageController extends QuestionPageController {
|
|
|
34
34
|
listSummaryViewName = 'repeat-list-summary'
|
|
35
35
|
listDeleteViewName = 'item-delete'
|
|
36
36
|
repeat: Repeat
|
|
37
|
+
allowSaveAndExit = true
|
|
37
38
|
|
|
38
39
|
constructor(model: FormModel, pageDef: PageRepeat) {
|
|
39
40
|
super(model, pageDef)
|
|
@@ -257,6 +258,11 @@ export class RepeatPageController extends QuestionPageController {
|
|
|
257
258
|
return super.proceed(request, h, nextPath)
|
|
258
259
|
}
|
|
259
260
|
|
|
261
|
+
// Check if this is a save-and-exit action
|
|
262
|
+
if (action === FormAction.SaveAndExit) {
|
|
263
|
+
return this.handleSaveAndExit(request, context, h)
|
|
264
|
+
}
|
|
265
|
+
|
|
260
266
|
const nextPath = this.getNextPath(context)
|
|
261
267
|
return super.proceed(request, h, nextPath)
|
|
262
268
|
}
|
|
@@ -433,7 +439,8 @@ export class RepeatPageController extends QuestionPageController {
|
|
|
433
439
|
showTitle: true,
|
|
434
440
|
context,
|
|
435
441
|
errors,
|
|
436
|
-
checkAnswers: [{ summaryList }]
|
|
442
|
+
checkAnswers: [{ summaryList }],
|
|
443
|
+
allowSaveAndExit: this.shouldShowSaveAndExit(request.server)
|
|
437
444
|
}
|
|
438
445
|
}
|
|
439
446
|
|
|
@@ -151,10 +151,12 @@ export function makeLoadFormPreHandler(server: Server, options: PluginOptions) {
|
|
|
151
151
|
: `${prefix}/${slug}`
|
|
152
152
|
).substring(1)
|
|
153
153
|
|
|
154
|
+
const versionNumber = metadata.versions?.[0]?.versionNumber
|
|
155
|
+
|
|
154
156
|
// Construct the form model
|
|
155
157
|
const model = new FormModel(
|
|
156
158
|
definition,
|
|
157
|
-
{ basePath },
|
|
159
|
+
{ basePath, versionNumber },
|
|
158
160
|
services,
|
|
159
161
|
controllers
|
|
160
162
|
)
|