@pdtf/schemas 3.4.1-1 → 3.4.1-3

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.
@@ -4,45 +4,63 @@ const {
4
4
  getValidator,
5
5
  } = require("../../../index.js");
6
6
 
7
- const schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
7
+ const schemaId =
8
+ "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
8
9
 
9
10
  describe("Extension Overlays", () => {
10
-
11
11
  describe("String key support", () => {
12
12
  test("should load extension overlays using string keys", () => {
13
13
  const schema = getTransactionSchema(schemaId, ["nts2023", "jk"]);
14
- const jkProp = schema.properties?.propertyPack?.properties?.specialistIssues?.properties?.japaneseKnotweed;
15
-
14
+ const jkProp =
15
+ schema.properties?.propertyPack?.properties?.specialistIssues
16
+ ?.properties?.japaneseKnotweed;
17
+
16
18
  expect(jkProp).toBeDefined();
17
19
  expect(jkProp.ntsRef).toBe("A5.3");
18
20
  });
19
21
 
20
22
  test("should work with multiple extension string keys", () => {
21
- const schema = getTransactionSchema(schemaId, ["nts2023", "jk", "tf", "ma"]);
22
-
23
- const jkProp = schema.properties?.propertyPack?.properties?.specialistIssues?.properties?.japaneseKnotweed;
24
- const tfProp = schema.properties?.propertyPack?.properties?.ownership?.properties?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties?.leaseholdInformation?.properties?.serviceCharge?.oneOf?.[1]?.properties?.transferFees;
25
- const maProp = schema.properties?.propertyPack?.properties?.ownership?.properties?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties?.leaseholdInformation?.properties?.contactDetails;
26
-
23
+ const schema = getTransactionSchema(schemaId, [
24
+ "nts2023",
25
+ "jk",
26
+ "tf",
27
+ "ma",
28
+ "er",
29
+ ]);
30
+
31
+ const jkProp =
32
+ schema.properties?.propertyPack?.properties?.specialistIssues
33
+ ?.properties?.japaneseKnotweed;
34
+ const tfProp =
35
+ schema.properties?.propertyPack?.properties?.ownership?.properties
36
+ ?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties
37
+ ?.leaseholdInformation?.properties?.serviceCharge?.oneOf?.[1]
38
+ ?.properties?.transferFees;
39
+ const maProp =
40
+ schema.properties?.propertyPack?.properties?.ownership?.properties
41
+ ?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties
42
+ ?.leaseholdInformation?.properties?.contactDetails;
43
+ const erProp =
44
+ schema.properties?.propertyPack?.properties?.ownership?.properties
45
+ ?.ownershipsToBeTransferred?.items?.oneOf?.[0]?.properties
46
+ ?.estateRentcharges;
47
+
27
48
  expect(jkProp?.ntsRef).toBe("A5.3");
28
49
  expect(tfProp?.ntsRef).toBe("A3.5.1.1");
29
50
  expect(maProp?.ntsRef).toBe("A1.5.4");
30
- });
31
-
32
- test("should be equivalent to direct object access", () => {
33
- const schemaString = getTransactionSchema(schemaId, ["nts2023", "jk"]);
34
- const schemaDirect = getTransactionSchema(schemaId, ["nts2023", extensionOverlays.jk]);
35
-
36
- expect(schemaString).toEqual(schemaDirect);
51
+ expect(erProp?.ntsRef).toBe("A3.4");
37
52
  });
38
53
  });
39
54
 
40
55
  describe("Individual extension functionality", () => {
41
56
  test("should add Japanese Knotweed extension correctly", () => {
42
57
  const schema = getTransactionSchema(schemaId, ["nts2023", "jk"]);
43
- const jkProp = schema.properties?.propertyPack?.properties?.specialistIssues?.properties?.japaneseKnotweed;
44
-
58
+ const jkProp =
59
+ schema.properties?.propertyPack?.properties?.specialistIssues
60
+ ?.properties?.japaneseKnotweed;
61
+
45
62
  expect(jkProp).toBeDefined();
63
+
46
64
  expect(jkProp.ntsRef).toBe("A5.3");
47
65
  expect(jkProp.required).toContain("yesNo");
48
66
  expect(jkProp.discriminator?.propertyName).toBe("yesNo");
@@ -50,17 +68,26 @@ describe("Extension Overlays", () => {
50
68
 
51
69
  test("should add Transfer Fees extension correctly", () => {
52
70
  const schema = getTransactionSchema(schemaId, ["nts2023", "tf"]);
53
- const tfProp = schema.properties?.propertyPack?.properties?.ownership?.properties?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties?.leaseholdInformation?.properties?.serviceCharge?.oneOf?.[1]?.properties?.transferFees;
54
-
71
+ const tfProp =
72
+ schema.properties?.propertyPack?.properties?.ownership?.properties
73
+ ?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties
74
+ ?.leaseholdInformation?.properties?.serviceCharge?.oneOf?.[1]
75
+ ?.properties?.transferFees;
76
+
55
77
  expect(tfProp).toBeDefined();
56
78
  expect(tfProp.ntsRef).toBe("A3.5.1.1");
57
- expect(tfProp.title).toBe("Are there any additional fees payable on sale or letting?");
79
+ expect(tfProp.title).toBe(
80
+ "Are there any additional fees payable on sale or letting?"
81
+ );
58
82
  });
59
83
 
60
84
  test("should add Managing Agent extension correctly", () => {
61
85
  const schema = getTransactionSchema(schemaId, ["nts2023", "ma"]);
62
- const maProp = schema.properties?.propertyPack?.properties?.ownership?.properties?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties?.leaseholdInformation?.properties?.contactDetails;
63
-
86
+ const maProp =
87
+ schema.properties?.propertyPack?.properties?.ownership?.properties
88
+ ?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties
89
+ ?.leaseholdInformation?.properties?.contactDetails;
90
+
64
91
  expect(maProp).toBeDefined();
65
92
  expect(maProp.ntsRef).toBe("A1.5.4");
66
93
  expect(maProp.required).toContain("contacts");
@@ -68,32 +95,77 @@ describe("Extension Overlays", () => {
68
95
 
69
96
  test("should add Solar Panels extension correctly", () => {
70
97
  const schema = getTransactionSchema(schemaId, ["nts2023", "sl"]);
71
- const slProp = schema.properties?.propertyPack?.properties?.electricity?.properties?.solarPanels?.oneOf?.[1]?.properties?.panelsOwnedOutright;
72
-
98
+ const slProp =
99
+ schema.properties?.propertyPack?.properties?.electricity?.properties
100
+ ?.solarPanels?.oneOf?.[1]?.properties?.panelsOwnedOutright;
101
+
73
102
  expect(slProp).toBeDefined();
74
103
  expect(slProp.ntsRef).toBe("B3.7.1");
75
104
  });
76
105
 
77
106
  test("should add Heating Installation extension correctly", () => {
78
107
  const schema = getTransactionSchema(schemaId, ["nts2023", "hi"]);
79
- const hiProp = schema.properties?.propertyPack?.properties?.heating?.properties?.heatingSystem?.oneOf?.[1]?.properties?.centralHeatingDetails?.properties?.centralHeatingInstalled;
80
-
108
+ const hiProp =
109
+ schema.properties?.propertyPack?.properties?.heating?.properties
110
+ ?.heatingSystem?.oneOf?.[1]?.properties?.centralHeatingDetails
111
+ ?.properties?.centralHeatingInstalled;
112
+
81
113
  expect(hiProp).toBeDefined();
82
114
  expect(hiProp.ntsRef).toBe("B3.4.3.2");
83
115
  });
116
+
117
+ test("should add Estate Rentcharges extension correctly", () => {
118
+ const schema = getTransactionSchema(schemaId, ["nts2023", "er"]);
119
+ const erProp =
120
+ schema.properties?.propertyPack?.properties?.ownership?.properties
121
+ ?.ownershipsToBeTransferred?.items?.oneOf?.[0]?.properties
122
+ ?.estateRentcharges;
123
+
124
+ expect(erProp).toBeDefined();
125
+
126
+ expect(erProp.ntsRef).toBe("A3.4");
127
+ expect(erProp.required).toContain("yesNo");
128
+ expect(erProp.discriminator?.propertyName).toBe("yesNo");
129
+ expect(erProp.oneOf).toBeDefined();
130
+ expect(Array.isArray(erProp.oneOf)).toBe(true);
131
+ expect(erProp.oneOf.length).toBeGreaterThan(0);
132
+
133
+ // Check the conditional branch for "Yes" response
134
+ const yesBranch = erProp.oneOf.find(
135
+ (branch) =>
136
+ branch.required?.includes("details") &&
137
+ branch.required?.includes("amount")
138
+ );
139
+ expect(yesBranch).toBeDefined();
140
+ expect(yesBranch.properties?.details?.ntsRef).toBe("A3.4.2");
141
+ expect(yesBranch.properties?.amount?.ntsRef).toBe("A3.4.3");
142
+ });
84
143
  });
85
144
 
86
145
  describe("Extension overlay availability", () => {
87
146
  test("should export all extension overlays", () => {
88
147
  const expectedKeys = [
89
- "as", "dr", "jk", "sb", "hs", // Specialist Issues
90
- "oa", "la", "sf", "mc", // Property Features
91
- "er", "ma", "tf", // Ownership & Financial
92
- "sl", "hi", "fd", // Utilities & Services
93
- "oc" // Transaction
148
+ "as",
149
+ "dr",
150
+ "jk",
151
+ "sb",
152
+ "hs", // Specialist Issues
153
+ "oa",
154
+ "la",
155
+ "sf",
156
+ "mc", // Property Features
157
+ "er",
158
+ "ma",
159
+ "tf", // Ownership & Financial
160
+ "sl",
161
+ "hi",
162
+ "fd", // Utilities & Services
163
+ "oc", // Transaction
94
164
  ];
95
-
96
- expect(Object.keys(extensionOverlays).sort()).toEqual(expectedKeys.sort());
165
+
166
+ expect(Object.keys(extensionOverlays).sort()).toEqual(
167
+ expectedKeys.sort()
168
+ );
97
169
  });
98
170
 
99
171
  test("should have valid JSON structure for all extensions", () => {
@@ -110,78 +182,131 @@ describe("Extension Overlays", () => {
110
182
  test("should produce equivalent result to nts2 when all relevant extensions are combined", () => {
111
183
  // Get the full NTS2 schema
112
184
  const nts2Schema = getTransactionSchema(schemaId, ["nts2025"]);
113
-
185
+
114
186
  // Get NTS base with all relevant extensions
115
187
  const allExtensions = [
116
188
  "nts2023",
117
189
  // Specialist issues extensions
118
- "as", "dr", "jk", "sb", "hs",
119
- // Property features extensions
120
- "oa", "la", "sf", "mc",
190
+ "as",
191
+ "dr",
192
+ "jk",
193
+ "sb",
194
+ "hs",
195
+ // Property features extensions
196
+ "oa",
197
+ "la",
198
+ "sf",
199
+ "mc",
121
200
  // Ownership & financial extensions
122
- "er", "ma", "tf",
201
+ "er",
202
+ "ma",
203
+ "tf",
123
204
  // Utilities & services extensions
124
- "sl", "hi", "fd",
205
+ "sl",
206
+ "hi",
207
+ "fd",
125
208
  // Transaction extensions
126
- "oc"
209
+ "oc",
127
210
  ];
128
-
129
- const ntsWithAllExtensions = getTransactionSchema(schemaId, allExtensions);
130
-
211
+
212
+ const ntsWithAllExtensions = getTransactionSchema(
213
+ schemaId,
214
+ allExtensions
215
+ );
216
+
131
217
  // Compare key structural elements
132
218
  const nts2PropPack = nts2Schema.properties?.propertyPack;
133
219
  const extPropPack = ntsWithAllExtensions.properties?.propertyPack;
134
-
220
+
135
221
  // Check that specialist issues section exists in both
136
222
  expect(nts2PropPack?.properties?.specialistIssues).toBeDefined();
137
223
  expect(extPropPack?.properties?.specialistIssues).toBeDefined();
138
-
224
+
139
225
  // Check key specialist issues properties
140
226
  const nts2SI = nts2PropPack.properties.specialistIssues.properties;
141
227
  const extSI = extPropPack.properties.specialistIssues.properties;
142
-
228
+
143
229
  expect(extSI?.japaneseKnotweed).toBeDefined();
144
230
  expect(extSI?.dryRotEtcTreatment).toBeDefined();
145
231
  expect(extSI?.containsAsbestos).toBeDefined();
146
232
  expect(extSI?.subsidenceOrStructuralFault).toBeDefined();
147
233
  expect(extSI?.ongoingHealthOrSafetyIssue).toBeDefined();
148
-
234
+
149
235
  // Check that references match (extensions use ntsRef, NTS2 uses nts2Ref)
150
236
  if (nts2SI?.japaneseKnotweed && extSI?.japaneseKnotweed) {
151
237
  expect(extSI.japaneseKnotweed.ntsRef).toBe("A5.3");
152
238
  // NTS2 might have nts2Ref instead - this is expected difference
153
239
  }
154
-
240
+
155
241
  // Check outside areas
156
- expect(extPropPack?.properties?.residentialPropertyFeatures?.properties?.outsideAreas).toBeDefined();
157
-
242
+ expect(
243
+ extPropPack?.properties?.residentialPropertyFeatures?.properties
244
+ ?.outsideAreas
245
+ ).toBeDefined();
246
+
158
247
  // Check leasehold extensions
159
- const leaseholdInfo = extPropPack?.properties?.ownership?.properties?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties?.leaseholdInformation;
248
+ const leaseholdInfo =
249
+ extPropPack?.properties?.ownership?.properties
250
+ ?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties
251
+ ?.leaseholdInformation;
160
252
  expect(leaseholdInfo?.properties?.contactDetails).toBeDefined(); // Managing agent
161
- expect(leaseholdInfo?.properties?.serviceCharge?.oneOf?.[1]?.properties?.transferFees).toBeDefined(); // Transfer fees
162
-
253
+ expect(
254
+ leaseholdInfo?.properties?.serviceCharge?.oneOf?.[1]?.properties
255
+ ?.transferFees
256
+ ).toBeDefined(); // Transfer fees
257
+
163
258
  // Check utility extensions
164
- expect(extPropPack?.properties?.electricity?.properties?.solarPanels?.oneOf?.[1]?.properties?.panelsOwnedOutright).toBeDefined();
165
- expect(extPropPack?.properties?.heating?.properties?.heatingSystem?.oneOf?.[1]?.properties?.centralHeatingDetails?.properties?.centralHeatingInstalled).toBeDefined();
166
-
259
+ expect(
260
+ extPropPack?.properties?.electricity?.properties?.solarPanels
261
+ ?.oneOf?.[1]?.properties?.panelsOwnedOutright
262
+ ).toBeDefined();
263
+ expect(
264
+ extPropPack?.properties?.heating?.properties?.heatingSystem?.oneOf?.[1]
265
+ ?.properties?.centralHeatingDetails?.properties
266
+ ?.centralHeatingInstalled
267
+ ).toBeDefined();
268
+
167
269
  // Check construction extensions
168
- expect(extPropPack?.properties?.typeOfConstruction?.properties?.loft).toBeDefined();
169
- expect(extPropPack?.properties?.typeOfConstruction?.properties?.sprayFoamInsulation).toBeDefined();
170
-
270
+ expect(
271
+ extPropPack?.properties?.typeOfConstruction?.properties?.loft
272
+ ).toBeDefined();
273
+ expect(
274
+ extPropPack?.properties?.typeOfConstruction?.properties
275
+ ?.sprayFoamInsulation
276
+ ).toBeDefined();
277
+
171
278
  // Check completion and moving
172
- expect(extPropPack?.properties?.completionAndMoving?.properties?.otherPropertyInChain).toBeDefined();
279
+ expect(
280
+ extPropPack?.properties?.completionAndMoving?.properties
281
+ ?.otherPropertyInChain
282
+ ).toBeDefined();
173
283
  });
174
284
 
175
285
  test("should validate data successfully with all extensions", () => {
176
286
  const allExtensions = [
177
- "nts2023", "as", "dr", "jk", "sb", "hs", "oa", "la", "sf", "mc",
178
- "er", "ma", "tf", "sl", "hi", "fd", "oc"
287
+ "nts2023",
288
+ "as",
289
+ "dr",
290
+ "jk",
291
+ "sb",
292
+ "hs",
293
+ "oa",
294
+ "la",
295
+ "sf",
296
+ "mc",
297
+ "er",
298
+ "ma",
299
+ "tf",
300
+ "sl",
301
+ "hi",
302
+ "fd",
303
+ "oc",
179
304
  ];
180
-
305
+
181
306
  const validator = getValidator(schemaId, allExtensions);
182
307
  expect(validator).toBeDefined();
183
308
  expect(typeof validator).toBe("function");
184
-
309
+
185
310
  // Test with minimal valid data structure
186
311
  const testData = {
187
312
  propertyPack: {
@@ -205,11 +330,11 @@ describe("Extension Overlays", () => {
205
330
  dryRotEtcTreatment: { yesNo: "No" },
206
331
  containsAsbestos: { yesNo: "No" },
207
332
  subsidenceOrStructuralFault: { yesNo: "No" },
208
- ongoingHealthOrSafetyIssue: { yesNo: "No" }
209
- }
210
- }
333
+ ongoingHealthOrSafetyIssue: { yesNo: "No" },
334
+ },
335
+ },
211
336
  };
212
-
337
+
213
338
  // This might not validate due to missing required fields, but validator should not throw
214
339
  expect(() => validator(testData)).not.toThrow();
215
340
  });
@@ -218,8 +343,9 @@ describe("Extension Overlays", () => {
218
343
  describe("Extension merging behavior", () => {
219
344
  test("should preserve required arrays when merging extensions", () => {
220
345
  const schema = getTransactionSchema(schemaId, ["nts2023", "jk", "as"]);
221
- const siSection = schema.properties?.propertyPack?.properties?.specialistIssues;
222
-
346
+ const siSection =
347
+ schema.properties?.propertyPack?.properties?.specialistIssues;
348
+
223
349
  expect(siSection?.required).toBeDefined();
224
350
  expect(siSection.required).toContain("japaneseKnotweed");
225
351
  expect(siSection.required).toContain("containsAsbestos");
@@ -227,8 +353,10 @@ describe("Extension Overlays", () => {
227
353
 
228
354
  test("should handle discriminator patterns correctly", () => {
229
355
  const schema = getTransactionSchema(schemaId, ["nts2023", "jk"]);
230
- const jkProp = schema.properties?.propertyPack?.properties?.specialistIssues?.properties?.japaneseKnotweed;
231
-
356
+ const jkProp =
357
+ schema.properties?.propertyPack?.properties?.specialistIssues
358
+ ?.properties?.japaneseKnotweed;
359
+
232
360
  expect(jkProp?.discriminator).toBeDefined();
233
361
  expect(jkProp.discriminator.propertyName).toBe("yesNo");
234
362
  expect(jkProp.oneOf).toBeDefined();
@@ -237,17 +365,78 @@ describe("Extension Overlays", () => {
237
365
 
238
366
  test("should merge oneOf schemas correctly", () => {
239
367
  const schema = getTransactionSchema(schemaId, ["nts2023", "tf"]);
240
- const tfProp = schema.properties?.propertyPack?.properties?.ownership?.properties?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties?.leaseholdInformation?.properties?.serviceCharge?.oneOf?.[1]?.properties?.transferFees;
241
-
368
+ const tfProp =
369
+ schema.properties?.propertyPack?.properties?.ownership?.properties
370
+ ?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties
371
+ ?.leaseholdInformation?.properties?.serviceCharge?.oneOf?.[1]
372
+ ?.properties?.transferFees;
373
+
242
374
  expect(tfProp?.oneOf).toBeDefined();
243
375
  expect(tfProp.oneOf.length).toBeGreaterThan(0);
244
-
376
+
245
377
  // Check the structure - details should be in the conditional oneOf branch
246
- const conditionalBranch = tfProp.oneOf.find(branch =>
247
- branch.properties?.details || branch.required?.includes("details")
378
+ const conditionalBranch = tfProp.oneOf.find(
379
+ (branch) =>
380
+ branch.properties?.details || branch.required?.includes("details")
248
381
  );
249
382
  expect(conditionalBranch).toBeDefined();
250
- expect(conditionalBranch.properties?.details || conditionalBranch.required?.includes("details")).toBeTruthy();
383
+ expect(
384
+ conditionalBranch.properties?.details ||
385
+ conditionalBranch.required?.includes("details")
386
+ ).toBeTruthy();
387
+ });
388
+
389
+ test("should not require additional fields when 'No' is selected in extension overlays", () => {
390
+ // Test the 'as' (additional searches) extension overlay
391
+ const schema = getTransactionSchema(schemaId, ["as"]);
392
+
393
+ const containsAsbestos =
394
+ schema.properties?.propertyPack?.properties?.specialistIssues
395
+ ?.properties?.containsAsbestos;
396
+
397
+ expect(containsAsbestos).toBeDefined();
398
+ expect(containsAsbestos.discriminator?.propertyName).toBe("yesNo");
399
+ expect(containsAsbestos.oneOf).toBeDefined();
400
+ expect(containsAsbestos.oneOf).toHaveLength(2); // Both "No" and "Yes" branches
401
+
402
+ // Find the "No" branch - it should not have additional required fields
403
+ const noBranch = containsAsbestos.oneOf.find((branch) =>
404
+ branch.properties?.yesNo?.enum?.includes("No")
405
+ );
406
+ expect(noBranch).toBeDefined();
407
+ expect(noBranch.properties.yesNo.enum).toEqual(["No"]);
408
+ // The "No" branch should not have additional required fields beyond "yesNo"
409
+ expect(noBranch.required).toBeUndefined();
410
+
411
+ // Find the "Yes" branch - it should have additional required fields
412
+ const yesBranch = containsAsbestos.oneOf.find((branch) =>
413
+ branch.properties?.yesNo?.enum?.includes("Yes")
414
+ );
415
+ expect(yesBranch).toBeDefined();
416
+ expect(yesBranch.properties.yesNo.enum).toEqual(["Yes"]);
417
+ expect(yesBranch.required).toBeDefined();
418
+ expect(yesBranch.required).toContain("details");
419
+
420
+ // Test that data with "No" answer validates correctly using subschema validation
421
+ const { getSubschemaValidator } = require("../../../index.js");
422
+ const subschemaValidator = getSubschemaValidator(
423
+ "/propertyPack/specialistIssues/containsAsbestos",
424
+ schemaId,
425
+ ["as"]
426
+ );
427
+
428
+ const validDataWithNo = {
429
+ yesNo: "No",
430
+ };
431
+
432
+ const result = subschemaValidator(validDataWithNo);
433
+ // Handle both boolean and object return types
434
+ if (typeof result === "boolean") {
435
+ expect(result).toBe(true);
436
+ } else {
437
+ expect(result.valid).toBe(true);
438
+ expect(result.errors).toHaveLength(0);
439
+ }
251
440
  });
252
441
  });
253
- });
442
+ });