@defra/forms-engine-plugin 4.0.8 → 4.0.10
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/forms/page-events.yaml +1 -1
- package/.server/server/plugins/engine/components/EastingNorthingField.js +30 -10
- package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
- package/.server/server/plugins/engine/components/LatLongField.js +32 -4
- package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
- package/.server/server/plugins/engine/components/NumberField.d.ts +18 -0
- package/.server/server/plugins/engine/components/NumberField.js +103 -2
- package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js +4 -0
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/package.json +2 -2
- package/src/server/plugins/engine/components/EastingNorthingField.test.ts +15 -1
- package/src/server/plugins/engine/components/EastingNorthingField.ts +38 -10
- package/src/server/plugins/engine/components/LatLongField.test.ts +173 -16
- package/src/server/plugins/engine/components/LatLongField.ts +41 -4
- package/src/server/plugins/engine/components/NumberField.test.ts +504 -12
- package/src/server/plugins/engine/components/NumberField.ts +133 -4
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { ComponentType, type NumberFieldComponent } from '@defra/forms-model'
|
|
2
2
|
|
|
3
3
|
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
NumberField,
|
|
6
|
+
validateMinimumPrecision,
|
|
7
|
+
validateStringLength
|
|
8
|
+
} from '~/src/server/plugins/engine/components/NumberField.js'
|
|
5
9
|
import {
|
|
6
10
|
getAnswer,
|
|
7
11
|
type Field
|
|
@@ -19,6 +23,94 @@ describe('NumberField', () => {
|
|
|
19
23
|
})
|
|
20
24
|
})
|
|
21
25
|
|
|
26
|
+
describe('Helper Functions', () => {
|
|
27
|
+
describe('validateStringLength', () => {
|
|
28
|
+
it('returns valid when no constraints provided', () => {
|
|
29
|
+
expect(validateStringLength(123)).toEqual({ isValid: true })
|
|
30
|
+
expect(validateStringLength(123, undefined, undefined)).toEqual({
|
|
31
|
+
isValid: true
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('validates minimum length correctly', () => {
|
|
36
|
+
expect(validateStringLength(12, 3)).toEqual({
|
|
37
|
+
isValid: false,
|
|
38
|
+
error: 'minLength'
|
|
39
|
+
})
|
|
40
|
+
expect(validateStringLength(123, 3)).toEqual({ isValid: true })
|
|
41
|
+
expect(validateStringLength(1234, 3)).toEqual({ isValid: true })
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('validates maximum length correctly', () => {
|
|
45
|
+
expect(validateStringLength(123456, undefined, 5)).toEqual({
|
|
46
|
+
isValid: false,
|
|
47
|
+
error: 'maxLength'
|
|
48
|
+
})
|
|
49
|
+
expect(validateStringLength(12345, undefined, 5)).toEqual({
|
|
50
|
+
isValid: true
|
|
51
|
+
})
|
|
52
|
+
expect(validateStringLength(123, undefined, 5)).toEqual({
|
|
53
|
+
isValid: true
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('validates both min and max length', () => {
|
|
58
|
+
expect(validateStringLength(12, 3, 5)).toEqual({
|
|
59
|
+
isValid: false,
|
|
60
|
+
error: 'minLength'
|
|
61
|
+
})
|
|
62
|
+
expect(validateStringLength(123456, 3, 5)).toEqual({
|
|
63
|
+
isValid: false,
|
|
64
|
+
error: 'maxLength'
|
|
65
|
+
})
|
|
66
|
+
expect(validateStringLength(1234, 3, 5)).toEqual({ isValid: true })
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('handles decimal numbers correctly', () => {
|
|
70
|
+
// "52.1" = 4 characters
|
|
71
|
+
expect(validateStringLength(52.1, 3, 5)).toEqual({ isValid: true })
|
|
72
|
+
// "52.123456" = 9 characters
|
|
73
|
+
expect(validateStringLength(52.123456, undefined, 8)).toEqual({
|
|
74
|
+
isValid: false,
|
|
75
|
+
error: 'maxLength'
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('handles negative numbers correctly', () => {
|
|
80
|
+
// "-1.5" = 4 characters
|
|
81
|
+
expect(validateStringLength(-1.5, 3, 5)).toEqual({ isValid: true })
|
|
82
|
+
// "-9.1234567" = 10 characters
|
|
83
|
+
expect(validateStringLength(-9.1234567, undefined, 9)).toEqual({
|
|
84
|
+
isValid: false,
|
|
85
|
+
error: 'maxLength'
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('validateMinimumPrecision', () => {
|
|
91
|
+
it('returns false for integers', () => {
|
|
92
|
+
expect(validateMinimumPrecision(52, 1)).toBe(false)
|
|
93
|
+
expect(validateMinimumPrecision(100, 2)).toBe(false)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('validates minimum precision correctly', () => {
|
|
97
|
+
expect(validateMinimumPrecision(52.1, 1)).toBe(true)
|
|
98
|
+
expect(validateMinimumPrecision(52.12, 2)).toBe(true)
|
|
99
|
+
expect(validateMinimumPrecision(52.123, 3)).toBe(true)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('returns false when precision is insufficient', () => {
|
|
103
|
+
expect(validateMinimumPrecision(52.1, 2)).toBe(false)
|
|
104
|
+
expect(validateMinimumPrecision(52.12, 3)).toBe(false)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('handles exact precision requirement', () => {
|
|
108
|
+
expect(validateMinimumPrecision(52.12345, 5)).toBe(true)
|
|
109
|
+
expect(validateMinimumPrecision(52.1234, 5)).toBe(false)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
22
114
|
describe('Defaults', () => {
|
|
23
115
|
let def: NumberFieldComponent
|
|
24
116
|
let collection: ComponentCollection
|
|
@@ -504,17 +596,184 @@ describe('NumberField', () => {
|
|
|
504
596
|
]
|
|
505
597
|
},
|
|
506
598
|
{
|
|
507
|
-
description: 'Schema
|
|
599
|
+
description: 'Schema minPrecision (minimum 1 decimal place)',
|
|
600
|
+
component: createPrecisionTestComponent(1),
|
|
601
|
+
assertions: [
|
|
602
|
+
{
|
|
603
|
+
input: getFormData('52'),
|
|
604
|
+
output: {
|
|
605
|
+
value: getFormData(52),
|
|
606
|
+
errors: [
|
|
607
|
+
expect.objectContaining({
|
|
608
|
+
text: 'Example number field must have at least 1 decimal place'
|
|
609
|
+
})
|
|
610
|
+
]
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
input: getFormData('52.0'),
|
|
615
|
+
output: {
|
|
616
|
+
value: getFormData(52),
|
|
617
|
+
errors: [
|
|
618
|
+
expect.objectContaining({
|
|
619
|
+
text: 'Example number field must have at least 1 decimal place'
|
|
620
|
+
})
|
|
621
|
+
]
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
input: getFormData('52.1'),
|
|
626
|
+
output: { value: getFormData(52.1) }
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
input: getFormData('52.123456'),
|
|
630
|
+
output: { value: getFormData(52.123456) }
|
|
631
|
+
}
|
|
632
|
+
]
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
description: 'Schema minPrecision (minimum 2 decimal places)',
|
|
636
|
+
component: createPrecisionTestComponent(2),
|
|
637
|
+
assertions: [
|
|
638
|
+
{
|
|
639
|
+
input: getFormData('52.1'),
|
|
640
|
+
output: {
|
|
641
|
+
value: getFormData(52.1),
|
|
642
|
+
errors: [
|
|
643
|
+
expect.objectContaining({
|
|
644
|
+
text: 'Example number field must have at least 2 decimal places'
|
|
645
|
+
})
|
|
646
|
+
]
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
input: getFormData('52.12'),
|
|
651
|
+
output: { value: getFormData(52.12) }
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
input: getFormData('52.1234567'),
|
|
655
|
+
output: { value: getFormData(52.1234567) }
|
|
656
|
+
}
|
|
657
|
+
]
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
description: 'Schema minLength (minimum 3 characters)',
|
|
661
|
+
component: createLengthTestComponent(3, undefined),
|
|
662
|
+
assertions: [
|
|
663
|
+
{
|
|
664
|
+
input: getFormData('12'),
|
|
665
|
+
output: {
|
|
666
|
+
value: getFormData(12),
|
|
667
|
+
errors: [
|
|
668
|
+
expect.objectContaining({
|
|
669
|
+
text: 'Example number field must be at least 3 characters'
|
|
670
|
+
})
|
|
671
|
+
]
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
input: getFormData('123'),
|
|
676
|
+
output: { value: getFormData(123) }
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
input: getFormData('1234'),
|
|
680
|
+
output: { value: getFormData(1234) }
|
|
681
|
+
}
|
|
682
|
+
]
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
description: 'Schema maxLength (maximum 5 characters)',
|
|
686
|
+
component: createLengthTestComponent(undefined, 5),
|
|
687
|
+
assertions: [
|
|
688
|
+
{
|
|
689
|
+
input: getFormData('123456'),
|
|
690
|
+
output: {
|
|
691
|
+
value: getFormData(123456),
|
|
692
|
+
errors: [
|
|
693
|
+
expect.objectContaining({
|
|
694
|
+
text: 'Example number field must be no more than 5 characters'
|
|
695
|
+
})
|
|
696
|
+
]
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
input: getFormData('12345'),
|
|
701
|
+
output: { value: getFormData(12345) }
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
input: getFormData('123'),
|
|
705
|
+
output: { value: getFormData(123) }
|
|
706
|
+
}
|
|
707
|
+
]
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
description:
|
|
711
|
+
'Schema minLength and maxLength (3-8 characters, like latitude)',
|
|
508
712
|
component: {
|
|
509
|
-
title: '
|
|
713
|
+
title: 'Latitude field',
|
|
714
|
+
shortDescription: 'Latitude',
|
|
510
715
|
name: 'myComponent',
|
|
511
716
|
type: ComponentType.NumberField,
|
|
512
|
-
options: {
|
|
717
|
+
options: {
|
|
718
|
+
customValidationMessages: {
|
|
719
|
+
'number.minPrecision':
|
|
720
|
+
'{{#label}} must have at least {{#minPrecision}} decimal place',
|
|
721
|
+
'number.minLength':
|
|
722
|
+
'{{#label}} must be between 3 and 10 characters',
|
|
723
|
+
'number.maxLength':
|
|
724
|
+
'{{#label}} must be between 3 and 10 characters'
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
schema: {
|
|
728
|
+
min: 49,
|
|
729
|
+
max: 60,
|
|
730
|
+
precision: 7,
|
|
731
|
+
minPrecision: 1,
|
|
732
|
+
minLength: 3,
|
|
733
|
+
maxLength: 10
|
|
734
|
+
}
|
|
735
|
+
} as NumberFieldComponent,
|
|
736
|
+
assertions: [
|
|
737
|
+
{
|
|
738
|
+
input: getFormData('52'),
|
|
739
|
+
output: {
|
|
740
|
+
value: getFormData(52),
|
|
741
|
+
errors: [
|
|
742
|
+
expect.objectContaining({
|
|
743
|
+
text: 'Latitude must have at least 1 decimal place'
|
|
744
|
+
})
|
|
745
|
+
]
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
input: getFormData('52.12345678'),
|
|
750
|
+
output: {
|
|
751
|
+
value: getFormData(52.12345678),
|
|
752
|
+
errors: [
|
|
753
|
+
expect.objectContaining({
|
|
754
|
+
text: 'Latitude must have 7 or fewer decimal places'
|
|
755
|
+
})
|
|
756
|
+
]
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
input: getFormData('52.1'),
|
|
761
|
+
output: { value: getFormData(52.1) }
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
input: getFormData('52.1234'),
|
|
765
|
+
output: { value: getFormData(52.1234) }
|
|
766
|
+
}
|
|
767
|
+
]
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
description: 'Schema min and max',
|
|
771
|
+
component: createNumberComponent({
|
|
513
772
|
schema: {
|
|
514
773
|
min: 5,
|
|
515
774
|
max: 8
|
|
516
775
|
}
|
|
517
|
-
}
|
|
776
|
+
}),
|
|
518
777
|
assertions: [
|
|
519
778
|
{
|
|
520
779
|
input: getFormData('4'),
|
|
@@ -542,10 +801,7 @@ describe('NumberField', () => {
|
|
|
542
801
|
},
|
|
543
802
|
{
|
|
544
803
|
description: 'Custom validation message',
|
|
545
|
-
component: {
|
|
546
|
-
title: 'Example number field',
|
|
547
|
-
name: 'myComponent',
|
|
548
|
-
type: ComponentType.NumberField,
|
|
804
|
+
component: createNumberComponent({
|
|
549
805
|
options: {
|
|
550
806
|
customValidationMessage: 'This is a custom error',
|
|
551
807
|
customValidationMessages: {
|
|
@@ -554,9 +810,8 @@ describe('NumberField', () => {
|
|
|
554
810
|
'number.min': 'This is not used',
|
|
555
811
|
'number.max': 'This is not used'
|
|
556
812
|
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
} satisfies NumberFieldComponent,
|
|
813
|
+
}
|
|
814
|
+
}),
|
|
560
815
|
assertions: [
|
|
561
816
|
{
|
|
562
817
|
input: getFormData(''),
|
|
@@ -686,6 +941,45 @@ describe('NumberField', () => {
|
|
|
686
941
|
}
|
|
687
942
|
]
|
|
688
943
|
},
|
|
944
|
+
{
|
|
945
|
+
description: 'Custom validation message overrides length validation',
|
|
946
|
+
component: {
|
|
947
|
+
title: 'Example number field',
|
|
948
|
+
name: 'myComponent',
|
|
949
|
+
type: ComponentType.NumberField,
|
|
950
|
+
options: {
|
|
951
|
+
customValidationMessage: 'This is a custom length error'
|
|
952
|
+
},
|
|
953
|
+
schema: {
|
|
954
|
+
minLength: 3,
|
|
955
|
+
maxLength: 5
|
|
956
|
+
}
|
|
957
|
+
} satisfies NumberFieldComponent,
|
|
958
|
+
assertions: [
|
|
959
|
+
{
|
|
960
|
+
input: getFormData('12'),
|
|
961
|
+
output: {
|
|
962
|
+
value: getFormData(12),
|
|
963
|
+
errors: [
|
|
964
|
+
expect.objectContaining({
|
|
965
|
+
text: 'This is a custom length error'
|
|
966
|
+
})
|
|
967
|
+
]
|
|
968
|
+
}
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
input: getFormData('123456'),
|
|
972
|
+
output: {
|
|
973
|
+
value: getFormData(123456),
|
|
974
|
+
errors: [
|
|
975
|
+
expect.objectContaining({
|
|
976
|
+
text: 'This is a custom length error'
|
|
977
|
+
})
|
|
978
|
+
]
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
]
|
|
982
|
+
},
|
|
689
983
|
{
|
|
690
984
|
description: 'Optional field',
|
|
691
985
|
component: {
|
|
@@ -720,4 +1014,202 @@ describe('NumberField', () => {
|
|
|
720
1014
|
)
|
|
721
1015
|
})
|
|
722
1016
|
})
|
|
1017
|
+
|
|
1018
|
+
describe('Edge cases', () => {
|
|
1019
|
+
let collection: ComponentCollection
|
|
1020
|
+
|
|
1021
|
+
beforeEach(() => {
|
|
1022
|
+
const def = createNumberComponent({
|
|
1023
|
+
schema: {
|
|
1024
|
+
min: -100,
|
|
1025
|
+
max: 100,
|
|
1026
|
+
precision: 2
|
|
1027
|
+
}
|
|
1028
|
+
})
|
|
1029
|
+
collection = new ComponentCollection([def], { model })
|
|
1030
|
+
})
|
|
1031
|
+
|
|
1032
|
+
it('handles negative numbers correctly', () => {
|
|
1033
|
+
const result = collection.validate(getFormData('-50.5'))
|
|
1034
|
+
expect(result).toEqual({
|
|
1035
|
+
value: getFormData(-50.5)
|
|
1036
|
+
})
|
|
1037
|
+
})
|
|
1038
|
+
|
|
1039
|
+
it('handles zero correctly', () => {
|
|
1040
|
+
const result = collection.validate(getFormData('0'))
|
|
1041
|
+
expect(result).toEqual({
|
|
1042
|
+
value: getFormData(0)
|
|
1043
|
+
})
|
|
1044
|
+
})
|
|
1045
|
+
|
|
1046
|
+
it('handles zero with decimal correctly', () => {
|
|
1047
|
+
const result = collection.validate(getFormData('0.0'))
|
|
1048
|
+
expect(result).toEqual({
|
|
1049
|
+
value: getFormData(0)
|
|
1050
|
+
})
|
|
1051
|
+
})
|
|
1052
|
+
|
|
1053
|
+
it('handles negative zero correctly', () => {
|
|
1054
|
+
const result = collection.validate(getFormData('-0'))
|
|
1055
|
+
expect(result).toEqual({
|
|
1056
|
+
value: getFormData(0)
|
|
1057
|
+
})
|
|
1058
|
+
})
|
|
1059
|
+
|
|
1060
|
+
it('handles scientific notation (parsed as number, may fail range)', () => {
|
|
1061
|
+
// JavaScript parses '1e10' as 10000000000, which exceeds max of 100
|
|
1062
|
+
const result = collection.validate(getFormData('1e10'))
|
|
1063
|
+
expect(result).toEqual({
|
|
1064
|
+
value: getFormData(10000000000),
|
|
1065
|
+
errors: [
|
|
1066
|
+
expect.objectContaining({
|
|
1067
|
+
text: 'Example number field must be 100 or lower'
|
|
1068
|
+
})
|
|
1069
|
+
]
|
|
1070
|
+
})
|
|
1071
|
+
})
|
|
1072
|
+
|
|
1073
|
+
it('handles scientific notation with negative exponent (parsed as number)', () => {
|
|
1074
|
+
// JavaScript parses '1e-5' as 0.00001, which fails precision check (5 decimal places > 2)
|
|
1075
|
+
const result = collection.validate(getFormData('1e-5'))
|
|
1076
|
+
expect(result.value).toEqual(getFormData(0.00001))
|
|
1077
|
+
expect(result.errors).toBeDefined()
|
|
1078
|
+
expect(result.errors?.[0]).toMatchObject({
|
|
1079
|
+
text: 'Example number field must have 2 or fewer decimal places'
|
|
1080
|
+
})
|
|
1081
|
+
})
|
|
1082
|
+
|
|
1083
|
+
it('handles large negative numbers', () => {
|
|
1084
|
+
const result = collection.validate(getFormData('-99.99'))
|
|
1085
|
+
expect(result).toEqual({
|
|
1086
|
+
value: getFormData(-99.99)
|
|
1087
|
+
})
|
|
1088
|
+
})
|
|
1089
|
+
|
|
1090
|
+
it('handles numbers at boundary limits', () => {
|
|
1091
|
+
const maxResult = collection.validate(getFormData('100'))
|
|
1092
|
+
expect(maxResult).toEqual({
|
|
1093
|
+
value: getFormData(100)
|
|
1094
|
+
})
|
|
1095
|
+
|
|
1096
|
+
const minResult = collection.validate(getFormData('-100'))
|
|
1097
|
+
expect(minResult).toEqual({
|
|
1098
|
+
value: getFormData(-100)
|
|
1099
|
+
})
|
|
1100
|
+
})
|
|
1101
|
+
|
|
1102
|
+
describe('with length constraints', () => {
|
|
1103
|
+
beforeEach(() => {
|
|
1104
|
+
const def = createNumberComponent({
|
|
1105
|
+
schema: {
|
|
1106
|
+
min: -9,
|
|
1107
|
+
max: 9,
|
|
1108
|
+
precision: 7,
|
|
1109
|
+
minPrecision: 1,
|
|
1110
|
+
minLength: 2,
|
|
1111
|
+
maxLength: 10
|
|
1112
|
+
},
|
|
1113
|
+
options: {
|
|
1114
|
+
customValidationMessages: {
|
|
1115
|
+
'number.minPrecision':
|
|
1116
|
+
'Example number field must have at least {{minPrecision}} decimal place',
|
|
1117
|
+
'number.precision':
|
|
1118
|
+
'Example number field must have no more than {{limit}} decimal places',
|
|
1119
|
+
'number.minLength':
|
|
1120
|
+
'Example number field must be at least {{minLength}} characters',
|
|
1121
|
+
'number.maxLength':
|
|
1122
|
+
'Example number field must be no more than {{maxLength}} characters'
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
})
|
|
1126
|
+
collection = new ComponentCollection([def], { model })
|
|
1127
|
+
})
|
|
1128
|
+
|
|
1129
|
+
it('validates negative numbers with decimals', () => {
|
|
1130
|
+
const result = collection.validate(getFormData('-5.1234567'))
|
|
1131
|
+
expect(result).toEqual({
|
|
1132
|
+
value: getFormData(-5.1234567)
|
|
1133
|
+
})
|
|
1134
|
+
})
|
|
1135
|
+
|
|
1136
|
+
it('rejects negative numbers that are too short', () => {
|
|
1137
|
+
const result = collection.validate(getFormData('-5'))
|
|
1138
|
+
expect(result.value).toEqual(getFormData(-5))
|
|
1139
|
+
expect(result.errors).toBeDefined()
|
|
1140
|
+
expect(result.errors?.[0].text).toContain('decimal place')
|
|
1141
|
+
})
|
|
1142
|
+
|
|
1143
|
+
it('rejects numbers with too many characters', () => {
|
|
1144
|
+
const result = collection.validate(getFormData('-5.12345678'))
|
|
1145
|
+
expect(result.value).toEqual(getFormData(-5.12345678))
|
|
1146
|
+
expect(result.errors).toBeDefined()
|
|
1147
|
+
expect(result.errors?.[0].text).toContain('decimal places')
|
|
1148
|
+
})
|
|
1149
|
+
})
|
|
1150
|
+
})
|
|
723
1151
|
})
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* Factory function to create a default NumberField component with optional overrides
|
|
1155
|
+
*/
|
|
1156
|
+
function createNumberComponent(
|
|
1157
|
+
overrides: Partial<NumberFieldComponent> = {}
|
|
1158
|
+
): NumberFieldComponent {
|
|
1159
|
+
const base = {
|
|
1160
|
+
title: 'Example number field',
|
|
1161
|
+
name: 'myComponent',
|
|
1162
|
+
type: ComponentType.NumberField,
|
|
1163
|
+
options: {},
|
|
1164
|
+
schema: {}
|
|
1165
|
+
} satisfies NumberFieldComponent
|
|
1166
|
+
|
|
1167
|
+
// Deep merge for nested objects like options and schema
|
|
1168
|
+
return {
|
|
1169
|
+
...base,
|
|
1170
|
+
...overrides,
|
|
1171
|
+
options: { ...base.options, ...(overrides.options ?? {}) },
|
|
1172
|
+
schema: { ...base.schema, ...(overrides.schema ?? {}) }
|
|
1173
|
+
} satisfies NumberFieldComponent
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Helper for precision validation tests
|
|
1178
|
+
*/
|
|
1179
|
+
function createPrecisionTestComponent(
|
|
1180
|
+
minPrecision: number,
|
|
1181
|
+
precision = 7
|
|
1182
|
+
): NumberFieldComponent {
|
|
1183
|
+
const pluralSuffix = minPrecision > 1 ? 's' : ''
|
|
1184
|
+
return createNumberComponent({
|
|
1185
|
+
options: {
|
|
1186
|
+
customValidationMessages: {
|
|
1187
|
+
'number.minPrecision': `{{#label}} must have at least {{#minPrecision}} decimal place${pluralSuffix}`
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
schema: { precision, minPrecision }
|
|
1191
|
+
})
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* Helper for length validation tests
|
|
1196
|
+
*/
|
|
1197
|
+
function createLengthTestComponent(
|
|
1198
|
+
minLength?: number,
|
|
1199
|
+
maxLength?: number
|
|
1200
|
+
): NumberFieldComponent {
|
|
1201
|
+
const messages: Record<string, string> = {}
|
|
1202
|
+
if (minLength) {
|
|
1203
|
+
messages['number.minLength'] =
|
|
1204
|
+
'{{#label}} must be at least {{#minLength}} characters'
|
|
1205
|
+
}
|
|
1206
|
+
if (maxLength) {
|
|
1207
|
+
messages['number.maxLength'] =
|
|
1208
|
+
'{{#label}} must be no more than {{#maxLength}} characters'
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
return createNumberComponent({
|
|
1212
|
+
options: { customValidationMessages: messages },
|
|
1213
|
+
schema: { minLength, maxLength }
|
|
1214
|
+
})
|
|
1215
|
+
}
|