@pdtf/schemas 3.6.0-36 → 3.6.0-37

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 (38) hide show
  1. package/README.md +52 -33
  2. package/docs/sef25-extensions-ui-spec.md +258 -49
  3. package/index.js +9 -0
  4. package/package.json +1 -1
  5. package/src/schemas/v3/combined.json +199 -12
  6. package/src/schemas/v3/compactSkeleton.txt +14 -0
  7. package/src/schemas/v3/overlays/extensions/as.json +4 -2
  8. package/src/schemas/v3/overlays/extensions/dk.json +32 -0
  9. package/src/schemas/v3/overlays/extensions/dr.json +4 -2
  10. package/src/schemas/v3/overlays/extensions/er.json +4 -2
  11. package/src/schemas/v3/overlays/extensions/fd.json +6 -3
  12. package/src/schemas/v3/overlays/extensions/hi.json +6 -3
  13. package/src/schemas/v3/overlays/extensions/hs.json +4 -2
  14. package/src/schemas/v3/overlays/extensions/ic.json +59 -0
  15. package/src/schemas/v3/overlays/extensions/jk.json +4 -2
  16. package/src/schemas/v3/overlays/extensions/la.json +4 -2
  17. package/src/schemas/v3/overlays/extensions/lc.json +52 -0
  18. package/src/schemas/v3/overlays/extensions/ma.json +6 -3
  19. package/src/schemas/v3/overlays/extensions/mc.json +4 -2
  20. package/src/schemas/v3/overlays/extensions/mi.json +55 -0
  21. package/src/schemas/v3/overlays/extensions/nd.json +43 -0
  22. package/src/schemas/v3/overlays/extensions/oa.json +4 -2
  23. package/src/schemas/v3/overlays/extensions/oc.json +4 -2
  24. package/src/schemas/v3/overlays/extensions/pc.json +2 -1
  25. package/src/schemas/v3/overlays/extensions/ph.json +11 -14
  26. package/src/schemas/v3/overlays/extensions/rw.json +68 -0
  27. package/src/schemas/v3/overlays/extensions/sb.json +4 -2
  28. package/src/schemas/v3/overlays/extensions/sd.json +63 -0
  29. package/src/schemas/v3/overlays/extensions/sf.json +4 -2
  30. package/src/schemas/v3/overlays/extensions/sl.json +4 -2
  31. package/src/schemas/v3/overlays/extensions/tf.json +6 -3
  32. package/src/schemas/v3/overlays/extensions/tr.json +73 -0
  33. package/src/schemas/v3/overlays/extensions/wg.json +250 -0
  34. package/src/schemas/v3/overlays/sef25.json +639 -0
  35. package/src/schemas/v3/pdtf-transaction.json +150 -0
  36. package/src/schemas/v3/skeleton.json +20 -1
  37. package/src/tests/v3/extensionOverlays.test.js +397 -0
  38. package/src/utils/extractExtensionOverlays.js +116 -4
@@ -1677,6 +1677,45 @@
1677
1677
  }
1678
1678
  ]
1679
1679
  },
1680
+ "titleRestrictions": {
1681
+ "title": "Are there any known restrictions on the title?",
1682
+ "type": "object",
1683
+ "properties": {
1684
+ "yesNo": {
1685
+ "type": "string",
1686
+ "title": "",
1687
+ "enum": [
1688
+ "Yes",
1689
+ "No"
1690
+ ]
1691
+ }
1692
+ },
1693
+ "oneOf": [
1694
+ {
1695
+ "properties": {
1696
+ "yesNo": {
1697
+ "enum": [
1698
+ "No"
1699
+ ]
1700
+ }
1701
+ }
1702
+ },
1703
+ {
1704
+ "properties": {
1705
+ "yesNo": {
1706
+ "enum": [
1707
+ "Yes"
1708
+ ]
1709
+ },
1710
+ "details": {
1711
+ "title": "Details",
1712
+ "type": "string",
1713
+ "minLength": 1
1714
+ }
1715
+ }
1716
+ }
1717
+ ]
1718
+ },
1680
1719
  "wholeFreeholdForSale": {
1681
1720
  "title": "Is the entire freehold being sold?",
1682
1721
  "type": "object",
@@ -8430,6 +8469,20 @@
8430
8469
  }
8431
8470
  ]
