@pdtf/schemas 3.6.0-dev.3 → 3.6.0-dev.4

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 (263) hide show
  1. package/charming-napier-57a77c/LICENSE +21 -0
  2. package/charming-napier-57a77c/README.md +430 -0
  3. package/charming-napier-57a77c/docs/sef25-extensions-ui-spec.md +355 -0
  4. package/charming-napier-57a77c/index.js +654 -0
  5. package/charming-napier-57a77c/package-lock.json +7297 -0
  6. package/charming-napier-57a77c/package.json +41 -0
  7. package/charming-napier-57a77c/src/examples/v1/exampleAddParticipantVouch.json +32 -0
  8. package/charming-napier-57a77c/src/examples/v1/exampleDocumentedVouch.json +40 -0
  9. package/charming-napier-57a77c/src/examples/v1/exampleElectronicRecord.json +30 -0
  10. package/charming-napier-57a77c/src/examples/v1/exampleTransaction.json +878 -0
  11. package/charming-napier-57a77c/src/examples/v1/exampleVouch.json +25 -0
  12. package/charming-napier-57a77c/src/examples/v2/exampleTransaction.json +901 -0
  13. package/charming-napier-57a77c/src/examples/v3/exampleAddParticipantVouch.json +38 -0
  14. package/charming-napier-57a77c/src/examples/v3/exampleCustomOverlay.json +55 -0
  15. package/charming-napier-57a77c/src/examples/v3/exampleCustomOverlay2.json +93 -0
  16. package/charming-napier-57a77c/src/examples/v3/exampleDocumentedVouch.json +47 -0
  17. package/charming-napier-57a77c/src/examples/v3/exampleElectronicRecord.json +36 -0
  18. package/charming-napier-57a77c/src/examples/v3/exampleTransaction.json +1038 -0
  19. package/charming-napier-57a77c/src/examples/v3/exampleVouch.json +32 -0
  20. package/charming-napier-57a77c/src/mappings/SendPropertyDetailsRequestSchema.json +1588 -0
  21. package/charming-napier-57a77c/src/mappings/adf.js +198 -0
  22. package/charming-napier-57a77c/src/schemas/v1/GeoJSON.json +1279 -0
  23. package/charming-napier-57a77c/src/schemas/v1/baspiNotes.html +102 -0
  24. package/charming-napier-57a77c/src/schemas/v1/energy-performance-certificate.json +124 -0
  25. package/charming-napier-57a77c/src/schemas/v1/freehold-information.json +2051 -0
  26. package/charming-napier-57a77c/src/schemas/v1/leasehold-information.json +2064 -0
  27. package/charming-napier-57a77c/src/schemas/v1/legal-information.json +1045 -0
  28. package/charming-napier-57a77c/src/schemas/v1/material-facts.json +2109 -0
  29. package/charming-napier-57a77c/src/schemas/v1/pdtf-transaction-search-results.json +40 -0
  30. package/charming-napier-57a77c/src/schemas/v1/pdtf-transaction.json +191 -0
  31. package/charming-napier-57a77c/src/schemas/v1/searches/drainage-and-water.json +261 -0
  32. package/charming-napier-57a77c/src/schemas/v1/searches/local-land-charges.json +69 -0
  33. package/charming-napier-57a77c/src/schemas/v1/searches/local-searches-required.json +857 -0
  34. package/charming-napier-57a77c/src/schemas/v1/searches.json +237 -0
  35. package/charming-napier-57a77c/src/schemas/v1/title-deed.json +841 -0
  36. package/charming-napier-57a77c/src/schemas/v2/baspiNotes.html +102 -0
  37. package/charming-napier-57a77c/src/schemas/v2/combined.json +37731 -0
  38. package/charming-napier-57a77c/src/schemas/v2/overlays/baspi.json +8132 -0
  39. package/charming-napier-57a77c/src/schemas/v2/overlays/con29DW.json +705 -0
  40. package/charming-napier-57a77c/src/schemas/v2/overlays/con29R.json +2956 -0
  41. package/charming-napier-57a77c/src/schemas/v2/overlays/fme1.json +1259 -0
  42. package/charming-napier-57a77c/src/schemas/v2/overlays/llc1.json +25 -0
  43. package/charming-napier-57a77c/src/schemas/v2/overlays/lpe1.json +2171 -0
  44. package/charming-napier-57a77c/src/schemas/v2/overlays/nts.json +326 -0
  45. package/charming-napier-57a77c/src/schemas/v2/overlays/oc1.json +2799 -0
  46. package/charming-napier-57a77c/src/schemas/v2/overlays/piq.json +3419 -0
  47. package/charming-napier-57a77c/src/schemas/v2/overlays/rds.json +3736 -0
  48. package/charming-napier-57a77c/src/schemas/v2/overlays/ta10.json +5306 -0
  49. package/charming-napier-57a77c/src/schemas/v2/overlays/ta6.json +4533 -0
  50. package/charming-napier-57a77c/src/schemas/v2/overlays/ta7.json +1045 -0
  51. package/charming-napier-57a77c/src/schemas/v2/pdtf-transaction.json +34351 -0
  52. package/charming-napier-57a77c/src/schemas/v2/skeleton.json +3973 -0
  53. package/charming-napier-57a77c/src/schemas/v3/combined.json +48153 -0
  54. package/charming-napier-57a77c/src/schemas/v3/compactSkeleton.txt +4457 -0
  55. package/charming-napier-57a77c/src/schemas/v3/overlays/README.md +299 -0
  56. package/charming-napier-57a77c/src/schemas/v3/overlays/baspi4.json +8027 -0
  57. package/charming-napier-57a77c/src/schemas/v3/overlays/baspi5.json +8177 -0
  58. package/charming-napier-57a77c/src/schemas/v3/overlays/con29DW.json +716 -0
  59. package/charming-napier-57a77c/src/schemas/v3/overlays/con29R.json +2981 -0
  60. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/ac.json +333 -0
  61. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/as.json +74 -0
  62. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/dk.json +32 -0
  63. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/dr.json +73 -0
  64. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/er.json +85 -0
  65. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/fd.json +75 -0
  66. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/hi.json +51 -0
  67. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/hs.json +73 -0
  68. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/ic.json +59 -0
  69. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/jk.json +96 -0
  70. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/la.json +85 -0
  71. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/lc.json +75 -0
  72. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/ma.json +90 -0
  73. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/mc.json +38 -0
  74. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/mi.json +69 -0
  75. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/nd.json +43 -0
  76. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/oa.json +24 -0
  77. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/oc.json +57 -0
  78. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/pc.json +37 -0
  79. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/ph.json +194 -0
  80. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/rw.json +68 -0
  81. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/sb.json +73 -0
  82. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/sc.json +129 -0
  83. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/sd.json +63 -0
  84. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/sf.json +74 -0
  85. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/sl.json +68 -0
  86. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/ta.json +240 -0
  87. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/tf.json +103 -0
  88. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/tr.json +73 -0
  89. package/charming-napier-57a77c/src/schemas/v3/overlays/extensions/wg.json +350 -0
  90. package/charming-napier-57a77c/src/schemas/v3/overlays/fme1.json +1266 -0
  91. package/charming-napier-57a77c/src/schemas/v3/overlays/llc1.json +25 -0
  92. package/charming-napier-57a77c/src/schemas/v3/overlays/lpe1.json +2199 -0
  93. package/charming-napier-57a77c/src/schemas/v3/overlays/nts.json +2157 -0
  94. package/charming-napier-57a77c/src/schemas/v3/overlays/nts2.json +2742 -0
  95. package/charming-napier-57a77c/src/schemas/v3/overlays/ntsl.json +1813 -0
  96. package/charming-napier-57a77c/src/schemas/v3/overlays/ntsl2.json +2217 -0
  97. package/charming-napier-57a77c/src/schemas/v3/overlays/oc1.json +2817 -0
  98. package/charming-napier-57a77c/src/schemas/v3/overlays/piq.json +3321 -0
  99. package/charming-napier-57a77c/src/schemas/v3/overlays/rds.json +3665 -0
  100. package/charming-napier-57a77c/src/schemas/v3/overlays/sef25.json +1639 -0
  101. package/charming-napier-57a77c/src/schemas/v3/overlays/sr24.json +38 -0
  102. package/charming-napier-57a77c/src/schemas/v3/overlays/ta10.json +4654 -0
  103. package/charming-napier-57a77c/src/schemas/v3/overlays/ta6.json +4568 -0
  104. package/charming-napier-57a77c/src/schemas/v3/overlays/ta6ed6.json +6537 -0
  105. package/charming-napier-57a77c/src/schemas/v3/overlays/ta7.json +1537 -0
  106. package/charming-napier-57a77c/src/schemas/v3/overlays/ta7ed5.json +1551 -0
  107. package/charming-napier-57a77c/src/schemas/v3/pdtf-transaction.json +41229 -0
  108. package/charming-napier-57a77c/src/schemas/v3/skeleton.json +5703 -0
  109. package/charming-napier-57a77c/src/schemas/verifiedClaims/README.md +21 -0
  110. package/charming-napier-57a77c/src/schemas/verifiedClaims/pdtf-verified-claims.json +447 -0
  111. package/charming-napier-57a77c/src/tests/v3/buyerCircumstances.test.js +543 -0
  112. package/charming-napier-57a77c/src/tests/v3/caching.test.js +591 -0
  113. package/charming-napier-57a77c/src/tests/v3/enquiries.test.js +1096 -0
  114. package/charming-napier-57a77c/src/tests/v3/extensionOverlays.test.js +1599 -0
  115. package/charming-napier-57a77c/src/tests/v3/offers.test.js +823 -0
  116. package/charming-napier-57a77c/src/tests/v3/patternProperties.test.js +688 -0
  117. package/charming-napier-57a77c/src/tests/v3/ta6ed6.test.js +2374 -0
  118. package/charming-napier-57a77c/src/tests/v3/ta7ed5.test.js +600 -0
  119. package/charming-napier-57a77c/src/tests/v3/transactionSchema.test.js +646 -0
  120. package/charming-napier-57a77c/src/tests/v3/verifiedClaimsValidator.test.js +195 -0
  121. package/charming-napier-57a77c/src/utils/compactSkeleton.js +84 -0
  122. package/charming-napier-57a77c/src/utils/countTokens.js +67 -0
  123. package/charming-napier-57a77c/src/utils/extractExtensionOverlays.js +624 -0
  124. package/charming-napier-57a77c/src/utils/extractOverlay.js +381 -0
  125. package/charming-napier-57a77c/src/utils/extractProperties.js +44 -0
  126. package/charming-napier-57a77c/src/utils/listPathTitles.js +36 -0
  127. package/charming-napier-57a77c/src/utils/minimalSkeleton.js +85 -0
  128. package/charming-napier-57a77c/src/utils/pathList.csv +903 -0
  129. package/charming-napier-57a77c/src/utils/pathSkeleton.js +181 -0
  130. package/keen-dewdney-6ae15b/LICENSE +21 -0
  131. package/keen-dewdney-6ae15b/README.md +334 -0
  132. package/keen-dewdney-6ae15b/index.js +557 -0
  133. package/keen-dewdney-6ae15b/package-lock.json +7296 -0
  134. package/keen-dewdney-6ae15b/package.json +39 -0
  135. package/keen-dewdney-6ae15b/src/examples/v1/exampleAddParticipantVouch.json +32 -0
  136. package/keen-dewdney-6ae15b/src/examples/v1/exampleDocumentedVouch.json +40 -0
  137. package/keen-dewdney-6ae15b/src/examples/v1/exampleElectronicRecord.json +30 -0
  138. package/keen-dewdney-6ae15b/src/examples/v1/exampleTransaction.json +878 -0
  139. package/keen-dewdney-6ae15b/src/examples/v1/exampleVouch.json +25 -0
  140. package/keen-dewdney-6ae15b/src/examples/v2/exampleTransaction.json +901 -0
  141. package/keen-dewdney-6ae15b/src/examples/v3/exampleAddParticipantVouch.json +35 -0
  142. package/keen-dewdney-6ae15b/src/examples/v3/exampleCustomOverlay.json +55 -0
  143. package/keen-dewdney-6ae15b/src/examples/v3/exampleCustomOverlay2.json +93 -0
  144. package/keen-dewdney-6ae15b/src/examples/v3/exampleDocumentedVouch.json +44 -0
  145. package/keen-dewdney-6ae15b/src/examples/v3/exampleElectronicRecord.json +33 -0
  146. package/keen-dewdney-6ae15b/src/examples/v3/exampleTransaction.json +1034 -0
  147. package/keen-dewdney-6ae15b/src/examples/v3/exampleVouch.json +29 -0
  148. package/keen-dewdney-6ae15b/src/mappings/SendPropertyDetailsRequestSchema.json +1588 -0
  149. package/keen-dewdney-6ae15b/src/mappings/adf.js +198 -0
  150. package/keen-dewdney-6ae15b/src/schemas/v1/GeoJSON.json +1279 -0
  151. package/keen-dewdney-6ae15b/src/schemas/v1/baspiNotes.html +102 -0
  152. package/keen-dewdney-6ae15b/src/schemas/v1/energy-performance-certificate.json +124 -0
  153. package/keen-dewdney-6ae15b/src/schemas/v1/freehold-information.json +2051 -0
  154. package/keen-dewdney-6ae15b/src/schemas/v1/leasehold-information.json +2064 -0
  155. package/keen-dewdney-6ae15b/src/schemas/v1/legal-information.json +1045 -0
  156. package/keen-dewdney-6ae15b/src/schemas/v1/material-facts.json +2109 -0
  157. package/keen-dewdney-6ae15b/src/schemas/v1/pdtf-transaction-search-results.json +40 -0
  158. package/keen-dewdney-6ae15b/src/schemas/v1/pdtf-transaction.json +191 -0
  159. package/keen-dewdney-6ae15b/src/schemas/v1/searches/drainage-and-water.json +261 -0
  160. package/keen-dewdney-6ae15b/src/schemas/v1/searches/local-land-charges.json +69 -0
  161. package/keen-dewdney-6ae15b/src/schemas/v1/searches/local-searches-required.json +857 -0
  162. package/keen-dewdney-6ae15b/src/schemas/v1/searches.json +237 -0
  163. package/keen-dewdney-6ae15b/src/schemas/v1/title-deed.json +841 -0
  164. package/keen-dewdney-6ae15b/src/schemas/v2/baspiNotes.html +102 -0
  165. package/keen-dewdney-6ae15b/src/schemas/v2/combined.json +37731 -0
  166. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/baspi.json +8132 -0
  167. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/con29DW.json +705 -0
  168. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/con29R.json +2956 -0
  169. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/fme1.json +1259 -0
  170. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/llc1.json +25 -0
  171. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/lpe1.json +2171 -0
  172. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/nts.json +326 -0
  173. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/oc1.json +2799 -0
  174. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/piq.json +3419 -0
  175. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/rds.json +3736 -0
  176. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/ta10.json +5306 -0
  177. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/ta6.json +4533 -0
  178. package/keen-dewdney-6ae15b/src/schemas/v2/overlays/ta7.json +1045 -0
  179. package/keen-dewdney-6ae15b/src/schemas/v2/pdtf-transaction.json +34351 -0
  180. package/keen-dewdney-6ae15b/src/schemas/v2/skeleton.json +3973 -0
  181. package/keen-dewdney-6ae15b/src/schemas/v3/combined.json +41720 -0
  182. package/keen-dewdney-6ae15b/src/schemas/v3/compactSkeleton.txt +3325 -0
  183. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/README.md +299 -0
  184. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/baspi4.json +7872 -0
  185. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/baspi5.json +8019 -0
  186. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/con29DW.json +716 -0
  187. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/con29R.json +2981 -0
  188. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/as.json +72 -0
  189. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/dr.json +73 -0
  190. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/er.json +83 -0
  191. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/fd.json +72 -0
  192. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/hi.json +48 -0
  193. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/hs.json +71 -0
  194. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/jk.json +76 -0
  195. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/la.json +83 -0
  196. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/ma.json +87 -0
  197. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/mc.json +36 -0
  198. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/oa.json +22 -0
  199. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/oc.json +55 -0
  200. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/sb.json +71 -0
  201. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/sf.json +72 -0
  202. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/sl.json +66 -0
  203. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/extensions/tf.json +100 -0
  204. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/fme1.json +1264 -0
  205. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/llc1.json +25 -0
  206. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/lpe1.json +2226 -0
  207. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/nts.json +2090 -0
  208. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/nts2.json +2678 -0
  209. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/ntsl.json +1746 -0
  210. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/ntsl2.json +2153 -0
  211. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/oc1.json +2817 -0
  212. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/piq.json +3276 -0
  213. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/rds.json +3552 -0
  214. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/sr24.json +38 -0
  215. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/ta10.json +4642 -0
  216. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/ta6.json +4501 -0
  217. package/keen-dewdney-6ae15b/src/schemas/v3/overlays/ta7.json +1435 -0
  218. package/keen-dewdney-6ae15b/src/schemas/v3/pdtf-transaction.json +37225 -0
  219. package/keen-dewdney-6ae15b/src/schemas/v3/skeleton.json +4323 -0
  220. package/keen-dewdney-6ae15b/src/schemas/verifiedClaims/README.md +12 -0
  221. package/keen-dewdney-6ae15b/src/schemas/verifiedClaims/pdtf-verified-claims.json +431 -0
  222. package/keen-dewdney-6ae15b/src/tests/v3/caching.test.js +591 -0
  223. package/keen-dewdney-6ae15b/src/tests/v3/extensionOverlays.test.js +525 -0
  224. package/keen-dewdney-6ae15b/src/tests/v3/transactionSchema.test.js +574 -0
  225. package/keen-dewdney-6ae15b/src/tests/v3/verifiedClaimsValidator.test.js +128 -0
  226. package/keen-dewdney-6ae15b/src/utils/compactSkeleton.js +84 -0
  227. package/keen-dewdney-6ae15b/src/utils/countTokens.js +67 -0
  228. package/keen-dewdney-6ae15b/src/utils/extractExtensionOverlays.js +362 -0
  229. package/keen-dewdney-6ae15b/src/utils/extractOverlay.js +290 -0
  230. package/keen-dewdney-6ae15b/src/utils/extractProperties.js +44 -0
  231. package/keen-dewdney-6ae15b/src/utils/listPathTitles.js +36 -0
  232. package/keen-dewdney-6ae15b/src/utils/minimalSkeleton.js +85 -0
  233. package/keen-dewdney-6ae15b/src/utils/pathList.csv +903 -0
  234. package/keen-dewdney-6ae15b/src/utils/pathSkeleton.js +181 -0
  235. package/package.json +1 -1
  236. package/src/schemas/v3/combined.json +8 -15
  237. package/src/schemas/v3/overlays/baspi4.json +12 -7
  238. package/src/schemas/v3/overlays/baspi5.json +12 -7
  239. package/src/schemas/v3/overlays/extensions/ac.json +2 -1
  240. package/src/schemas/v3/overlays/extensions/dk.json +3 -2
  241. package/src/schemas/v3/overlays/extensions/ic.json +2 -1
  242. package/src/schemas/v3/overlays/extensions/lc.json +3 -2
  243. package/src/schemas/v3/overlays/extensions/mi.json +2 -1
  244. package/src/schemas/v3/overlays/extensions/nd.json +3 -2
  245. package/src/schemas/v3/overlays/extensions/pc.json +3 -2
  246. package/src/schemas/v3/overlays/extensions/ph.json +2 -1
  247. package/src/schemas/v3/overlays/extensions/rw.json +4 -3
  248. package/src/schemas/v3/overlays/extensions/sc.json +8 -4
  249. package/src/schemas/v3/overlays/extensions/sd.json +3 -2
  250. package/src/schemas/v3/overlays/extensions/tr.json +3 -2
  251. package/src/schemas/v3/overlays/extensions/wg.json +2 -1
  252. package/src/schemas/v3/overlays/lpe1.json +1 -1
  253. package/src/schemas/v3/overlays/nts.json +3 -1
  254. package/src/schemas/v3/overlays/nts2.json +3 -1
  255. package/src/schemas/v3/overlays/ntsl.json +3 -1
  256. package/src/schemas/v3/overlays/ntsl2.json +3 -1
  257. package/src/schemas/v3/overlays/piq.json +7 -5
  258. package/src/schemas/v3/overlays/rds.json +9 -6
  259. package/src/schemas/v3/overlays/ta10.json +0 -9
  260. package/src/schemas/v3/overlays/ta6.json +8 -4
  261. package/src/schemas/v3/overlays/ta6ed6.json +4 -2
  262. package/src/schemas/v3/pdtf-transaction.json +15 -18
  263. package/src/utils/extractExtensionOverlays.js +10 -1