8432
8471
  },
8472
+ "droppedKerbAccess": {
8473
+ "title": "Is there a dropped kerb access to parking?",
8474
+ "type": "object",
8475
+ "properties": {
8476
+ "yesNo": {
8477
+ "type": "string",
8478
+ "title": "",
8479
+ "enum": [
8480
+ "Yes",
8481
+ "No"
8482
+ ]
8483
+ }
8484
+ }
8485
+ },
8433
8486
  "electricVehicleChargingPoint": {
8434
8487
  "title": "Is there an electric vehicle charging point belonging to the property?",
8435
8488
  "type": "object",
@@ -18009,6 +18062,25 @@
18009
18062
  "Attached",
18010
18063
  "To follow"
18011
18064
  ]
18065
+ },
18066
+ "associatedCost": {
18067
+ "title": "Associated costs of leased solar panels",
18068
+ "type": "object",
18069
+ "properties": {
18070
+ "amount": {
18071
+ "title": "Associated costs of leased solar panels if any (£)",
18072
+ "type": "number"
18073
+ },
18074
+ "frequency": {
18075
+ "title": "Payment frequency if costs are associated",
18076
+ "type": "string",
18077
+ "enum": [
18078
+ "Per month",
18079
+ "Per year",
18080
+ "Not applicable"
18081
+ ]
18082
+ }
18083
+ }
18012
18084
  }
18013
18085
  }
18014
18086
  }
@@ -20952,6 +21024,45 @@
20952
21024
  }
20953
21025
  ]
20954
21026
  },
21027
+ "privateRightOfWay": {
21028
+ "title": "A private right of way through and/or across your house, buildings or land",
21029
+ "type": "object",
21030
+ "properties": {
21031
+ "yesNo": {
21032
+ "type": "string",
21033
+ "title": "",
21034
+ "enum": [
21035
+ "Yes",
21036
+ "No"
21037
+ ]
21038
+ }
21039
+ },
21040
+ "oneOf": [
21041
+ {
21042
+ "properties": {
21043
+ "yesNo": {
21044
+ "enum": [
21045
+ "No"
21046
+ ]
21047
+ }
21048
+ }
21049
+ },
21050
+ {
21051
+ "properties": {
21052
+ "yesNo": {
21053
+ "enum": [
21054
+ "Yes"
21055
+ ]
21056
+ },
21057
+ "details": {
21058
+ "title": "Please provide details",
21059
+ "type": "string",
21060
+ "minLength": 1
21061
+ }
21062
+ }
21063
+ }
21064
+ ]
21065
+ },
20955
21066
  "rightsOfLight": {
20956
21067
  "title": "Rights of light",
20957
21068
  "type": "object",
@@ -21600,6 +21711,45 @@
21600
21711
  }
21601
21712
  }
21602
21713
  },
21714
+ "stormFireFloodDamage": {
21715
+ "title": "Has the property ever suffered storm, fire or flood damage?",
21716
+ "type": "object",
21717
+ "properties": {
21718
+ "yesNo": {
21719
+ "type": "string",
21720
+ "title": "",
21721
+ "enum": [
21722
+ "Yes",
21723
+ "No"
21724
+ ]
21725
+ }
21726
+ },
21727
+ "oneOf": [
21728
+ {
21729
+ "properties": {
21730
+ "yesNo": {
21731
+ "enum": [
21732
+ "No"
21733
+ ]
21734
+ }
21735
+ }
21736
+ },
21737
+ {
21738
+ "properties": {
21739
+ "yesNo": {
21740
+ "enum": [
21741
+ "Yes"
21742
+ ]
21743
+ },
21744
+ "details": {
21745
+ "title": "Please provide details",
21746
+ "type": "string",
21747
+ "minLength": 1
21748
+ }
21749
+ }
21750
+ }
21751
+ ]
21752
+ },
21603
21753
  "radon": {
21604
21754
  "title": "Radon",
21605
21755
  "type": "object",
@@ -289,6 +289,10 @@
289
289
  "details": "string",
290
290
  "amount": "number"
291
291
  },
292
+ "titleRestrictions": {
293
+ "yesNo": "variant",
294
+ "details": "string"
295
+ },
292
296
  "wholeFreeholdForSale": {
293
297
  "yesNo": "variant",
294
298
  "details": "string"
@@ -1229,6 +1233,9 @@
1229
1233
  "yesNo": "variant",
1230
1234
  "details": "string"
1231
1235
  },
1236
+ "droppedKerbAccess": {
1237
+ "yesNo": "string"
1238
+ },
1232
1239
  "electricVehicleChargingPoint": {
1233
1240
  "yesNo": "variant",
1234
1241
  "chargerDetails": "string",
@@ -2386,7 +2393,11 @@
2386
2393
  "panelsOwnedOutright": {
2387
2394
  "yesNo": "variant",
2388
2395
  "details": "string",
2389
- "attachments": "string"
2396
+ "attachments": "string",
2397
+ "associatedCost": {
2398
+ "amount": "number",
2399
+ "frequency": "string"
2400
+ }
2390
2401
  },
2391
2402
  "panelProviderLease": {
2392
2403
  "yesNo": "variant",
@@ -2736,6 +2747,10 @@
2736
2747
  "details": "string",
2737
2748
  "attachments": "string"
2738
2749
  },
2750
+ "privateRightOfWay": {
2751
+ "yesNo": "variant",
2752
+ "details": "string"
2753
+ },
2739
2754
  "rightsOfLight": {
2740
2755
  "yesNo": "variant",
2741
2756
  "details": "string"
@@ -2815,6 +2830,10 @@
2815
2830
  "details": "string"
2816
2831
  }
2817
2832
  },
2833
+ "stormFireFloodDamage": {
2834
+ "yesNo": "variant",
2835
+ "details": "string"
2836
+ },
2818
2837
  "radon": {
2819
2838
  "radonRisk": {
2820
2839
  "riskIndicator": "string",
@@ -165,6 +165,15 @@ describe("Extension Overlays", () => {
165
165
  "sc",
166
166
  "pc",
167
167
  "ph", // SEF25
168
+ "dk",
169
+ "rw",
170
+ "sd",
171
+ "lc",
172
+ "wg",
173
+ "ic",
174
+ "nd",
175
+ "mi",
176
+ "tr", // SEF25 (new)
168
177
  ];
169
178
 
170
179
  expect(Object.keys(extensionOverlays).sort()).toEqual(
@@ -642,6 +651,25 @@ describe("Extension Overlays", () => {
642
651
  expect(result).toBe(true);
643
652
  });
644
653
 
654
+ test("should validate private water with no associated costs", () => {
655
+ const validator = getSubschemaValidator(
656
+ "/propertyPack/waterAndDrainage/water/mainsWater",
657
+ schemaId,
658
+ overlays
659
+ );
660
+
661
+ const result = validator({
662
+ yesNo: "No",
663
+ details: "Private borehole",
664
+ associatedCost: {
665
+ amount: 0,
666
+ frequency: "Not applicable",
667
+ },
668
+ });
669
+
670
+ expect(result).toBe(true);
671
+ });
672
+
645
673
  test("should require associated cost for private water supply", () => {
646
674
  const validator = getSubschemaValidator(
647
675
  "/propertyPack/waterAndDrainage/water/mainsWater",
@@ -957,5 +985,374 @@ describe("Extension Overlays", () => {
957
985
  );
958
986
  });
959
987
  });
988
+
989
+ describe("Dropped kerb (dk)", () => {
990
+ test("should validate Yes answer", () => {
991
+ const validator = getSubschemaValidator(
992
+ "/propertyPack/parking/droppedKerbAccess",
993
+ schemaId,
994
+ ["dk"]
995
+ );
996
+ expect(validator({ yesNo: "Yes" })).toBe(true);
997
+ });
998
+
999
+ test("should validate No answer", () => {
1000
+ const validator = getSubschemaValidator(
1001
+ "/propertyPack/parking/droppedKerbAccess",
1002
+ schemaId,
1003
+ ["dk"]
1004
+ );
1005
+ expect(validator({ yesNo: "No" })).toBe(true);
1006
+ });
1007
+
1008
+ test("should reject invalid value", () => {
1009
+ const validator = getSubschemaValidator(
1010
+ "/propertyPack/parking/droppedKerbAccess",
1011
+ schemaId,
1012
+ ["dk"]
1013
+ );
1014
+ expect(validator({ yesNo: "Maybe" })).toBe(false);
1015
+ });
1016
+
1017
+ test("should not be required without dk overlay", () => {
1018
+ const schema = getTransactionSchema(schemaId, ["baspiV5"]);
1019
+ const parking = schema.properties?.propertyPack?.properties?.parking;
1020
+ expect(parking?.required || []).not.toContain("droppedKerbAccess");
1021
+ });
1022
+
1023
+ test("should be required with dk overlay", () => {
1024
+ const schema = getTransactionSchema(schemaId, ["dk"]);
1025
+ const parking = schema.properties?.propertyPack?.properties?.parking;
1026
+ expect(parking?.required).toContain("droppedKerbAccess");
1027
+ });
1028
+ });
1029
+
1030
+ describe("Private right of way (rw)", () => {
1031
+ test("should validate No answer", () => {
1032
+ const validator = getSubschemaValidator(
1033
+ "/propertyPack/rightsAndInformalArrangements/rightsOrArrangements/privateRightOfWay",
1034
+ schemaId,
1035
+ ["rw"]
1036
+ );
1037
+ expect(validator({ yesNo: "No" })).toBe(true);
1038
+ });
1039
+
1040
+ test("should validate Yes with details", () => {
1041
+ const validator = getSubschemaValidator(
1042
+ "/propertyPack/rightsAndInformalArrangements/rightsOrArrangements/privateRightOfWay",
1043
+ schemaId,
1044
+ ["rw"]
1045
+ );
1046
+ expect(validator({ yesNo: "Yes", details: "Neighbour has access through garden" })).toBe(true);
1047
+ });
1048
+
1049
+ test("should reject Yes without details", () => {
1050
+ const validator = getSubschemaValidator(
1051
+ "/propertyPack/rightsAndInformalArrangements/rightsOrArrangements/privateRightOfWay",
1052
+ schemaId,
1053
+ ["rw"]
1054
+ );
1055
+ expect(validator({ yesNo: "Yes" })).toBe(false);
1056
+ });
1057
+
1058
+ test("should reject Yes with empty details", () => {
1059
+ const validator = getSubschemaValidator(
1060
+ "/propertyPack/rightsAndInformalArrangements/rightsOrArrangements/privateRightOfWay",
1061
+ schemaId,
1062
+ ["rw"]
1063
+ );
1064
+ expect(validator({ yesNo: "Yes", details: "" })).toBe(false);
1065
+ });
1066
+
1067
+ test("should not be required without rw overlay", () => {
1068
+ const schema = getTransactionSchema(schemaId, ["baspiV5"]);
1069
+ const rights = schema.properties?.propertyPack?.properties?.rightsAndInformalArrangements
1070
+ ?.properties?.rightsOrArrangements;
1071
+ expect(rights?.required || []).not.toContain("privateRightOfWay");
1072
+ });
1073
+
1074
+ test("should be required with rw overlay", () => {
1075
+ const schema = getTransactionSchema(schemaId, ["rw"]);
1076
+ const rights = schema.properties?.propertyPack?.properties?.rightsAndInformalArrangements
1077
+ ?.properties?.rightsOrArrangements;
1078
+ expect(rights?.required).toContain("privateRightOfWay");
1079
+ });
1080
+ });
1081
+
1082
+ describe("Storm/fire/flood damage (sd)", () => {
1083
+ test("should validate No answer", () => {
1084
+ const validator = getSubschemaValidator(
1085
+ "/propertyPack/environmentalIssues/stormFireFloodDamage",
1086
+ schemaId,
1087
+ ["sd"]
1088
+ );
1089
+ expect(validator({ yesNo: "No" })).toBe(true);
1090
+ });
1091
+
1092
+ test("should validate Yes with details", () => {
1093
+ const validator = getSubschemaValidator(
1094
+ "/propertyPack/environmentalIssues/stormFireFloodDamage",
1095
+ schemaId,
1096
+ ["sd"]
1097
+ );
1098
+ expect(validator({ yesNo: "Yes", details: "Flood damage in 2020" })).toBe(true);
1099
+ });
1100
+
1101
+ test("should reject Yes without details", () => {
1102
+ const validator = getSubschemaValidator(
1103
+ "/propertyPack/environmentalIssues/stormFireFloodDamage",
1104
+ schemaId,
1105
+ ["sd"]
1106
+ );
1107
+ expect(validator({ yesNo: "Yes" })).toBe(false);
1108
+ });
1109
+
1110
+ test("should not be required without sd overlay", () => {
1111
+ const schema = getTransactionSchema(schemaId, ["baspiV5"]);
1112
+ const env = schema.properties?.propertyPack?.properties?.environmentalIssues;
1113
+ expect(env?.required || []).not.toContain("stormFireFloodDamage");
1114
+ });
1115
+
1116
+ test("should be required with sd overlay", () => {
1117
+ const schema = getTransactionSchema(schemaId, ["sd"]);
1118
+ const env = schema.properties?.propertyPack?.properties?.environmentalIssues;
1119
+ expect(env?.required).toContain("stormFireFloodDamage");
1120
+ });
1121
+ });
1122
+
1123
+ describe("Solar lease costs (lc)", () => {
1124
+ test("should validate Not applicable frequency", () => {
1125
+ const validator = getSubschemaValidator(
1126
+ "/propertyPack/electricity/solarPanels/panelsOwnedOutright/associatedCost",
1127
+ schemaId,
1128
+ ["lc"]
1129
+ );
1130
+ expect(validator({ amount: 0, frequency: "Not applicable" })).toBe(true);
1131
+ });
1132
+
1133
+ test("should validate amount and frequency", () => {
1134
+ const validator = getSubschemaValidator(
1135
+ "/propertyPack/electricity/solarPanels/panelsOwnedOutright/associatedCost",
1136
+ schemaId,
1137
+ ["lc"]
1138
+ );
1139
+ expect(validator({ amount: 75, frequency: "Per month" })).toBe(true);
1140
+ });
1141
+
1142
+ test("should reject missing amount", () => {
1143
+ const validator = getSubschemaValidator(
1144
+ "/propertyPack/electricity/solarPanels/panelsOwnedOutright/associatedCost",
1145
+ schemaId,
1146
+ ["lc"]
1147
+ );
1148
+ expect(validator({ frequency: "Per month" })).toBe(false);
1149
+ });
1150
+
1151
+ test("should reject missing frequency", () => {
1152
+ const validator = getSubschemaValidator(
1153
+ "/propertyPack/electricity/solarPanels/panelsOwnedOutright/associatedCost",
1154
+ schemaId,
1155
+ ["lc"]
1156
+ );
1157
+ expect(validator({ amount: 75 })).toBe(false);
1158
+ });
1159
+
1160
+ test("should reject invalid frequency", () => {
1161
+ const validator = getSubschemaValidator(
1162
+ "/propertyPack/electricity/solarPanels/panelsOwnedOutright/associatedCost",
1163
+ schemaId,
1164
+ ["lc"]
1165
+ );
1166
+ expect(validator({ amount: 75, frequency: "Weekly" })).toBe(false);
1167
+ });
1168
+ });
1169
+
1170
+ describe("Warranties and guarantees (wg)", () => {
1171
+ const warrantyCategoryPaths = [
1172
+ { path: "newHomeWarranty", ref: "W1.1" },
1173
+ { path: "roofingWork", ref: "W1.2" },
1174
+ { path: "dampProofingTreatment", ref: "W1.3" },
1175
+ { path: "centralHeatingAndorPlumbing", ref: "W1.4" },
1176
+ { path: "electricalRepairOrInstallation", ref: "W1.5" },
1177
+ { path: "subsidenceWork", ref: "W1.6" },
1178
+ { path: "otherGuarantees", ref: "W1.7" },
1179
+ ];
1180
+
1181
+ test("should have ntsRef on all warranty categories when wg overlay loaded", () => {
1182
+ const schema = getTransactionSchema(schemaId, ["baspiV5", "wg"]);
1183
+ const warranties =
1184
+ schema.properties?.propertyPack?.properties?.guaranteesWarrantiesAndIndemnityInsurances;
1185
+
1186
+ // Warranty fields are in oneOf[1] (hasValidGuaranteesOrWarranties = Yes)
1187
+ const yesBranch = warranties.oneOf[1];
1188
+
1189
+ warrantyCategoryPaths.forEach(({ path, ref }) => {
1190
+ expect(yesBranch.properties[path]?.ntsRef).toBe(ref);
1191
+ });
1192
+ });
1193
+
1194
+ test("should mark warranty categories as required when wg overlay loaded", () => {
1195
+ const schema = getTransactionSchema(schemaId, ["baspiV5", "wg"]);
1196
+ const warranties =
1197
+ schema.properties?.propertyPack?.properties?.guaranteesWarrantiesAndIndemnityInsurances;
1198
+
1199
+ const yesBranch = warranties.oneOf[1];
1200
+ warrantyCategoryPaths.forEach(({ path }) => {
1201
+ expect(yesBranch.required).toContain(path);
1202
+ });
1203
+ });
1204
+
1205
+ test("should not require warranty categories without any overlay", () => {
1206
+ const schema = getTransactionSchema(schemaId, []);
1207
+ const warranties =
1208
+ schema.properties?.propertyPack?.properties?.guaranteesWarrantiesAndIndemnityInsurances;
1209
+
1210
+ const yesBranch = warranties.oneOf[1];
1211
+ // Without any overlay, warranty categories should not be in required
1212
+ expect(yesBranch.required || []).not.toContain("newHomeWarranty");
1213
+ expect(yesBranch.required || []).not.toContain("electricalRepairOrInstallation");
1214
+ });
1215
+ });
1216
+
1217
+ describe("Insurance claims (ic)", () => {
1218
+ test("should mark insuranceClaims as required in isInsured=Yes branch", () => {
1219
+ const schema = getTransactionSchema(schemaId, ["ic"]);
1220
+ const insurance = schema.properties?.propertyPack?.properties?.insurance;
1221
+ // oneOf[1] is isInsured=Yes
1222
+ const yesBranch = insurance.oneOf[1];
1223
+ expect(yesBranch?.required).toContain("insuranceClaims");
1224
+ });
1225
+
1226
+ test("should validate insuranceClaims No answer", () => {
1227
+ const validator = getSubschemaValidator(
1228
+ "/propertyPack/insurance/insuranceClaims",
1229
+ schemaId,
1230
+ ["baspiV5", "ic"]
1231
+ );
1232
+ expect(validator({ yesNo: "No" })).toBe(true);
1233
+ });
1234
+
1235
+ test("should validate insuranceClaims Yes with details", () => {
1236
+ const validator = getSubschemaValidator(
1237
+ "/propertyPack/insurance/insuranceClaims",
1238
+ schemaId,
1239
+ ["baspiV5", "ic"]
1240
+ );
1241
+ expect(validator({ yesNo: "Yes", details: "Storm damage claim in 2019" })).toBe(true);
1242
+ });
1243
+ });
1244
+
1245
+ describe("Neighbour development (nd)", () => {
1246
+ test("should add ntsRef to neighbourDevelopment", () => {
1247
+ const schema = getTransactionSchema(schemaId, ["nd"]);
1248
+ const nd = schema.properties?.propertyPack?.properties?.notices
1249
+ ?.properties?.neighbourDevelopment;
1250
+ expect(nd?.ntsRef).toBe("N1.1");
1251
+ });
1252
+
1253
+ test("should add ntsRef via nd overlay", () => {
1254
+ const schema = getTransactionSchema(schemaId, ["nd"]);
1255
+ const notices = schema.properties?.propertyPack?.properties?.notices;
1256
+ expect(notices?.ntsRef).toBe("N1");
1257
+ });
1258
+
1259
+ test("should validate No answer", () => {
1260
+ const validator = getSubschemaValidator(
1261
+ "/propertyPack/notices/neighbourDevelopment",
1262
+ schemaId,
1263
+ ["baspiV5", "nd"]
1264
+ );
1265
+ expect(validator({ yesNo: "No" })).toBe(true);
1266
+ });
1267
+ });
1268
+
1269
+ describe("Other material issue (mi)", () => {
1270
+ test("should mark otherMaterialIssue as required", () => {
1271
+ const schema = getTransactionSchema(schemaId, ["mi"]);
1272
+ const additional = schema.properties?.propertyPack?.properties?.additionalInformation;
1273
+ expect(additional?.required).toContain("otherMaterialIssue");
1274
+ });
1275
+
1276
+ test("should not require otherMaterialIssue without mi overlay", () => {
1277
+ const schema = getTransactionSchema(schemaId, []);
1278
+ const additional = schema.properties?.propertyPack?.properties?.additionalInformation;
1279
+ expect(additional?.required || []).not.toContain("otherMaterialIssue");
1280
+ });
1281
+
1282
+ test("should validate No answer", () => {
1283
+ const validator = getSubschemaValidator(
1284
+ "/propertyPack/additionalInformation/otherMaterialIssue",
1285
+ schemaId,
1286
+ ["baspiV5", "mi"]
1287
+ );
1288
+ expect(validator({ yesNo: "No" })).toBe(true);
1289
+ });
1290
+
1291
+ test("should validate Yes with details", () => {
1292
+ const validator = getSubschemaValidator(
1293
+ "/propertyPack/additionalInformation/otherMaterialIssue",
1294
+ schemaId,
1295
+ ["baspiV5", "mi"]
1296
+ );
1297
+ expect(validator({ yesNo: "Yes", details: "Previous subsidence issues" })).toBe(true);
1298
+ });
1299
+ });
1300
+
1301
+ describe("Title restrictions (tr)", () => {
1302
+ test("should require titleRestrictions in freehold branch with tr overlay", () => {
1303
+ const schema = getTransactionSchema(schemaId, ["tr"]);
1304
+ const items = schema.properties?.propertyPack?.properties?.ownership
1305
+ ?.properties?.ownershipsToBeTransferred?.items;
1306
+ // oneOf[0] is Freehold
1307
+ const freeholdBranch = items.oneOf[0];
1308
+ expect(freeholdBranch?.properties?.titleRestrictions).toBeDefined();
1309
+ expect(freeholdBranch?.required).toContain("titleRestrictions");
1310
+ });
1311
+
1312
+ test("should not require titleRestrictions without tr overlay", () => {
1313
+ const schema = getTransactionSchema(schemaId, ["baspiV5"]);
1314
+ const items = schema.properties?.propertyPack?.properties?.ownership
1315
+ ?.properties?.ownershipsToBeTransferred?.items;
1316
+ const freeholdBranch = items.oneOf[0];
1317
+ expect(freeholdBranch?.required || []).not.toContain("titleRestrictions");
1318
+ });
1319
+
1320
+ test("should have correct ntsRef", () => {
1321
+ const schema = getTransactionSchema(schemaId, ["tr"]);
1322
+ const items = schema.properties?.propertyPack?.properties?.ownership
1323
+ ?.properties?.ownershipsToBeTransferred?.items;
1324
+ const freeholdBranch = items.oneOf[0];
1325
+ expect(freeholdBranch?.properties?.titleRestrictions?.ntsRef).toBe("T1.1");
1326
+ });
1327
+ });
1328
+
1329
+ describe("All SEF25 extensions combined", () => {
1330
+ const allSef25 = ["sc", "pc", "ph", "dk", "rw", "sd", "lc", "wg", "ic", "nd", "mi", "tr"];
1331
+
1332
+ test("should load all SEF25 extensions together without error", () => {
1333
+ const schema = getTransactionSchema(schemaId, ["baspiV5", ...allSef25]);
1334
+ expect(schema).toBeDefined();
1335
+ expect(schema.properties?.propertyPack).toBeDefined();
1336
+ });
1337
+
1338
+ test("should not interfere with existing NTS2 extensions when combined", () => {
1339
+ const schema = getTransactionSchema(schemaId, ["nts2025", ...allSef25]);
1340
+
1341
+ // NTS2 fields still work
1342
+ const si = schema.properties?.propertyPack?.properties?.specialistIssues;
1343
+ expect(si?.properties?.wellsDitchesShaft?.ntsRef).toBe("H1.1");
1344
+
1345
+ // New SEF25 fields present
1346
+ const parking = schema.properties?.propertyPack?.properties?.parking?.properties;
1347
+ expect(parking?.droppedKerbAccess).toBeDefined();
1348
+
1349
+ const env = schema.properties?.propertyPack?.properties?.environmentalIssues?.properties;
1350
+ expect(env?.stormFireFloodDamage).toBeDefined();
1351
+
1352
+ const rights = schema.properties?.propertyPack?.properties?.rightsAndInformalArrangements
1353
+ ?.properties?.rightsOrArrangements?.properties;
1354
+ expect(rights?.privateRightOfWay).toBeDefined();
1355
+ });
1356
+ });
960
1357
  });
961
1358
  });