@@ -0,0 +1,2374 @@
1
+ const {
2
+ getTransactionSchema,
3
+ getSubschemaValidator,
4
+ } = require("../../../index.js");
5
+ const ta6ed6Overlay = require("../../schemas/v3/overlays/ta6ed6.json");
6
+
7
+ const schemaId =
8
+ "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
9
+
10
+ const collectRequiredWithoutPropertyIssues = (schema) => {
11
+ const issues = [];
12
+
13
+ const walk = (node, path = "#") => {
14
+ if (!node || typeof node !== "object") return;
15
+
16
+ if (Array.isArray(node.required)) {
17
+ node.required.forEach((propertyName) => {
18
+ if (!node.properties?.[propertyName]) {
19
+ issues.push(`${path} requires ${propertyName}`);
20
+ }
21
+ });
22
+ }
23
+
24
+ Object.entries(node.properties || {}).forEach(([key, value]) =>
25
+ walk(value, `${path}/properties/${key}`)
26
+ );
27
+ (node.oneOf || []).forEach((value, index) =>
28
+ walk(value, `${path}/oneOf/${index}`)
29
+ );
30
+ if (node.items) walk(node.items, `${path}/items`);
31
+ };
32
+
33
+ walk(schema);
34
+ return issues;
35
+ };
36
+
37
+ describe("TA6 Edition 6 overlay", () => {
38
+ describe("Schema merging", () => {
39
+ test("merged schema contains ta6ed6Ref annotations", () => {
40
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
41
+ expect(schema.properties.propertyPack.properties.address.ta6ed6Ref).toBe(
42
+ "1.1"
43
+ );
44
+ expect(
45
+ schema.properties.propertyPack.properties.parking.ta6ed6Ref
46
+ ).toBe("10");
47
+ expect(
48
+ schema.properties.propertyPack.properties.electricalWorks.ta6ed6Ref
49
+ ).toBe("11");
50
+ });
51
+
52
+ test("generated overlay exposes every TA6 edition 6 required property", () => {
53
+ expect(collectRequiredWithoutPropertyIssues(ta6ed6Overlay)).toEqual([]);
54
+ });
55
+
56
+ test("merged schema has all 22 ta6ed6 required propertyPack sections", () => {
57
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
58
+ const required = schema.properties.propertyPack.required;
59
+ const expectedRequired = [
60
+ "address",
61
+ "parking",
62
+ "disputesAndComplaints",
63
+ "alterationsAndChanges",
64
+ "listingAndConservation",
65
+ "notices",
66
+ "specialistIssues",
67
+ "waterAndDrainage",
68
+ "electricity",
69
+ "heating",
70
+ "connectivity",
71
+ "insurance",
72
+ "rightsAndInformalArrangements",
73
+ "environmentalIssues",
74
+ "legalBoundaries",
75
+ "servicesCrossing",
76
+ "electricalWorks",
77
+ "guaranteesWarrantiesAndIndemnityInsurances",
78
+ "occupiers",
79
+ "completionAndMoving",
80
+ "confirmationOfAccuracyByOwners",
81
+ "additionalInformation",
82
+ ];
83
+ expectedRequired.forEach((field) => {
84
+ expect(required).toContain(field);
85
+ });
86
+ });
87
+
88
+ test("merged schema requires participants and propertyPack at root", () => {
89
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
90
+ expect(schema.required).toContain("participants");
91
+ expect(schema.required).toContain("propertyPack");
92
+ });
93
+
94
+ test("can combine ta6ed6 with baspiV5 overlay", () => {
95
+ const schema = getTransactionSchema(schemaId, ["baspiV5", "ta6ed6"]);
96
+ const parkingProp =
97
+ schema.properties.propertyPack.properties.parking.properties
98
+ .parkingArrangements;
99
+ expect(parkingProp.baspi5Ref).toBe("A1.6.0");
100
+ expect(parkingProp.ta6ed6Ref).toBe("10.1");
101
+ });
102
+
103
+ test("alterationsAndChanges requires ta6ed6-specific fields", () => {
104
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
105
+ const required =
106
+ schema.properties.propertyPack.properties.alterationsAndChanges
107
+ .required;
108
+ expect(required).toContain("windowReplacementsSince2002");
109
+ expect(required).toContain("hasAddedConservatory");
110
+ expect(required).toContain("extension");
111
+ expect(required).toContain("loftConversion");
112
+ expect(required).toContain("garageConversion");
113
+ expect(required).toContain("removalOfInternalWalls");
114
+ expect(required).toContain("removalOfChimneyBreast");
115
+ expect(required).toContain("insulation");
116
+ expect(required).toContain("otherBuildingWorksOrChangesToTheProperty");
117
+ expect(required).not.toContain("changeOfUse"); // removed in ed6
118
+ expect(required).toContain("planningPermissionBreaches");
119
+ expect(required).toContain("unresolvedPlanningIssues");
120
+ });
121
+
122
+ test("parking requires parkingArrangements, controlledParking, and electricVehicleChargingPoint", () => {
123
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
124
+ const required =
125
+ schema.properties.propertyPack.properties.parking.required;
126
+ expect(required).toEqual(
127
+ expect.arrayContaining([
128
+ "parkingArrangements",
129
+ "controlledParking",
130
+ "electricVehicleChargingPoint",
131
+ ])
132
+ );
133
+ });
134
+
135
+ test("occupiers requires sellerLivesAtProperty and othersAged17OrOver", () => {
136
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
137
+ const required =
138
+ schema.properties.propertyPack.properties.occupiers.required;
139
+ expect(required).toContain("sellerLivesAtProperty");
140
+ expect(required).toContain("othersAged17OrOver");
141
+ });
142
+
143
+ test("completionAndMoving requires sellerWillEnsure and sufficientToRepayAllMortgages", () => {
144
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
145
+ const required =
146
+ schema.properties.propertyPack.properties.completionAndMoving.required;
147
+ expect(required).toContain("sellerWillEnsure");
148
+ expect(required).toContain("sufficientToRepayAllMortgages");
149
+ });
150
+
151
+ test("heating requires ta6ed6-specific fields", () => {
152
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
153
+ const required =
154
+ schema.properties.propertyPack.properties.heating.required;
155
+ expect(required).toContain("heatingSystem");
156
+ expect(required).toContain("otherHeatingFeatures");
157
+ expect(required).toContain("multipleHeatingSystems");
158
+ });
159
+ });
160
+
161
+ describe("Parking section validation", () => {
162
+ let parkingValidator;
163
+
164
+ beforeAll(() => {
165
+ parkingValidator = getSubschemaValidator(
166
+ "/propertyPack/parking",
167
+ schemaId,
168
+ ["ta6ed6"]
169
+ );
170
+ });
171
+
172
+ test("valid parking data passes", () => {
173
+ const data = {
174
+ parkingArrangements: ["Driveway"],
175
+ controlledParking: { yesNo: "No" },
176
+ electricVehicleChargingPoint: { yesNo: "No" },
177
+ };
178
+ expect(parkingValidator(data)).toBe(true);
179
+ });
180
+
181
+ test("missing parkingArrangements is invalid", () => {
182
+ const data = {
183
+ controlledParking: { yesNo: "No" },
184
+ electricVehicleChargingPoint: { yesNo: "No" },
185
+ };
186
+ expect(parkingValidator(data)).toBe(false);
187
+ expect(
188
+ parkingValidator.errors.some((e) =>
189
+ e.message.includes("'parkingArrangements'")
190
+ )
191
+ ).toBe(true);
192
+ });
193
+
194
+ test("missing controlledParking is invalid", () => {
195
+ const data = {
196
+ parkingArrangements: ["Driveway"],
197
+ electricVehicleChargingPoint: { yesNo: "No" },
198
+ };
199
+ expect(parkingValidator(data)).toBe(false);
200
+ expect(
201
+ parkingValidator.errors.some((e) =>
202
+ e.message.includes("'controlledParking'")
203
+ )
204
+ ).toBe(true);
205
+ });
206
+
207
+ test("missing electricVehicleChargingPoint is invalid", () => {
208
+ const data = {
209
+ parkingArrangements: ["Driveway"],
210
+ controlledParking: { yesNo: "No" },
211
+ };
212
+ expect(parkingValidator(data)).toBe(false);
213
+ expect(
214
+ parkingValidator.errors.some((e) =>
215
+ e.message.includes("'electricVehicleChargingPoint'")
216
+ )
217
+ ).toBe(true);
218
+ });
219
+
220
+ test("controlledParking Yes is valid", () => {
221
+ const data = {
222
+ parkingArrangements: ["Driveway"],
223
+ controlledParking: { yesNo: "Yes" },
224
+ electricVehicleChargingPoint: { yesNo: "No" },
225
+ };
226
+ expect(parkingValidator(data)).toBe(true);
227
+ });
228
+
229
+ test("controlledParking No is valid", () => {
230
+ const data = {
231
+ parkingArrangements: ["Driveway"],
232
+ controlledParking: { yesNo: "No" },
233
+ electricVehicleChargingPoint: { yesNo: "No" },
234
+ };
235
+ expect(parkingValidator(data)).toBe(true);
236
+ });
237
+
238
+ test("electricVehicleChargingPoint Yes without additional details is invalid", () => {
239
+ const data = {
240
+ parkingArrangements: ["Driveway"],
241
+ controlledParking: { yesNo: "No" },
242
+ electricVehicleChargingPoint: { yesNo: "Yes" },
243
+ };
244
+ expect(parkingValidator(data)).toBe(false);
245
+ expect(
246
+ parkingValidator.errors.some((e) =>
247
+ e.message.includes("'chargerDetails'")
248
+ )
249
+ ).toBe(true);
250
+ });
251
+
252
+ test("electricVehicleChargingPoint Yes with all required details is valid", () => {
253
+ const data = {
254
+ parkingArrangements: ["Driveway"],
255
+ controlledParking: { yesNo: "No" },
256
+ electricVehicleChargingPoint: {
257
+ yesNo: "Yes",
258
+ chargerDetails: "7kW home charger",
259
+ chargerDetailsAttachments: "Attached",
260
+ cableCrossesPavement: { yesNo: "No" },
261
+ },
262
+ };
263
+ expect(parkingValidator(data)).toBe(true);
264
+ });
265
+
266
+ test("electricVehicleChargingPoint cable crosses pavement Yes without details is invalid", () => {
267
+ const data = {
268
+ parkingArrangements: ["Driveway"],
269
+ controlledParking: { yesNo: "No" },
270
+ electricVehicleChargingPoint: {
271
+ yesNo: "Yes",
272
+ chargerDetails: "7kW home charger",
273
+ chargerDetailsAttachments: "Attached",
274
+ cableCrossesPavement: { yesNo: "Yes" },
275
+ },
276
+ };
277
+ expect(parkingValidator(data)).toBe(false);
278
+ expect(
279
+ parkingValidator.errors.some((e) => e.message.includes("'details'"))
280
+ ).toBe(true);
281
+ });
282
+
283
+ test("electricVehicleChargingPoint cable crosses pavement Yes with details is valid", () => {
284
+ const data = {
285
+ parkingArrangements: ["Driveway"],
286
+ controlledParking: { yesNo: "No" },
287
+ electricVehicleChargingPoint: {
288
+ yesNo: "Yes",
289
+ chargerDetails: "7kW home charger",
290
+ chargerDetailsAttachments: "Attached",
291
+ cableCrossesPavement: {
292
+ yesNo: "Yes",
293
+ details: "Cable runs under pavement with council approval",
294
+ },
295
+ },
296
+ };
297
+ expect(parkingValidator(data)).toBe(true);
298
+ });
299
+ });
300
+
301
+ describe("Disputes and complaints validation", () => {
302
+ let validator;
303
+
304
+ beforeAll(() => {
305
+ validator = getSubschemaValidator(
306
+ "/propertyPack/disputesAndComplaints",
307
+ schemaId,
308
+ ["ta6ed6"]
309
+ );
310
+ });
311
+
312
+ test("valid disputes data with No answers passes", () => {
313
+ const data = {
314
+ hasDisputesAndComplaints: { yesNo: "No" },
315
+ leadingToDisputesAndComplaints: { yesNo: "No" },
316
+ };
317
+ expect(validator(data)).toBe(true);
318
+ });
319
+
320
+ test("hasDisputesAndComplaints Yes without details is invalid", () => {
321
+ const data = {
322
+ hasDisputesAndComplaints: { yesNo: "Yes" },
323
+ leadingToDisputesAndComplaints: { yesNo: "No" },
324
+ };
325
+ expect(validator(data)).toBe(false);
326
+ expect(
327
+ validator.errors.some((e) => e.message.includes("'details'"))
328
+ ).toBe(true);
329
+ });
330
+
331
+ test("hasDisputesAndComplaints Yes with details is valid", () => {
332
+ const data = {
333
+ hasDisputesAndComplaints: {
334
+ yesNo: "Yes",
335
+ details: "Boundary fence dispute with neighbour",
336
+ },
337
+ leadingToDisputesAndComplaints: { yesNo: "No" },
338
+ };
339
+ expect(validator(data)).toBe(true);
340
+ });
341
+
342
+ test("leadingToDisputesAndComplaints Yes without details is invalid", () => {
343
+ const data = {
344
+ hasDisputesAndComplaints: { yesNo: "No" },
345
+ leadingToDisputesAndComplaints: { yesNo: "Yes" },
346
+ };
347
+ expect(validator(data)).toBe(false);
348
+ expect(
349
+ validator.errors.some((e) => e.message.includes("'details'"))
350
+ ).toBe(true);
351
+ });
352
+
353
+ test("leadingToDisputesAndComplaints Yes with details is valid", () => {
354
+ const data = {
355
+ hasDisputesAndComplaints: { yesNo: "No" },
356
+ leadingToDisputesAndComplaints: {
357
+ yesNo: "Yes",
358
+ details: "Ongoing noise dispute",
359
+ },
360
+ };
361
+ expect(validator(data)).toBe(true);
362
+ });
363
+ });
364
+
365
+ describe("Listing and conservation validation", () => {
366
+ let validator;
367
+
368
+ beforeAll(() => {
369
+ validator = getSubschemaValidator(
370
+ "/propertyPack/listingAndConservation",
371
+ schemaId,
372
+ ["ta6ed6"]
373
+ );
374
+ });
375
+
376
+ test("valid data with all No answers passes", () => {
377
+ const data = {
378
+ isListed: { yesNo: "No" },
379
+ isConservationArea: { yesNo: "No" },
380
+ hasTreePreservationOrder: { yesNo: "No" },
381
+ };
382
+ expect(validator(data)).toBe(true);
383
+ });
384
+
385
+ test("isListed missing yesNo is invalid", () => {
386
+ const data = {
387
+ isListed: {},
388
+ isConservationArea: { yesNo: "No" },
389
+ hasTreePreservationOrder: { yesNo: "No" },
390
+ };
391
+ expect(validator(data)).toBe(false);
392
+ expect(
393
+ validator.errors.some((e) => e.message.includes("'yesNo'"))
394
+ ).toBe(true);
395
+ });
396
+
397
+ test("missing isConservationArea is invalid", () => {
398
+ const data = {
399
+ isListed: { yesNo: "No" },
400
+ hasTreePreservationOrder: { yesNo: "No" },
401
+ };
402
+ expect(validator(data)).toBe(false);
403
+ expect(
404
+ validator.errors.some((e) =>
405
+ e.message.includes("'isConservationArea'")
406
+ )
407
+ ).toBe(true);
408
+ });
409
+
410
+ test("missing hasTreePreservationOrder is invalid", () => {
411
+ const data = {
412
+ isListed: { yesNo: "No" },
413
+ isConservationArea: { yesNo: "No" },
414
+ };
415
+ expect(validator(data)).toBe(false);
416
+ expect(
417
+ validator.errors.some((e) =>
418
+ e.message.includes("'hasTreePreservationOrder'")
419
+ )
420
+ ).toBe(true);
421
+ });
422
+
423
+ test("Not known is valid for isListed and isConservationArea", () => {
424
+ const data = {
425
+ isListed: { yesNo: "Not known" },
426
+ isConservationArea: { yesNo: "Not known" },
427
+ hasTreePreservationOrder: { yesNo: "No" },
428
+ };
429
+ expect(validator(data)).toBe(true);
430
+ });
431
+ });
432
+
433
+ describe("Heating section validation", () => {
434
+ let validator;
435
+
436
+ beforeAll(() => {
437
+ validator = getSubschemaValidator(
438
+ "/propertyPack/heating",
439
+ schemaId,
440
+ ["ta6ed6"]
441
+ );
442
+ });
443
+
444
+ test("valid heating data passes", () => {
445
+ const data = {
446
+ heatingSystem: { heatingType: "None" },
447
+ otherHeatingFeatures: ["None"],
448
+ multipleHeatingSystems: "Not applicable",
449
+ };
450
+ expect(validator(data)).toBe(true);
451
+ });
452
+
453
+ test("missing heatingSystem is invalid", () => {
454
+ const data = {
455
+ otherHeatingFeatures: {},
456
+ multipleHeatingSystems: {},
457
+ };
458
+ expect(validator(data)).toBe(false);
459
+ expect(
460
+ validator.errors.some((e) => e.message.includes("'heatingSystem'"))
461
+ ).toBe(true);
462
+ });
463
+
464
+ test("missing otherHeatingFeatures is invalid", () => {
465
+ const data = {
466
+ heatingSystem: { heatingType: "None" },
467
+ multipleHeatingSystems: {},
468
+ };
469
+ expect(validator(data)).toBe(false);
470
+ expect(
471
+ validator.errors.some((e) =>
472
+ e.message.includes("'otherHeatingFeatures'")
473
+ )
474
+ ).toBe(true);
475
+ });
476
+ });
477
+
478
+ describe("Services crossing validation", () => {
479
+ let validator;
480
+
481
+ beforeAll(() => {
482
+ validator = getSubschemaValidator(
483
+ "/propertyPack/servicesCrossing",
484
+ schemaId,
485
+ ["ta6ed6"]
486
+ );
487
+ });
488
+
489
+ test("valid data with all No answers passes", () => {
490
+ const data = {
491
+ pipesWiresCablesDrainsToProperty: { yesNo: "No" },
492
+ pipesWiresCablesDrainsFromProperty: { yesNo: "No" },
493
+ formalOrInformalAgreements: { yesNo: "No" },
494
+ };
495
+ expect(validator(data)).toBe(true);
496
+ });
497
+
498
+ test("missing pipesWiresCablesDrainsToProperty is invalid", () => {
499
+ const data = {
500
+ pipesWiresCablesDrainsFromProperty: { yesNo: "No" },
501
+ formalOrInformalAgreements: { yesNo: "No" },
502
+ };
503
+ expect(validator(data)).toBe(false);
504
+ expect(
505
+ validator.errors.some((e) =>
506
+ e.message.includes("'pipesWiresCablesDrainsToProperty'")
507
+ )
508
+ ).toBe(true);
509
+ });
510
+
511
+ test("pipesWiresCablesDrainsToProperty Yes without details is invalid", () => {
512
+ const data = {
513
+ pipesWiresCablesDrainsToProperty: { yesNo: "Yes" },
514
+ pipesWiresCablesDrainsFromProperty: { yesNo: "No" },
515
+ formalOrInformalAgreements: { yesNo: "No" },
516
+ };
517
+ expect(validator(data)).toBe(false);
518
+ expect(
519
+ validator.errors.some((e) => e.message.includes("'details'"))
520
+ ).toBe(true);
521
+ });
522
+
523
+ test("pipesWiresCablesDrainsToProperty Yes with details is valid", () => {
524
+ const data = {
525
+ pipesWiresCablesDrainsToProperty: {
526
+ yesNo: "Yes",
527
+ details: "Shared drainage pipe runs from next door",
528
+ },
529
+ pipesWiresCablesDrainsFromProperty: { yesNo: "No" },
530
+ formalOrInformalAgreements: { yesNo: "No" },
531
+ };
532
+ expect(validator(data)).toBe(true);
533
+ });
534
+ });
535
+
536
+ describe("Electrical works validation", () => {
537
+ let validator;
538
+
539
+ beforeAll(() => {
540
+ validator = getSubschemaValidator(
541
+ "/propertyPack/electricalWorks",
542
+ schemaId,
543
+ ["ta6ed6"]
544
+ );
545
+ });
546
+
547
+ test("valid data with all No answers passes", () => {
548
+ const data = {
549
+ testedByQualifiedElectrician: { yesNo: "No" },
550
+ electricalWorkSince2005: { yesNo: "No" },
551
+ };
552
+ expect(validator(data)).toBe(true);
553
+ });
554
+
555
+ test("missing testedByQualifiedElectrician is invalid", () => {
556
+ const data = {
557
+ electricalWorkSince2005: { yesNo: "No" },
558
+ };
559
+ expect(validator(data)).toBe(false);
560
+ expect(
561
+ validator.errors.some((e) =>
562
+ e.message.includes("'testedByQualifiedElectrician'")
563
+ )
564
+ ).toBe(true);
565
+ });
566
+
567
+ test("missing electricalWorkSince2005 is invalid", () => {
568
+ const data = {
569
+ testedByQualifiedElectrician: { yesNo: "No" },
570
+ };
571
+ expect(validator(data)).toBe(false);
572
+ expect(
573
+ validator.errors.some((e) =>
574
+ e.message.includes("'electricalWorkSince2005'")
575
+ )
576
+ ).toBe(true);
577
+ });
578
+
579
+ test("testedByQualifiedElectrician Yes with year and attachments is valid", () => {
580
+ const data = {
581
+ testedByQualifiedElectrician: {
582
+ yesNo: "Yes",
583
+ yearTested: 2023,
584
+ attachments: "Attached",
585
+ },
586
+ electricalWorkSince2005: { yesNo: "No" },
587
+ };
588
+ expect(validator(data)).toBe(true);
589
+ });
590
+
591
+ test("electricalWorkSince2005 Yes with details and certificate is valid", () => {
592
+ const data = {
593
+ testedByQualifiedElectrician: { yesNo: "No" },
594
+ electricalWorkSince2005: {
595
+ yesNo: "Yes",
596
+ details: "Rewired ground floor",
597
+ yearWorkCarriedOut: 2020,
598
+ suppliedCertificate: {
599
+ certificateType: "Electrical Safety Certificate (BS7671)",
600
+ attachments: "Attached",
601
+ },
602
+ },
603
+ };
604
+ expect(validator(data)).toBe(true);
605
+ });
606
+ });
607
+
608
+ describe("Occupiers section validation", () => {
609
+ let validator;
610
+
611
+ beforeAll(() => {
612
+ validator = getSubschemaValidator(
613
+ "/propertyPack/occupiers",
614
+ schemaId,
615
+ ["ta6ed6"]
616
+ );
617
+ });
618
+
619
+ test("valid occupiers data passes", () => {
620
+ const data = {
621
+ sellerLivesAtProperty: { yesNo: "Yes" },
622
+ othersAged17OrOver: { hasOthersAged17OrOver: "No" },
623
+ };
624
+ expect(validator(data)).toBe(true);
625
+ });
626
+
627
+ test("missing sellerLivesAtProperty is invalid", () => {
628
+ const data = {
629
+ othersAged17OrOver: { hasOthersAged17OrOver: "No" },
630
+ };
631
+ expect(validator(data)).toBe(false);
632
+ expect(
633
+ validator.errors.some((e) =>
634
+ e.message.includes("'sellerLivesAtProperty'")
635
+ )
636
+ ).toBe(true);
637
+ });
638
+
639
+ test("missing othersAged17OrOver is invalid", () => {
640
+ const data = {
641
+ sellerLivesAtProperty: { yesNo: "Yes" },
642
+ };
643
+ expect(validator(data)).toBe(false);
644
+ expect(
645
+ validator.errors.some((e) =>
646
+ e.message.includes("'othersAged17OrOver'")
647
+ )
648
+ ).toBe(true);
649
+ });
650
+ });
651
+
652
+ describe("Completion and moving validation", () => {
653
+ let validator;
654
+
655
+ beforeAll(() => {
656
+ validator = getSubschemaValidator(
657
+ "/propertyPack/completionAndMoving",
658
+ schemaId,
659
+ ["ta6ed6"]
660
+ );
661
+ });
662
+
663
+ test("valid completion data passes", () => {
664
+ const data = {
665
+ sellerWillEnsure: {
666
+ removeRubbish: true,
667
+ replaceLightFittings: true,
668
+ takeReasonableCare: true,
669
+ leaveKeys: true,
670
+ },
671
+ sufficientToRepayAllMortgages: { yesNo: "Yes" },
672
+ };
673
+ expect(validator(data)).toBe(true);
674
+ });
675
+
676
+ test("missing sellerWillEnsure is invalid", () => {
677
+ const data = {
678
+ sufficientToRepayAllMortgages: { yesNo: "Yes" },
679
+ };
680
+ expect(validator(data)).toBe(false);
681
+ expect(
682
+ validator.errors.some((e) =>
683
+ e.message.includes("'sellerWillEnsure'")
684
+ )
685
+ ).toBe(true);
686
+ });
687
+
688
+ test("missing sufficientToRepayAllMortgages is invalid", () => {
689
+ const data = {
690
+ sellerWillEnsure: {
691
+ removeRubbish: true,
692
+ replaceLightFittings: true,
693
+ takeReasonableCare: true,
694
+ leaveKeys: true,
695
+ },
696
+ };
697
+ expect(validator(data)).toBe(false);
698
+ expect(
699
+ validator.errors.some((e) =>
700
+ e.message.includes("'sufficientToRepayAllMortgages'")
701
+ )
702
+ ).toBe(true);
703
+ });
704
+ });
705
+
706
+ describe("Connectivity validation", () => {
707
+ let validator;
708
+
709
+ beforeAll(() => {
710
+ validator = getSubschemaValidator(
711
+ "/propertyPack/connectivity",
712
+ schemaId,
713
+ ["ta6ed6"]
714
+ );
715
+ });
716
+
717
+ test("valid connectivity with telephone Yes passes", () => {
718
+ const data = {
719
+ telephone: { yesNo: "Yes", supplier: "BT" },
720
+ broadband: { typeOfConnection: "None" },
721
+ };
722
+ expect(validator(data)).toBe(true);
723
+ });
724
+
725
+ test("missing telephone is invalid", () => {
726
+ const data = { broadband: { typeOfConnection: "None" } };
727
+ expect(validator(data)).toBe(false);
728
+ expect(
729
+ validator.errors.some((e) => e.message.includes("'telephone'"))
730
+ ).toBe(true);
731
+ });
732
+
733
+ test("telephone No is valid", () => {
734
+ const data = {
735
+ telephone: { yesNo: "No" },
736
+ broadband: { typeOfConnection: "None" },
737
+ };
738
+ expect(validator(data)).toBe(true);
739
+ });
740
+ });
741
+
742
+ describe("Insurance section validation", () => {
743
+ let validator;
744
+
745
+ beforeAll(() => {
746
+ validator = getSubschemaValidator(
747
+ "/propertyPack/insurance",
748
+ schemaId,
749
+ ["ta6ed6"]
750
+ );
751
+ });
752
+
753
+ test("isInsured is a string enum in merged schema", () => {
754
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
755
+ const isInsured =
756
+ schema.properties.propertyPack.properties.insurance.properties
757
+ .isInsured;
758
+ expect(isInsured.type).toBe("string");
759
+ expect(isInsured.enum).toEqual(["Yes", "No"]);
760
+ });
761
+
762
+ test("isInsured Yes with insuranceClaims is valid", () => {
763
+ const data = {
764
+ isInsured: "Yes",
765
+ difficultiesObtainingInsurance: {
766
+ abnormalRiseInPremiums: { yesNo: "No" },
767
+ subjectToHighExcesses: { yesNo: "No" },
768
+ subjectToUnusualConditions: { yesNo: "No" },
769
+ refused: { yesNo: "No" },
770
+ },
771
+ insuranceClaims: { yesNo: "No" },
772
+ };
773
+ expect(validator(data)).toBe(true);
774
+ });
775
+
776
+ test("isInsured No with whoInsures is valid", () => {
777
+ const data = {
778
+ isInsured: "No",
779
+ difficultiesObtainingInsurance: {
780
+ abnormalRiseInPremiums: { yesNo: "No" },
781
+ subjectToHighExcesses: { yesNo: "No" },
782
+ subjectToUnusualConditions: { yesNo: "No" },
783
+ refused: { yesNo: "No" },
784
+ },
785
+ whoInsures: "Landlord insures the building",
786
+ };
787
+ expect(validator(data)).toBe(true);
788
+ });
789
+
790
+ test("isInsured Yes without insuranceClaims is invalid", () => {
791
+ const data = {
792
+ isInsured: "Yes",
793
+ difficultiesObtainingInsurance: {
794
+ abnormalRiseInPremiums: { yesNo: "No" },
795
+ subjectToHighExcesses: { yesNo: "No" },
796
+ subjectToUnusualConditions: { yesNo: "No" },
797
+ refused: { yesNo: "No" },
798
+ },
799
+ };
800
+ expect(validator(data)).toBe(false);
801
+ expect(
802
+ validator.errors.some((e) => e.message.includes("'insuranceClaims'"))
803
+ ).toBe(true);
804
+ });
805
+
806
+ test("isInsured No without whoInsures is invalid", () => {
807
+ const data = {
808
+ isInsured: "No",
809
+ difficultiesObtainingInsurance: { yesNo: "No" },
810
+ insuranceClaims: { yesNo: "No" },
811
+ };
812
+ expect(validator(data)).toBe(false);
813
+ expect(
814
+ validator.errors.some((e) => e.message.includes("'whoInsures'"))
815
+ ).toBe(true);
816
+ });
817
+
818
+ test("missing isInsured is invalid", () => {
819
+ const data = {
820
+ difficultiesObtainingInsurance: { yesNo: "No" },
821
+ insuranceClaims: { yesNo: "No" },
822
+ };
823
+ expect(validator(data)).toBe(false);
824
+ expect(
825
+ validator.errors.some((e) => e.message.includes("'isInsured'"))
826
+ ).toBe(true);
827
+ });
828
+
829
+ test("missing difficultiesObtainingInsurance is invalid", () => {
830
+ const data = {
831
+ isInsured: "Yes",
832
+ insuranceClaims: { yesNo: "No" },
833
+ };
834
+ expect(validator(data)).toBe(false);
835
+ expect(
836
+ validator.errors.some((e) =>
837
+ e.message.includes("'difficultiesObtainingInsurance'")
838
+ )
839
+ ).toBe(true);
840
+ });
841
+ });
842
+
843
+ describe("Confirmation of accuracy validation", () => {
844
+ let validator;
845
+
846
+ beforeAll(() => {
847
+ validator = getSubschemaValidator(
848
+ "/propertyPack/confirmationOfAccuracyByOwners",
849
+ schemaId,
850
+ ["ta6ed6"]
851
+ );
852
+ });
853
+
854
+ test("valid confirmation data passes", () => {
855
+ const data = {
856
+ confirmInformationIsAccurate: true,
857
+ };
858
+ expect(validator(data)).toBe(true);
859
+ });
860
+
861
+ test("missing confirmInformationIsAccurate is invalid", () => {
862
+ const data = {};
863
+ expect(validator(data)).toBe(false);
864
+ expect(
865
+ validator.errors.some((e) =>
866
+ e.message.includes("'confirmInformationIsAccurate'")
867
+ )
868
+ ).toBe(true);
869
+ });
870
+ });
871
+
872
+ describe("Rights and informal arrangements validation", () => {
873
+ let validator;
874
+
875
+ beforeAll(() => {
876
+ validator = getSubschemaValidator(
877
+ "/propertyPack/rightsAndInformalArrangements",
878
+ schemaId,
879
+ ["ta6ed6"]
880
+ );
881
+ });
882
+
883
+ test("valid data with all No answers passes", () => {
884
+ const data = {
885
+ neighbouringLandRights: { yesNo: "No" },
886
+ sharedContributions: { yesNo: "No" },
887
+ rightsOrArrangements: {
888
+ publicRightOfWay: { yesNo: "No" },
889
+ rightsOfLight: { yesNo: "No" },
890
+ rightsOfSupport: { yesNo: "No" },
891
+ rightsCreatedThroughCustom: { yesNo: "No" },
892
+ minesAndMinerals: { yesNo: "No" },
893
+ churchChancel: { yesNo: "No" },
894
+ rightsToTakeFromLand: { yesNo: "No" },
895
+ otherRights: { yesNo: "No" },
896
+ disagreementOrComplaint: { yesNo: "No" },
897
+ },
898
+ rightsOverProperty: { yesNo: "No" },
899
+ askedOthersToContribute: { yesNo: "No" },
900
+ };
901
+ expect(validator(data)).toBe(true);
902
+ });
903
+
904
+ test("missing neighbouringLandRights is invalid", () => {
905
+ const data = {
906
+ sharedContributions: { yesNo: "No" },
907
+ disagreementAboutRightsYouExercise: { yesNo: "No" },
908
+ rightsOverProperty: { yesNo: "No" },
909
+ askedOthersToContribute: { yesNo: "No" },
910
+ accessRestrictionAttempts: { yesNo: "No" },
911
+ };
912
+ expect(validator(data)).toBe(false);
913
+ expect(
914
+ validator.errors.some((e) =>
915
+ e.message.includes("'neighbouringLandRights'")
916
+ )
917
+ ).toBe(true);
918
+ });
919
+
920
+ test("missing all required fields is invalid", () => {
921
+ const data = {};
922
+ expect(validator(data)).toBe(false);
923
+ const errorMessages = validator.errors.map((e) => e.message);
924
+ expect(errorMessages).toContain(
925
+ "must have required property 'neighbouringLandRights'"
926
+ );
927
+ expect(errorMessages).toContain(
928
+ "must have required property 'sharedContributions'"
929
+ );
930
+ });
931
+ });
932
+
933
+ describe("Specialist issues validation", () => {
934
+ let validator;
935
+
936
+ beforeAll(() => {
937
+ validator = getSubschemaValidator(
938
+ "/propertyPack/specialistIssues",
939
+ schemaId,
940
+ ["ta6ed6"]
941
+ );
942
+ });
943
+
944
+ test("valid specialist issues data passes", () => {
945
+ const data = {
946
+ dryRotEtcTreatment: { yesNo: "No" },
947
+ containsAsbestos: { yesNo: "No" },
948
+ japaneseKnotweed: {
949
+ yesNo: "No",
950
+ knotweedSurveyCarriedOut: { yesNo: "No" },
951
+ },
952
+ subsidenceOrStructuralFault: { yesNo: "No" },
953
+ ongoingHealthOrSafetyIssue: { yesNo: "No" },
954
+ };
955
+ expect(validator(data)).toBe(true);
956
+ });
957
+
958
+ test("missing japaneseKnotweed is invalid (required by overlay)", () => {
959
+ const data = {};
960
+ expect(validator(data)).toBe(false);
961
+ expect(
962
+ validator.errors.some((e) =>
963
+ e.message.includes("'japaneseKnotweed'")
964
+ )
965
+ ).toBe(true);
966
+ });
967
+
968
+ test("japaneseKnotweed missing knotweedSurveyCarriedOut is invalid", () => {
969
+ const data = {
970
+ japaneseKnotweed: { yesNo: "No" },
971
+ };
972
+ expect(validator(data)).toBe(false);
973
+ expect(
974
+ validator.errors.some((e) =>
975
+ e.message.includes("'knotweedSurveyCarriedOut'")
976
+ )
977
+ ).toBe(true);
978
+ });
979
+
980
+ test("knotweedSurveyCarriedOut No is valid without attachments", () => {
981
+ const data = {
982
+ japaneseKnotweed: {
983
+ yesNo: "No",
984
+ knotweedSurveyCarriedOut: { yesNo: "No" },
985
+ },
986
+ };
987
+ expect(validator(data)).toBe(true);
988
+ });
989
+
990
+ test("knotweedSurveyCarriedOut Not known is valid without attachments", () => {
991
+ const data = {
992
+ japaneseKnotweed: {
993
+ yesNo: "No",
994
+ knotweedSurveyCarriedOut: { yesNo: "Not known" },
995
+ },
996
+ };
997
+ expect(validator(data)).toBe(true);
998
+ });
999
+
1000
+ test("knotweedSurveyCarriedOut Yes without attachments is invalid", () => {
1001
+ const data = {
1002
+ japaneseKnotweed: {
1003
+ yesNo: "No",
1004
+ knotweedSurveyCarriedOut: { yesNo: "Yes" },
1005
+ },
1006
+ };
1007
+ expect(validator(data)).toBe(false);
1008
+ expect(
1009
+ validator.errors.some((e) =>
1010
+ e.message.includes("'attachments'")
1011
+ )
1012
+ ).toBe(true);
1013
+ });
1014
+
1015
+ test("knotweedSurveyCarriedOut Yes with attachments is valid", () => {
1016
+ const data = {
1017
+ japaneseKnotweed: {
1018
+ yesNo: "No",
1019
+ knotweedSurveyCarriedOut: {
1020
+ yesNo: "Yes",
1021
+ attachments: "Attached",
1022
+ },
1023
+ },
1024
+ };
1025
+ expect(validator(data)).toBe(true);
1026
+ });
1027
+
1028
+ test("knotweedSurveyCarriedOut Yes with To follow attachments is valid", () => {
1029
+ const data = {
1030
+ japaneseKnotweed: {
1031
+ yesNo: "No",
1032
+ knotweedSurveyCarriedOut: {
1033
+ yesNo: "Yes",
1034
+ attachments: "To follow",
1035
+ },
1036
+ },
1037
+ };
1038
+ expect(validator(data)).toBe(true);
1039
+ });
1040
+
1041
+ test("japaneseKnotweed Yes with management plan and survey is valid", () => {
1042
+ const data = {
1043
+ japaneseKnotweed: {
1044
+ yesNo: "Yes",
1045
+ managementPlanInPlace: "Yes",
1046
+ attachments: "Attached",
1047
+ knotweedSurveyCarriedOut: {
1048
+ yesNo: "Yes",
1049
+ attachments: "Attached",
1050
+ },
1051
+ },
1052
+ };
1053
+ expect(validator(data)).toBe(true);
1054
+ });
1055
+
1056
+ test("japaneseKnotweed Yes with management plan No does not require attachments", () => {
1057
+ const data = {
1058
+ japaneseKnotweed: {
1059
+ yesNo: "Yes",
1060
+ managementPlanInPlace: "No",
1061
+ knotweedSurveyCarriedOut: { yesNo: "No" },
1062
+ },
1063
+ };
1064
+ expect(validator(data)).toBe(true);
1065
+ });
1066
+
1067
+ test("japaneseKnotweed Yes with management plan Yes without attachments is invalid", () => {
1068
+ const data = {
1069
+ japaneseKnotweed: {
1070
+ yesNo: "Yes",
1071
+ managementPlanInPlace: "Yes",
1072
+ knotweedSurveyCarriedOut: { yesNo: "No" },
1073
+ },
1074
+ };
1075
+ expect(validator(data)).toBe(false);
1076
+ expect(
1077
+ validator.errors.some((e) =>
1078
+ e.message.includes("'attachments'")
1079
+ )
1080
+ ).toBe(true);
1081
+ });
1082
+ });
1083
+
1084
+ describe("Alterations section validation", () => {
1085
+ let validator;
1086
+
1087
+ beforeAll(() => {
1088
+ validator = getSubschemaValidator(
1089
+ "/propertyPack/alterationsAndChanges",
1090
+ schemaId,
1091
+ ["ta6ed6"]
1092
+ );
1093
+ });
1094
+
1095
+ test("all No answers passes", () => {
1096
+ const data = {
1097
+ windowReplacementsSince2002: { yesNo: "No" },
1098
+ hasAddedConservatory: { yesNo: "No" },
1099
+ extension: { yesNo: "No" },
1100
+ loftConversion: { yesNo: "No" },
1101
+ garageConversion: { yesNo: "No" },
1102
+ removalOfInternalWalls: { yesNo: "No" },
1103
+ removalOfChimneyBreast: { yesNo: "No" },
1104
+ insulation: { yesNo: "No" },
1105
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1106
+ changeOfUse: { yesNo: "No" },
1107
+ nonResidentialPurposes: { yesNo: "No" },
1108
+ planningPermissionBreaches: { yesNo: "No" },
1109
+ unresolvedPlanningIssues: { yesNo: "No" },
1110
+ };
1111
+ expect(validator(data)).toBe(true);
1112
+ });
1113
+
1114
+ test("missing extension is invalid", () => {
1115
+ const data = {
1116
+ windowReplacementsSince2002: { yesNo: "No" },
1117
+ hasAddedConservatory: { yesNo: "No" },
1118
+ loftConversion: { yesNo: "No" },
1119
+ garageConversion: { yesNo: "No" },
1120
+ removalOfInternalWalls: { yesNo: "No" },
1121
+ removalOfChimneyBreast: { yesNo: "No" },
1122
+ insulation: { yesNo: "No" },
1123
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1124
+ changeOfUse: { yesNo: "No" },
1125
+ nonResidentialPurposes: { yesNo: "No" },
1126
+ planningPermissionBreaches: { yesNo: "No" },
1127
+ unresolvedPlanningIssues: { yesNo: "No" },
1128
+ };
1129
+ expect(validator(data)).toBe(false);
1130
+ expect(
1131
+ validator.errors.some((e) => e.message.includes("'extension'"))
1132
+ ).toBe(true);
1133
+ });
1134
+
1135
+ test("extension Yes without details is invalid", () => {
1136
+ const data = {
1137
+ windowReplacementsSince2002: { yesNo: "No" },
1138
+ hasAddedConservatory: { yesNo: "No" },
1139
+ extension: { yesNo: "Yes" },
1140
+ loftConversion: { yesNo: "No" },
1141
+ garageConversion: { yesNo: "No" },
1142
+ removalOfInternalWalls: { yesNo: "No" },
1143
+ removalOfChimneyBreast: { yesNo: "No" },
1144
+ insulation: { yesNo: "No" },
1145
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1146
+ changeOfUse: { yesNo: "No" },
1147
+ nonResidentialPurposes: { yesNo: "No" },
1148
+ planningPermissionBreaches: { yesNo: "No" },
1149
+ unresolvedPlanningIssues: { yesNo: "No" },
1150
+ };
1151
+ expect(validator(data)).toBe(false);
1152
+ expect(
1153
+ validator.errors.some((e) => e.message.includes("'details'"))
1154
+ ).toBe(true);
1155
+ });
1156
+
1157
+ test("extension Yes with details is valid", () => {
1158
+ const data = {
1159
+ windowReplacementsSince2002: { yesNo: "No" },
1160
+ hasAddedConservatory: { yesNo: "No" },
1161
+ extension: {
1162
+ yesNo: "Yes",
1163
+ details: "Single storey rear extension built 2018",
1164
+ },
1165
+ loftConversion: { yesNo: "No" },
1166
+ garageConversion: { yesNo: "No" },
1167
+ removalOfInternalWalls: { yesNo: "No" },
1168
+ removalOfChimneyBreast: { yesNo: "No" },
1169
+ insulation: { yesNo: "No" },
1170
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1171
+ changeOfUse: { yesNo: "No" },
1172
+ nonResidentialPurposes: { yesNo: "No" },
1173
+ planningPermissionBreaches: { yesNo: "No" },
1174
+ unresolvedPlanningIssues: { yesNo: "No" },
1175
+ };
1176
+ expect(validator(data)).toBe(true);
1177
+ });
1178
+
1179
+ test("extension Yes with full details including nested discriminators is valid", () => {
1180
+ const data = {
1181
+ windowReplacementsSince2002: { yesNo: "No" },
1182
+ hasAddedConservatory: { yesNo: "No" },
1183
+ extension: {
1184
+ yesNo: "Yes",
1185
+ details: "Single storey rear extension built 2018",
1186
+ workCompleted: { yesNo: "Yes" },
1187
+ documents: "Attached",
1188
+ planningPermission: { yesNo: "Yes", attachments: "Attached" },
1189
+ buildingRegApproval: { yesNo: "Yes", attachments: "Attached" },
1190
+ },
1191
+ loftConversion: { yesNo: "No" },
1192
+ garageConversion: { yesNo: "No" },
1193
+ removalOfInternalWalls: { yesNo: "No" },
1194
+ removalOfChimneyBreast: { yesNo: "No" },
1195
+ insulation: { yesNo: "No" },
1196
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1197
+ changeOfUse: { yesNo: "No" },
1198
+ nonResidentialPurposes: { yesNo: "No" },
1199
+ planningPermissionBreaches: { yesNo: "No" },
1200
+ unresolvedPlanningIssues: { yesNo: "No" },
1201
+ };
1202
+ expect(validator(data)).toBe(true);
1203
+ });
1204
+
1205
+ test("workCompleted No without details is invalid", () => {
1206
+ const data = {
1207
+ windowReplacementsSince2002: { yesNo: "No" },
1208
+ hasAddedConservatory: { yesNo: "No" },
1209
+ extension: {
1210
+ yesNo: "Yes",
1211
+ details: "Extension built 2020",
1212
+ workCompleted: { yesNo: "No" },
1213
+ },
1214
+ loftConversion: { yesNo: "No" },
1215
+ garageConversion: { yesNo: "No" },
1216
+ removalOfInternalWalls: { yesNo: "No" },
1217
+ removalOfChimneyBreast: { yesNo: "No" },
1218
+ insulation: { yesNo: "No" },
1219
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1220
+ changeOfUse: { yesNo: "No" },
1221
+ nonResidentialPurposes: { yesNo: "No" },
1222
+ planningPermissionBreaches: { yesNo: "No" },
1223
+ unresolvedPlanningIssues: { yesNo: "No" },
1224
+ };
1225
+ expect(validator(data)).toBe(false);
1226
+ expect(
1227
+ validator.errors.some((e) => e.message.includes("'details'"))
1228
+ ).toBe(true);
1229
+ });
1230
+
1231
+ test("workCompleted No with details is valid", () => {
1232
+ const data = {
1233
+ windowReplacementsSince2002: { yesNo: "No" },
1234
+ hasAddedConservatory: { yesNo: "No" },
1235
+ extension: {
1236
+ yesNo: "Yes",
1237
+ details: "Extension built 2020",
1238
+ workCompleted: {
1239
+ yesNo: "No",
1240
+ details: "Roof tiles still to be completed",
1241
+ },
1242
+ },
1243
+ loftConversion: { yesNo: "No" },
1244
+ garageConversion: { yesNo: "No" },
1245
+ removalOfInternalWalls: { yesNo: "No" },
1246
+ removalOfChimneyBreast: { yesNo: "No" },
1247
+ insulation: { yesNo: "No" },
1248
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1249
+ changeOfUse: { yesNo: "No" },
1250
+ nonResidentialPurposes: { yesNo: "No" },
1251
+ planningPermissionBreaches: { yesNo: "No" },
1252
+ unresolvedPlanningIssues: { yesNo: "No" },
1253
+ };
1254
+ expect(validator(data)).toBe(true);
1255
+ });
1256
+
1257
+ test("windowReplacementsSince2002 Yes with details is valid", () => {
1258
+ const data = {
1259
+ windowReplacementsSince2002: {
1260
+ yesNo: "Yes",
1261
+ details: "All windows replaced with double glazing in 2015",
1262
+ },
1263
+ hasAddedConservatory: { yesNo: "No" },
1264
+ extension: { yesNo: "No" },
1265
+ loftConversion: { yesNo: "No" },
1266
+ garageConversion: { yesNo: "No" },
1267
+ removalOfInternalWalls: { yesNo: "No" },
1268
+ removalOfChimneyBreast: { yesNo: "No" },
1269
+ insulation: { yesNo: "No" },
1270
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1271
+ changeOfUse: { yesNo: "No" },
1272
+ nonResidentialPurposes: { yesNo: "No" },
1273
+ planningPermissionBreaches: { yesNo: "No" },
1274
+ unresolvedPlanningIssues: { yesNo: "No" },
1275
+ };
1276
+ expect(validator(data)).toBe(true);
1277
+ });
1278
+
1279
+ test("hasAddedConservatory Yes without details is invalid", () => {
1280
+ const data = {
1281
+ windowReplacementsSince2002: { yesNo: "No" },
1282
+ hasAddedConservatory: { yesNo: "Yes" },
1283
+ extension: { yesNo: "No" },
1284
+ loftConversion: { yesNo: "No" },
1285
+ garageConversion: { yesNo: "No" },
1286
+ removalOfInternalWalls: { yesNo: "No" },
1287
+ removalOfChimneyBreast: { yesNo: "No" },
1288
+ insulation: { yesNo: "No" },
1289
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1290
+ changeOfUse: { yesNo: "No" },
1291
+ nonResidentialPurposes: { yesNo: "No" },
1292
+ planningPermissionBreaches: { yesNo: "No" },
1293
+ unresolvedPlanningIssues: { yesNo: "No" },
1294
+ };
1295
+ expect(validator(data)).toBe(false);
1296
+ expect(
1297
+ validator.errors.some((e) => e.message.includes("'details'"))
1298
+ ).toBe(true);
1299
+ });
1300
+
1301
+ test("multiple alteration types with Yes answers all valid", () => {
1302
+ const data = {
1303
+ windowReplacementsSince2002: {
1304
+ yesNo: "Yes",
1305
+ details: "Double glazing throughout",
1306
+ },
1307
+ hasAddedConservatory: {
1308
+ yesNo: "Yes",
1309
+ details: "Rear conservatory 2019",
1310
+ },
1311
+ extension: { yesNo: "No" },
1312
+ loftConversion: {
1313
+ yesNo: "Yes",
1314
+ details: "Loft converted to bedroom 2017",
1315
+ workCompleted: { yesNo: "Yes" },
1316
+ planningPermission: {
1317
+ yesNo: "Not required",
1318
+ details: "Permitted development rights applied",
1319
+ },
1320
+ buildingRegApproval: {
1321
+ yesNo: "Yes",
1322
+ attachments: "Attached",
1323
+ },
1324
+ },
1325
+ garageConversion: { yesNo: "No" },
1326
+ removalOfInternalWalls: { yesNo: "No" },
1327
+ removalOfChimneyBreast: { yesNo: "No" },
1328
+ insulation: { yesNo: "No" },
1329
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1330
+ changeOfUse: { yesNo: "No" },
1331
+ nonResidentialPurposes: { yesNo: "No" },
1332
+ planningPermissionBreaches: { yesNo: "No" },
1333
+ unresolvedPlanningIssues: { yesNo: "No" },
1334
+ };
1335
+ expect(validator(data)).toBe(true);
1336
+ });
1337
+
1338
+ test("extension planningPermission Yes without attachments is invalid", () => {
1339
+ const data = {
1340
+ windowReplacementsSince2002: { yesNo: "No" },
1341
+ hasAddedConservatory: { yesNo: "No" },
1342
+ extension: {
1343
+ yesNo: "Yes",
1344
+ details: "Rear extension 2020",
1345
+ planningPermission: { yesNo: "Yes" },
1346
+ buildingRegApproval: { yesNo: "Yes", attachments: "Attached" },
1347
+ },
1348
+ loftConversion: { yesNo: "No" },
1349
+ garageConversion: { yesNo: "No" },
1350
+ removalOfInternalWalls: { yesNo: "No" },
1351
+ removalOfChimneyBreast: { yesNo: "No" },
1352
+ insulation: { yesNo: "No" },
1353
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1354
+ changeOfUse: { yesNo: "No" },
1355
+ nonResidentialPurposes: { yesNo: "No" },
1356
+ planningPermissionBreaches: { yesNo: "No" },
1357
+ unresolvedPlanningIssues: { yesNo: "No" },
1358
+ };
1359
+ expect(validator(data)).toBe(false);
1360
+ expect(
1361
+ validator.errors.some((e) => e.message.includes("'attachments'"))
1362
+ ).toBe(true);
1363
+ });
1364
+
1365
+ test("extension planningPermission Yes with attachments is valid", () => {
1366
+ const data = {
1367
+ windowReplacementsSince2002: { yesNo: "No" },
1368
+ hasAddedConservatory: { yesNo: "No" },
1369
+ extension: {
1370
+ yesNo: "Yes",
1371
+ details: "Rear extension 2020",
1372
+ planningPermission: { yesNo: "Yes", attachments: "Attached" },
1373
+ buildingRegApproval: { yesNo: "Yes", attachments: "Attached" },
1374
+ },
1375
+ loftConversion: { yesNo: "No" },
1376
+ garageConversion: { yesNo: "No" },
1377
+ removalOfInternalWalls: { yesNo: "No" },
1378
+ removalOfChimneyBreast: { yesNo: "No" },
1379
+ insulation: { yesNo: "No" },
1380
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1381
+ changeOfUse: { yesNo: "No" },
1382
+ nonResidentialPurposes: { yesNo: "No" },
1383
+ planningPermissionBreaches: { yesNo: "No" },
1384
+ unresolvedPlanningIssues: { yesNo: "No" },
1385
+ };
1386
+ expect(validator(data)).toBe(true);
1387
+ });
1388
+
1389
+ test("extension planningPermission No without details is invalid", () => {
1390
+ const data = {
1391
+ windowReplacementsSince2002: { yesNo: "No" },
1392
+ hasAddedConservatory: { yesNo: "No" },
1393
+ extension: {
1394
+ yesNo: "Yes",
1395
+ details: "Rear extension 2020",
1396
+ planningPermission: { yesNo: "No" },
1397
+ buildingRegApproval: { yesNo: "No", details: "Exempt from regs" },
1398
+ },
1399
+ loftConversion: { yesNo: "No" },
1400
+ garageConversion: { yesNo: "No" },
1401
+ removalOfInternalWalls: { yesNo: "No" },
1402
+ removalOfChimneyBreast: { yesNo: "No" },
1403
+ insulation: { yesNo: "No" },
1404
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1405
+ changeOfUse: { yesNo: "No" },
1406
+ nonResidentialPurposes: { yesNo: "No" },
1407
+ planningPermissionBreaches: { yesNo: "No" },
1408
+ unresolvedPlanningIssues: { yesNo: "No" },
1409
+ };
1410
+ expect(validator(data)).toBe(false);
1411
+ expect(
1412
+ validator.errors.some((e) => e.message.includes("'details'"))
1413
+ ).toBe(true);
1414
+ });
1415
+
1416
+ test("extension planningPermission No with details is valid", () => {
1417
+ const data = {
1418
+ windowReplacementsSince2002: { yesNo: "No" },
1419
+ hasAddedConservatory: { yesNo: "No" },
1420
+ extension: {
1421
+ yesNo: "Yes",
1422
+ details: "Rear extension 2020",
1423
+ planningPermission: {
1424
+ yesNo: "No",
1425
+ details: "Permitted development rights applied",
1426
+ },
1427
+ buildingRegApproval: {
1428
+ yesNo: "No",
1429
+ details: "Exempt from building regulations",
1430
+ },
1431
+ },
1432
+ loftConversion: { yesNo: "No" },
1433
+ garageConversion: { yesNo: "No" },
1434
+ removalOfInternalWalls: { yesNo: "No" },
1435
+ removalOfChimneyBreast: { yesNo: "No" },
1436
+ insulation: { yesNo: "No" },
1437
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1438
+ changeOfUse: { yesNo: "No" },
1439
+ nonResidentialPurposes: { yesNo: "No" },
1440
+ planningPermissionBreaches: { yesNo: "No" },
1441
+ unresolvedPlanningIssues: { yesNo: "No" },
1442
+ };
1443
+ expect(validator(data)).toBe(true);
1444
+ });
1445
+
1446
+ test("extension planningPermission Not required without details is invalid", () => {
1447
+ const data = {
1448
+ windowReplacementsSince2002: { yesNo: "No" },
1449
+ hasAddedConservatory: { yesNo: "No" },
1450
+ extension: {
1451
+ yesNo: "Yes",
1452
+ details: "Rear extension 2020",
1453
+ planningPermission: { yesNo: "Not required" },
1454
+ buildingRegApproval: {
1455
+ yesNo: "Not required",
1456
+ details: "Work exempt",
1457
+ },
1458
+ },
1459
+ loftConversion: { yesNo: "No" },
1460
+ garageConversion: { yesNo: "No" },
1461
+ removalOfInternalWalls: { yesNo: "No" },
1462
+ removalOfChimneyBreast: { yesNo: "No" },
1463
+ insulation: { yesNo: "No" },
1464
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1465
+ changeOfUse: { yesNo: "No" },
1466
+ nonResidentialPurposes: { yesNo: "No" },
1467
+ planningPermissionBreaches: { yesNo: "No" },
1468
+ unresolvedPlanningIssues: { yesNo: "No" },
1469
+ };
1470
+ expect(validator(data)).toBe(false);
1471
+ expect(
1472
+ validator.errors.some((e) => e.message.includes("'details'"))
1473
+ ).toBe(true);
1474
+ });
1475
+
1476
+ test("extension buildingRegApproval Yes without attachments is invalid", () => {
1477
+ const data = {
1478
+ windowReplacementsSince2002: { yesNo: "No" },
1479
+ hasAddedConservatory: { yesNo: "No" },
1480
+ extension: {
1481
+ yesNo: "Yes",
1482
+ details: "Rear extension 2020",
1483
+ planningPermission: { yesNo: "Yes", attachments: "Attached" },
1484
+ buildingRegApproval: { yesNo: "Yes" },
1485
+ },
1486
+ loftConversion: { yesNo: "No" },
1487
+ garageConversion: { yesNo: "No" },
1488
+ removalOfInternalWalls: { yesNo: "No" },
1489
+ removalOfChimneyBreast: { yesNo: "No" },
1490
+ insulation: { yesNo: "No" },
1491
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1492
+ changeOfUse: { yesNo: "No" },
1493
+ nonResidentialPurposes: { yesNo: "No" },
1494
+ planningPermissionBreaches: { yesNo: "No" },
1495
+ unresolvedPlanningIssues: { yesNo: "No" },
1496
+ };
1497
+ expect(validator(data)).toBe(false);
1498
+ expect(
1499
+ validator.errors.some((e) => e.message.includes("'attachments'"))
1500
+ ).toBe(true);
1501
+ });
1502
+
1503
+ test("extension buildingRegApproval No without details is invalid", () => {
1504
+ const data = {
1505
+ windowReplacementsSince2002: { yesNo: "No" },
1506
+ hasAddedConservatory: { yesNo: "No" },
1507
+ extension: {
1508
+ yesNo: "Yes",
1509
+ details: "Rear extension 2020",
1510
+ planningPermission: {
1511
+ yesNo: "No",
1512
+ details: "Permitted development",
1513
+ },
1514
+ buildingRegApproval: { yesNo: "No" },
1515
+ },
1516
+ loftConversion: { yesNo: "No" },
1517
+ garageConversion: { yesNo: "No" },
1518
+ removalOfInternalWalls: { yesNo: "No" },
1519
+ removalOfChimneyBreast: { yesNo: "No" },
1520
+ insulation: { yesNo: "No" },
1521
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1522
+ changeOfUse: { yesNo: "No" },
1523
+ nonResidentialPurposes: { yesNo: "No" },
1524
+ planningPermissionBreaches: { yesNo: "No" },
1525
+ unresolvedPlanningIssues: { yesNo: "No" },
1526
+ };
1527
+ expect(validator(data)).toBe(false);
1528
+ expect(
1529
+ validator.errors.some((e) => e.message.includes("'details'"))
1530
+ ).toBe(true);
1531
+ });
1532
+
1533
+ test("extension buildingRegApproval Not required with details is valid", () => {
1534
+ const data = {
1535
+ windowReplacementsSince2002: { yesNo: "No" },
1536
+ hasAddedConservatory: { yesNo: "No" },
1537
+ extension: {
1538
+ yesNo: "Yes",
1539
+ details: "Rear extension 2020",
1540
+ planningPermission: {
1541
+ yesNo: "Not required",
1542
+ details: "Permitted development rights applied",
1543
+ },
1544
+ buildingRegApproval: {
1545
+ yesNo: "Not required",
1546
+ details: "Work exempt from building regulations",
1547
+ },
1548
+ },
1549
+ loftConversion: { yesNo: "No" },
1550
+ garageConversion: { yesNo: "No" },
1551
+ removalOfInternalWalls: { yesNo: "No" },
1552
+ removalOfChimneyBreast: { yesNo: "No" },
1553
+ insulation: { yesNo: "No" },
1554
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
1555
+ changeOfUse: { yesNo: "No" },
1556
+ nonResidentialPurposes: { yesNo: "No" },
1557
+ planningPermissionBreaches: { yesNo: "No" },
1558
+ unresolvedPlanningIssues: { yesNo: "No" },
1559
+ };
1560
+ expect(validator(data)).toBe(true);
1561
+ });
1562
+ });
1563
+
1564
+ describe("Water and drainage section validation", () => {
1565
+ let validator;
1566
+
1567
+ beforeAll(() => {
1568
+ validator = getSubschemaValidator(
1569
+ "/propertyPack/waterAndDrainage",
1570
+ schemaId,
1571
+ ["ta6ed6"]
1572
+ );
1573
+ });
1574
+
1575
+ test("valid water and drainage with mains passes", () => {
1576
+ const data = {
1577
+ water: {
1578
+ mainsWater: {
1579
+ yesNo: "Yes",
1580
+ supplier: "Thames Water",
1581
+ stopcock: { location: "Under the stairs" },
1582
+ waterMeter: { isSupplyMetered: "No" },
1583
+ },
1584
+ },
1585
+ drainage: {
1586
+ mainsFoulDrainage: { yesNo: "Yes", supplier: "Thames Water" },
1587
+ mainsSurfaceWaterDrainage: { yesNo: "Yes" },
1588
+ offMainsDrainageSystem: {},
1589
+ },
1590
+ };
1591
+ expect(validator(data)).toBe(true);
1592
+ });
1593
+
1594
+ test("missing drainage is invalid", () => {
1595
+ const data = {
1596
+ water: {
1597
+ mainsWater: {
1598
+ yesNo: "Yes",
1599
+ supplier: "Thames Water",
1600
+ stopcock: { location: "Under the stairs" },
1601
+ waterMeter: { isSupplyMetered: "No" },
1602
+ },
1603
+ },
1604
+ };
1605
+ expect(validator(data)).toBe(false);
1606
+ expect(
1607
+ validator.errors.some((e) => e.message.includes("'drainage'"))
1608
+ ).toBe(true);
1609
+ });
1610
+
1611
+ test("missing water is invalid", () => {
1612
+ const data = {
1613
+ drainage: {
1614
+ mainsFoulDrainage: { yesNo: "Yes", supplier: "Thames Water" },
1615
+ mainsSurfaceWaterDrainage: { yesNo: "Yes" },
1616
+ offMainsDrainageSystem: {},
1617
+ },
1618
+ };
1619
+ expect(validator(data)).toBe(false);
1620
+ expect(
1621
+ validator.errors.some((e) => e.message.includes("'water'"))
1622
+ ).toBe(true);
1623
+ });
1624
+
1625
+ test("mainsFoulDrainage is an object with discriminator in merged schema", () => {
1626
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
1627
+ const mfd =
1628
+ schema.properties.propertyPack.properties.waterAndDrainage.properties
1629
+ .drainage.properties.mainsFoulDrainage;
1630
+ expect(mfd.type).toBe("object");
1631
+ expect(mfd.oneOf).toBeDefined();
1632
+ expect(mfd.discriminator).toBeDefined();
1633
+ });
1634
+
1635
+ test("plant registered yesNo carries TA6 edition 6 ref", () => {
1636
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
1637
+ const plantRegistered =
1638
+ schema.properties.propertyPack.properties.waterAndDrainage.properties
1639
+ .drainage.properties.mainsFoulDrainage.oneOf[2].properties
1640
+ .offMainsDrainageSystem.properties.plantRegistered;
1641
+ expect(plantRegistered.ta6ed6Ref).toBe("11.7j");
1642
+ expect(plantRegistered.properties.yesNo.ta6ed6Ref).toBe("11.7j");
1643
+ });
1644
+
1645
+ test("mainsFoulDrainage No with object is accepted", () => {
1646
+ const data = {
1647
+ water: {
1648
+ mainsWater: {
1649
+ yesNo: "Yes",
1650
+ supplier: "Thames Water",
1651
+ stopcock: { location: "Kitchen" },
1652
+ waterMeter: { isSupplyMetered: "No" },
1653
+ },
1654
+ },
1655
+ drainage: {
1656
+ mainsFoulDrainage: {
1657
+ yesNo: "No",
1658
+ offMainsDrainageSystem: {
1659
+ offMainsDrainageSystemType: "Other",
1660
+ otherConnectedProperties: { yesNo: "No" },
1661
+ plantOnOtherLand: { yesNo: "No" },
1662
+ plantRegistered: { yesNo: "No" },
1663
+ plantDrainsIntoWaterway: { yesNo: "Yes" },
1664
+ },
1665
+ },
1666
+ mainsSurfaceWaterDrainage: { yesNo: "Yes" },
1667
+ },
1668
+ };
1669
+ expect(validator(data)).toBe(true);
1670
+ });
1671
+
1672
+ test("mainsSurfaceWaterDrainage is an object with yesNo discriminator", () => {
1673
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
1674
+ const mswd =
1675
+ schema.properties.propertyPack.properties.waterAndDrainage.properties
1676
+ .drainage.properties.mainsSurfaceWaterDrainage;
1677
+ expect(mswd.type).toBe("object");
1678
+ expect(mswd.properties.yesNo.enum).toEqual(["Yes", "No", "Not known"]);
1679
+ });
1680
+ });
1681
+
1682
+ describe("Solar panels validation", () => {
1683
+ let validator;
1684
+
1685
+ beforeAll(() => {
1686
+ validator = getSubschemaValidator(
1687
+ "/propertyPack/electricity",
1688
+ schemaId,
1689
+ ["ta6ed6"]
1690
+ );
1691
+ });
1692
+
1693
+ test("solarPanels No is valid", () => {
1694
+ const data = {
1695
+ mainsElectricity: {
1696
+ yesNo: "Yes",
1697
+ supplier: "British Gas",
1698
+ electricityMeter: { location: "Hall cupboard", mpan: "1234567890123" },
1699
+ },
1700
+ solarPanels: { yesNo: "No" },
1701
+ };
1702
+ expect(validator(data)).toBe(true);
1703
+ });
1704
+
1705
+ test("solarPanels Yes without required sub-properties is invalid", () => {
1706
+ const data = {
1707
+ mainsElectricity: {
1708
+ yesNo: "Yes",
1709
+ supplier: "British Gas",
1710
+ electricityMeter: { location: "Hall cupboard", mpan: "1234567890123" },
1711
+ },
1712
+ solarPanels: { yesNo: "Yes" },
1713
+ };
1714
+ expect(validator(data)).toBe(false);
1715
+ expect(
1716
+ validator.errors.some((e) =>
1717
+ e.message.includes("'hotWaterOnly'")
1718
+ )
1719
+ ).toBe(true);
1720
+ });
1721
+
1722
+ test("solarPanels Yes with all required sub-properties is valid", () => {
1723
+ const data = {
1724
+ mainsElectricity: {
1725
+ yesNo: "Yes",
1726
+ supplier: "British Gas",
1727
+ electricityMeter: { location: "Hall cupboard", mpan: "1234567890123" },
1728
+ },
1729
+ solarPanels: {
1730
+ yesNo: "Yes",
1731
+ hotWaterOnly: "No",
1732
+ yearInstalled: 2020,
1733
+ panelsOwnedOutright: { yesNo: "Yes" },
1734
+ panelProviderLease: { yesNo: "No" },
1735
+ maintenanceAgreement: { yesNo: "No" },
1736
+ photovoltaicBatteries: { yesNo: "No" },
1737
+ nationalGrid: { yesNo: "No" },
1738
+ installationCertificates: "Attached",
1739
+ },
1740
+ };
1741
+ expect(validator(data)).toBe(true);
1742
+ });
1743
+
1744
+ test("solarPanels Yes with battery details and nationalGrid is valid", () => {
1745
+ const data = {
1746
+ mainsElectricity: {
1747
+ yesNo: "Yes",
1748
+ supplier: "British Gas",
1749
+ electricityMeter: { location: "Hall cupboard", mpan: "1234567890123" },
1750
+ },
1751
+ solarPanels: {
1752
+ yesNo: "Yes",
1753
+ hotWaterOnly: "No",
1754
+ yearInstalled: 2021,
1755
+ panelsOwnedOutright: { yesNo: "Yes" },
1756
+ panelProviderLease: { yesNo: "No" },
1757
+ maintenanceAgreement: { yesNo: "Yes", attachments: "Attached" },
1758
+ photovoltaicBatteries: {
1759
+ yesNo: "Yes",
1760
+ description: "Garage wall",
1761
+ details: "Tesla Powerwall 13.5kWh",
1762
+ },
1763
+ nationalGrid: { yesNo: "Yes", fitOrSegInPlace: { yesNo: "No" } },
1764
+ installationCertificates: "Attached",
1765
+ },
1766
+ };
1767
+ expect(validator(data)).toBe(true);
1768
+ });
1769
+ });
1770
+
1771
+ describe("Notices section validation", () => {
1772
+ let validator;
1773
+
1774
+ beforeAll(() => {
1775
+ validator = getSubschemaValidator(
1776
+ "/propertyPack/notices",
1777
+ schemaId,
1778
+ ["ta6ed6"]
1779
+ );
1780
+ });
1781
+
1782
+ test("all No answers passes", () => {
1783
+ const data = {
1784
+ neighbourDevelopment: { yesNo: "No" },
1785
+ planningApplication: { yesNo: "No" },
1786
+ requiredMaintenance: { yesNo: "No" },
1787
+ listedBuildingApplication: { yesNo: "No" },
1788
+ infrastructureProject: { yesNo: "No" },
1789
+ partyWallAct: { yesNo: "No" },
1790
+ otherNotices: { yesNo: "No" },
1791
+ };
1792
+ expect(validator(data)).toBe(true);
1793
+ });
1794
+
1795
+ test("missing neighbourDevelopment is invalid", () => {
1796
+ const data = {
1797
+ planningApplication: { yesNo: "No" },
1798
+ partyWallAct: { yesNo: "No" },
1799
+ otherNotices: { yesNo: "No" },
1800
+ };
1801
+ expect(validator(data)).toBe(false);
1802
+ expect(
1803
+ validator.errors.some((e) =>
1804
+ e.message.includes("'neighbourDevelopment'")
1805
+ )
1806
+ ).toBe(true);
1807
+ });
1808
+
1809
+ test("neighbourDevelopment Yes with details is valid", () => {
1810
+ const data = {
1811
+ neighbourDevelopment: {
1812
+ yesNo: "Yes",
1813
+ details: "Neighbour planning a two-storey extension",
1814
+ },
1815
+ planningApplication: { yesNo: "No" },
1816
+ partyWallAct: { yesNo: "No" },
1817
+ otherNotices: { yesNo: "No" },
1818
+ };
1819
+ expect(validator(data)).toBe(true);
1820
+ });
1821
+
1822
+ test("partyWallAct Yes with details is valid", () => {
1823
+ const data = {
1824
+ neighbourDevelopment: { yesNo: "No" },
1825
+ planningApplication: { yesNo: "No" },
1826
+ partyWallAct: {
1827
+ yesNo: "Yes",
1828
+ details: "Party wall notice served by neighbour for loft conversion",
1829
+ },
1830
+ otherNotices: { yesNo: "No" },
1831
+ };
1832
+ expect(validator(data)).toBe(true);
1833
+ });
1834
+ });
1835
+
1836
+ describe("Environmental issues validation", () => {
1837
+ let validator;
1838
+
1839
+ beforeAll(() => {
1840
+ validator = getSubschemaValidator(
1841
+ "/propertyPack/environmentalIssues",
1842
+ schemaId,
1843
+ ["ta6ed6"]
1844
+ );
1845
+ });
1846
+
1847
+ test("all No answers passes", () => {
1848
+ const data = {
1849
+ flooding: {
1850
+ historicalFlooding: { hasBeenFlooded: "No" },
1851
+ floodDefences: { hasFloodDefences: "No" },
1852
+ },
1853
+ radon: {
1854
+ radonTest: { yesNo: "No" },
1855
+ remedialMeasuresOnConstruction: { yesNo: "No" },
1856
+ },
1857
+ greenDealScheme: { hasGreenDealLoan: { yesNo: "No" } },
1858
+ };
1859
+ expect(validator(data)).toBe(true);
1860
+ });
1861
+
1862
+ test("missing flooding is invalid", () => {
1863
+ const data = {
1864
+ radon: {
1865
+ radonTest: { yesNo: "No" },
1866
+ remedialMeasuresOnConstruction: { yesNo: "No" },
1867
+ },
1868
+ greenDealScheme: { hasGreenDealLoan: { yesNo: "No" } },
1869
+ };
1870
+ expect(validator(data)).toBe(false);
1871
+ expect(
1872
+ validator.errors.some((e) => e.message.includes("'flooding'"))
1873
+ ).toBe(true);
1874
+ });
1875
+
1876
+ test("historicalFlooding Yes with type and details is valid", () => {
1877
+ const data = {
1878
+ flooding: {
1879
+ historicalFlooding: {
1880
+ hasBeenFlooded: "Yes",
1881
+ typeOfFlooding: ["Surface water"],
1882
+ details: "Minor surface water flooding in 2020 winter storms",
1883
+ },
1884
+ floodDefences: { hasFloodDefences: "No" },
1885
+ },
1886
+ radon: {
1887
+ radonTest: { yesNo: "No" },
1888
+ remedialMeasuresOnConstruction: { yesNo: "No" },
1889
+ },
1890
+ greenDealScheme: { hasGreenDealLoan: { yesNo: "No" } },
1891
+ };
1892
+ expect(validator(data)).toBe(true);
1893
+ });
1894
+
1895
+ test("floodDefences Yes with details is valid", () => {
1896
+ const data = {
1897
+ flooding: {
1898
+ historicalFlooding: { hasBeenFlooded: "No" },
1899
+ floodDefences: {
1900
+ hasFloodDefences: "Yes",
1901
+ details: "Flood barrier installed by EA in 2018",
1902
+ },
1903
+ },
1904
+ radon: {
1905
+ radonTest: { yesNo: "No" },
1906
+ remedialMeasuresOnConstruction: { yesNo: "No" },
1907
+ },
1908
+ greenDealScheme: { hasGreenDealLoan: { yesNo: "No" } },
1909
+ };
1910
+ expect(validator(data)).toBe(true);
1911
+ });
1912
+ });
1913
+
1914
+ describe("Legal boundaries validation", () => {
1915
+ let validator;
1916
+
1917
+ beforeAll(() => {
1918
+ validator = getSubschemaValidator(
1919
+ "/propertyPack/legalBoundaries",
1920
+ schemaId,
1921
+ ["ta6ed6"]
1922
+ );
1923
+ });
1924
+
1925
+ test("uniform boundaries with all sides specified passes", () => {
1926
+ // The merged schema requires both uniformBoundaries and irregularBoundaries
1927
+ // (union of required arrays from base and overlay oneOf branches).
1928
+ // When areBoundariesUniform is "Yes", provide irregularBoundaries with yesNo: "No".
1929
+ const data = {
1930
+ ownership: {
1931
+ areBoundariesUniform: "Yes",
1932
+ uniformBoundaries: {
1933
+ left: "Seller",
1934
+ right: "Neighbour",
1935
+ rear: "Seller",
1936
+ front: "Not known",
1937
+ },
1938
+ irregularBoundaries: { yesNo: "No" },
1939
+ },
1940
+ haveBoundaryFeaturesMoved: { yesNo: "No" },
1941
+ flyingFreehold: { yesNo: "No" },
1942
+ };
1943
+ expect(validator(data)).toBe(true);
1944
+ });
1945
+
1946
+ test("missing ownership is invalid", () => {
1947
+ const data = {
1948
+ haveBoundaryFeaturesMoved: { yesNo: "No" },
1949
+ flyingFreehold: { yesNo: "No" },
1950
+ };
1951
+ expect(validator(data)).toBe(false);
1952
+ expect(
1953
+ validator.errors.some((e) => e.message.includes("'ownership'"))
1954
+ ).toBe(true);
1955
+ });
1956
+
1957
+ test("missing haveBoundaryFeaturesMoved is invalid", () => {
1958
+ const data = {
1959
+ ownership: {
1960
+ areBoundariesUniform: "Yes",
1961
+ uniformBoundaries: {
1962
+ left: "Seller",
1963
+ right: "Seller",
1964
+ rear: "Seller",
1965
+ front: "Seller",
1966
+ },
1967
+ irregularBoundaries: { yesNo: "No" },
1968
+ },
1969
+ flyingFreehold: { yesNo: "No" },
1970
+ };
1971
+ expect(validator(data)).toBe(false);
1972
+ expect(
1973
+ validator.errors.some((e) =>
1974
+ e.message.includes("'haveBoundaryFeaturesMoved'")
1975
+ )
1976
+ ).toBe(true);
1977
+ });
1978
+
1979
+ test("boundaries Not applicable is valid", () => {
1980
+ // Even for "Not applicable", the merged schema requires uniformBoundaries
1981
+ // and irregularBoundaries due to required array union from overlays.
1982
+ const data = {
1983
+ ownership: {
1984
+ areBoundariesUniform: "Not applicable",
1985
+ uniformBoundaries: {
1986
+ left: "Not known",
1987
+ right: "Not known",
1988
+ rear: "Not known",
1989
+ front: "Not known",
1990
+ },
1991
+ irregularBoundaries: { yesNo: "No" },
1992
+ },
1993
+ haveBoundaryFeaturesMoved: { yesNo: "No" },
1994
+ flyingFreehold: { yesNo: "No" },
1995
+ };
1996
+ expect(validator(data)).toBe(true);
1997
+ });
1998
+
1999
+ test("haveBoundaryFeaturesMoved Yes with details is valid", () => {
2000
+ const data = {
2001
+ ownership: {
2002
+ areBoundariesUniform: "Yes",
2003
+ uniformBoundaries: {
2004
+ left: "Seller",
2005
+ right: "Neighbour",
2006
+ rear: "Shared",
2007
+ front: "Seller",
2008
+ },
2009
+ irregularBoundaries: { yesNo: "No" },
2010
+ },
2011
+ haveBoundaryFeaturesMoved: {
2012
+ yesNo: "Yes",
2013
+ details: "Rear fence moved by 30cm after storm damage",
2014
+ },
2015
+ flyingFreehold: { yesNo: "No" },
2016
+ };
2017
+ expect(validator(data)).toBe(true);
2018
+ });
2019
+ });
2020
+
2021
+ describe("Guarantees warranties and indemnity insurances validation", () => {
2022
+ let validator;
2023
+
2024
+ beforeAll(() => {
2025
+ validator = getSubschemaValidator(
2026
+ "/propertyPack/guaranteesWarrantiesAndIndemnityInsurances",
2027
+ schemaId,
2028
+ ["ta6ed6"]
2029
+ );
2030
+ });
2031
+
2032
+ test("all No answers passes", () => {
2033
+ const data = {
2034
+ hasValidGuaranteesOrWarranties: "No",
2035
+ newHomeWarranty: { yesNo: "No" },
2036
+ dampProofingTreatment: { yesNo: "No" },
2037
+ timberRotOrInfestationTreatment: { yesNo: "No" },
2038
+ doubleGlazing: { yesNo: "No" },
2039
+ roofingWork: { yesNo: "No" },
2040
+ centralHeatingAndorPlumbing: { yesNo: "No" },
2041
+ subsidenceWork: { yesNo: "No" },
2042
+ insulation: { yesNo: "No" },
2043
+ otherGuarantees: { yesNo: "No" },
2044
+ outstandingClaimsOrApplications: { yesNo: "No" },
2045
+ breachOfTermsOrConditions: { yesNo: "No" },
2046
+ };
2047
+ expect(validator(data)).toBe(true);
2048
+ });
2049
+
2050
+ test("missing hasValidGuaranteesOrWarranties is invalid", () => {
2051
+ const data = {
2052
+ newHomeWarranty: { yesNo: "No" },
2053
+ dampProofingTreatment: { yesNo: "No" },
2054
+ timberRotOrInfestationTreatment: { yesNo: "No" },
2055
+ doubleGlazing: { yesNo: "No" },
2056
+ roofingWork: { yesNo: "No" },
2057
+ centralHeatingAndorPlumbing: { yesNo: "No" },
2058
+ subsidenceWork: { yesNo: "No" },
2059
+ insulation: { yesNo: "No" },
2060
+ otherGuarantees: { yesNo: "No" },
2061
+ outstandingClaimsOrApplications: { yesNo: "No" },
2062
+ breachOfTermsOrConditions: { yesNo: "No" },
2063
+ };
2064
+ expect(validator(data)).toBe(false);
2065
+ expect(
2066
+ validator.errors.some((e) =>
2067
+ e.message.includes("'hasValidGuaranteesOrWarranties'")
2068
+ )
2069
+ ).toBe(true);
2070
+ });
2071
+
2072
+ test("newHomeWarranty Yes with attachments is valid", () => {
2073
+ const data = {
2074
+ hasValidGuaranteesOrWarranties: "Yes",
2075
+ newHomeWarranty: { yesNo: "Yes", attachments: "Attached" },
2076
+ dampProofingTreatment: { yesNo: "No" },
2077
+ timberRotOrInfestationTreatment: { yesNo: "No" },
2078
+ doubleGlazing: { yesNo: "No" },
2079
+ roofingWork: { yesNo: "No" },
2080
+ centralHeatingAndorPlumbing: { yesNo: "No" },
2081
+ subsidenceWork: { yesNo: "No" },
2082
+ subsidenceWork: { yesNo: "No" },
2083
+ insulation: { yesNo: "No" },
2084
+ otherGuarantees: { yesNo: "No" },
2085
+ outstandingClaimsOrApplications: { yesNo: "No" },
2086
+ breachOfTermsOrConditions: { yesNo: "No" },
2087
+ };
2088
+ expect(validator(data)).toBe(true);
2089
+ });
2090
+
2091
+ test("newHomeWarranty Yes without attachments is invalid", () => {
2092
+ const data = {
2093
+ hasValidGuaranteesOrWarranties: "Yes",
2094
+ newHomeWarranty: { yesNo: "Yes" },
2095
+ dampProofingTreatment: { yesNo: "No" },
2096
+ timberRotOrInfestationTreatment: { yesNo: "No" },
2097
+ doubleGlazing: { yesNo: "No" },
2098
+ roofingWork: { yesNo: "No" },
2099
+ centralHeatingAndorPlumbing: { yesNo: "No" },
2100
+ subsidenceWork: { yesNo: "No" },
2101
+ subsidenceWork: { yesNo: "No" },
2102
+ insulation: { yesNo: "No" },
2103
+ otherGuarantees: { yesNo: "No" },
2104
+ outstandingClaimsOrApplications: { yesNo: "No" },
2105
+ breachOfTermsOrConditions: { yesNo: "No" },
2106
+ };
2107
+ expect(validator(data)).toBe(false);
2108
+ expect(
2109
+ validator.errors.some((e) => e.message.includes("'attachments'"))
2110
+ ).toBe(true);
2111
+ });
2112
+
2113
+ test("multiple guarantees with Yes answers all valid", () => {
2114
+ const data = {
2115
+ hasValidGuaranteesOrWarranties: "Yes",
2116
+ newHomeWarranty: { yesNo: "Yes", attachments: "Attached" },
2117
+ dampProofingTreatment: { yesNo: "Yes", attachments: "To follow" },
2118
+ timberRotOrInfestationTreatment: { yesNo: "No" },
2119
+ doubleGlazing: { yesNo: "Yes", attachments: "Attached" },
2120
+ roofingWork: { yesNo: "No" },
2121
+ centralHeatingAndorPlumbing: { yesNo: "No" },
2122
+ subsidenceWork: { yesNo: "No" },
2123
+ subsidenceWork: { yesNo: "No" },
2124
+ insulation: { yesNo: "No" },
2125
+ otherGuarantees: { yesNo: "No" },
2126
+ outstandingClaimsOrApplications: { yesNo: "No" },
2127
+ breachOfTermsOrConditions: { yesNo: "No" },
2128
+ };
2129
+ expect(validator(data)).toBe(true);
2130
+ });
2131
+ });
2132
+
2133
+ describe("Previously broken discriminator sections now compile", () => {
2134
+ // These sections previously failed AJV compilation because their
2135
+ // discriminated properties were not in the required array of each
2136
+ // oneOf branch. This has been fixed in combined.json.
2137
+
2138
+ const sections = [
2139
+ "alterationsAndChanges",
2140
+ "notices",
2141
+ "waterAndDrainage",
2142
+ "electricity",
2143
+ "environmentalIssues",
2144
+ "legalBoundaries",
2145
+ "guaranteesWarrantiesAndIndemnityInsurances",
2146
+ ];
2147
+
2148
+ test.each(sections)(
2149
+ "%s subschema validator compiles successfully",
2150
+ (section) => {
2151
+ const validator = getSubschemaValidator(
2152
+ `/propertyPack/${section}`,
2153
+ schemaId,
2154
+ ["ta6ed6"]
2155
+ );
2156
+ expect(typeof validator).toBe("function");
2157
+ }
2158
+ );
2159
+
2160
+ test("full transaction validator compiles successfully", () => {
2161
+ const validator = getSubschemaValidator("", schemaId, ["ta6ed6"]);
2162
+ expect(typeof validator).toBe("function");
2163
+ });
2164
+ });
2165
+
2166
+ describe("Full transaction validation with ta6ed6", () => {
2167
+ let validator;
2168
+
2169
+ beforeAll(() => {
2170
+ validator = getSubschemaValidator("", schemaId, ["ta6ed6"]);
2171
+ });
2172
+
2173
+ test("example transaction is invalid under ta6ed6 (missing required sections)", () => {
2174
+ const exampleTransaction = require("../../examples/v3/exampleTransaction.json");
2175
+ expect(validator(exampleTransaction)).toBe(false);
2176
+ const errorMessages = validator.errors.map((e) => e.message);
2177
+ expect(errorMessages).toContain(
2178
+ "must have required property 'consents'"
2179
+ );
2180
+ });
2181
+
2182
+ test("transaction missing required propertyPack section is invalid", () => {
2183
+ const transaction = buildMinimalTransaction();
2184
+ delete transaction.propertyPack.parking;
2185
+ expect(validator(transaction)).toBe(false);
2186
+ expect(
2187
+ validator.errors.some((e) => e.message.includes("'parking'"))
2188
+ ).toBe(true);
2189
+ });
2190
+ });
2191
+ });
2192
+
2193
+ /**
2194
+ * Builds a minimal transaction object for testing missing-section validation.
2195
+ * Note: this transaction may not fully pass validation due to known schema
2196
+ * merge conflicts (e.g. insurance.isInsured type/enum conflict, capacity
2197
+ * enum mismatch), but it contains all required propertyPack sections so
2198
+ * we can test that removing one is detected.
2199
+ */
2200
+ function buildMinimalTransaction() {
2201
+ return {
2202
+ transactionId: "test-ta6ed6",
2203
+ status: "For sale",
2204
+ participants: [
2205
+ {
2206
+ role: "Seller",
2207
+ name: { firstName: "Jane", lastName: "Doe" },
2208
+ email: "jane@example.com",
2209
+ address: { line1: "1 Test St", postcode: "SW1A 1AA" },
2210
+ organisation: "Self",
2211
+ sellersCapacity: { capacity: "Legal Owner" },
2212
+ },
2213
+ {
2214
+ role: "Seller's Conveyancer",
2215
+ name: { firstName: "John", lastName: "Law" },
2216
+ email: "john@law.co.uk",
2217
+ address: { line1: "2 Law Lane", postcode: "EC1A 1BB" },
2218
+ organisation: "Law Firm LLP",
2219
+ organisationReference: "REF001",
2220
+ },
2221
+ ],
2222
+ propertyPack: {
2223
+ address: { line1: "1 Test St", postcode: "SW1A 1AA" },
2224
+ parking: {
2225
+ parkingArrangements: ["Driveway"],
2226
+ controlledParking: { yesNo: "No" },
2227
+ electricVehicleChargingPoint: { yesNo: "No" },
2228
+ },
2229
+ disputesAndComplaints: {
2230
+ hasDisputesAndComplaints: { yesNo: "No" },
2231
+ leadingToDisputesAndComplaints: { yesNo: "No" },
2232
+ },
2233
+ alterationsAndChanges: {
2234
+ windowReplacementsSince2002: { yesNo: "No" },
2235
+ hasAddedConservatory: { yesNo: "No" },
2236
+ extension: { yesNo: "No" },
2237
+ loftConversion: { yesNo: "No" },
2238
+ garageConversion: { yesNo: "No" },
2239
+ removalOfInternalWalls: { yesNo: "No" },
2240
+ removalOfChimneyBreast: { yesNo: "No" },
2241
+ insulation: { yesNo: "No" },
2242
+ otherBuildingWorksOrChangesToTheProperty: { yesNo: "No" },
2243
+ changeOfUse: { yesNo: "No" },
2244
+ nonResidentialPurposes: { yesNo: "No" },
2245
+ planningPermissionBreaches: { yesNo: "No" },
2246
+ unresolvedPlanningIssues: { yesNo: "No" },
2247
+ },
2248
+ listingAndConservation: {
2249
+ isListed: { yesNo: "No" },
2250
+ isConservationArea: { yesNo: "No" },
2251
+ hasTreePreservationOrder: { yesNo: "No" },
2252
+ },
2253
+ notices: {
2254
+ neighbourDevelopment: { yesNo: "No" },
2255
+ planningApplication: { yesNo: "No" },
2256
+ requiredMaintenance: { yesNo: "No" },
2257
+ listedBuildingApplication: { yesNo: "No" },
2258
+ infrastructureProject: { yesNo: "No" },
2259
+ partyWallAct: { yesNo: "No" },
2260
+ otherNotices: { yesNo: "No" },
2261
+ },
2262
+ specialistIssues: {},
2263
+ waterAndDrainage: {
2264
+ water: {
2265
+ mainsWater: {
2266
+ yesNo: "Yes",
2267
+ supplier: "Thames Water",
2268
+ stopcock: { location: "Under the stairs" },
2269
+ waterMeter: { isSupplyMetered: "No" },
2270
+ },
2271
+ },
2272
+ drainage: {
2273
+ mainsFoulDrainage: { yesNo: "Yes", supplier: "Thames Water" },
2274
+ mainsSurfaceWaterDrainage: { yesNo: "Yes" },
2275
+ offMainsDrainageSystem: {},
2276
+ },
2277
+ },
2278
+ electricity: {
2279
+ mainsElectricity: {
2280
+ yesNo: "Yes",
2281
+ supplier: "British Gas",
2282
+ electricityMeter: { location: "Hall cupboard" },
2283
+ },
2284
+ solarPanels: { yesNo: "No" },
2285
+ },
2286
+ heating: {
2287
+ heatingTypes: ["Central heating"],
2288
+ heatingOtherDetails: "Gas central heating",
2289
+ heatingSystemInstalledDate: "2015",
2290
+ boilerInstalledDate: "2015",
2291
+ heatingReplacementOtherThanBoiler: "No",
2292
+ heatingComplianceDocs: "None",
2293
+ heatingInspectionReport: "None",
2294
+ boilerWorking: "Yes",
2295
+ boilerLastServicedYear: "2024",
2296
+ multipleHeatingSystemsAttachment: "Attached",
2297
+ },
2298
+ connectivity: {
2299
+ telephone: { yesNo: "Yes", supplier: "BT" },
2300
+ broadband: { typeOfConnection: "None" },
2301
+ },
2302
+ insurance: {
2303
+ isInsured: "Yes",
2304
+ difficultiesObtainingInsurance: { yesNo: "No" },
2305
+ insuranceClaims: { yesNo: "No" },
2306
+ },
2307
+ rightsAndInformalArrangements: {
2308
+ neighbouringLandRights: { yesNo: "No" },
2309
+ sharedContributions: { yesNo: "No" },
2310
+ disagreementAboutRightsYouExercise: { yesNo: "No" },
2311
+ rightsOverProperty: { yesNo: "No" },
2312
+ askedOthersToContribute: { yesNo: "No" },
2313
+ accessRestrictionAttempts: { yesNo: "No" },
2314
+ },
2315
+ environmentalIssues: {
2316
+ flooding: {
2317
+ historicalFlooding: { hasBeenFlooded: "No" },
2318
+ floodDefences: { hasFloodDefences: "No" },
2319
+ },
2320
+ radon: { yesNo: "No" },
2321
+ radonRemedialMeasures: { yesNo: "No" },
2322
+ greenDealScheme: { yesNo: "No" },
2323
+ japaneseKnotweed: { yesNo: "No" },
2324
+ japaneseKnotweedSurvey: { yesNo: "No" },
2325
+ },
2326
+ legalBoundaries: {
2327
+ ownership: {
2328
+ areBoundariesUniform: "Yes",
2329
+ uniformBoundaries: {
2330
+ left: "Seller",
2331
+ right: "Neighbour",
2332
+ rear: "Seller",
2333
+ front: "Not known",
2334
+ },
2335
+ irregularBoundaries: { yesNo: "No" },
2336
+ },
2337
+ haveBoundaryFeaturesMoved: { yesNo: "No" },
2338
+ flyingFreehold: { yesNo: "No" },
2339
+ },
2340
+ servicesCrossing: {
2341
+ pipesWiresCablesDrainsToProperty: { yesNo: "No" },
2342
+ pipesWiresCablesDrainsFromProperty: { yesNo: "No" },
2343
+ formalOrInformalAgreements: { yesNo: "No" },
2344
+ },
2345
+ electricalWorks: {
2346
+ testedByQualifiedElectrician: { yesNo: "No" },
2347
+ electricalWorkSince2005: { yesNo: "No" },
2348
+ },
2349
+ guaranteesWarrantiesAndIndemnityInsurances: {
2350
+ hasValidGuaranteesOrWarranties: "No",
2351
+ },
2352
+ occupiers: {
2353
+ sellerLivesAtProperty: { yesNo: "Yes" },
2354
+ othersAged17OrOver: { hasOthersAged17OrOver: "No" },
2355
+ vacantPossession: { soldWithVacantPossession: "Yes" },
2356
+ occupiersAged17OrOverAgreedToSignAndVacate: { yesNo: "Yes" },
2357
+ tenancyAgreementsIfNotVacantPossession: "Attached",
2358
+ },
2359
+ completionAndMoving: {
2360
+ sellerWillEnsure: {
2361
+ removeRubbish: true,
2362
+ replaceLightFittings: true,
2363
+ takeReasonableCare: true,
2364
+ leaveKeys: true,
2365
+ },
2366
+ sufficientToRepayAllMortgages: { yesNo: "Yes" },
2367
+ },
2368
+ confirmationOfAccuracyByOwners: {
2369
+ confirmInformationIsAccurate: true,
2370
+ },
2371
+ additionalInformation: {},
2372
+ },
2373
+ };
2374
+ }