@pdtf/schemas 3.6.0-2 → 3.6.0-21

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 (35) hide show
  1. package/.claude/settings.local.json +17 -2
  2. package/docs/sef25-extensions-ui-spec.md +146 -0
  3. package/index.js +97 -58
  4. package/package.json +4 -2
  5. package/src/schemas/v3/combined.json +7013 -1313
  6. package/src/schemas/v3/compactSkeleton.txt +353 -11
  7. package/src/schemas/v3/overlays/baspi4.json +161 -17
  8. package/src/schemas/v3/overlays/baspi5.json +166 -19
  9. package/src/schemas/v3/overlays/extensions/jk.json +24 -6
  10. package/src/schemas/v3/overlays/extensions/pc.json +36 -0
  11. package/src/schemas/v3/overlays/extensions/ph.json +197 -0
  12. package/src/schemas/v3/overlays/extensions/sc.json +83 -0
  13. package/src/schemas/v3/overlays/fme1.json +16 -14
  14. package/src/schemas/v3/overlays/lpe1.json +60 -30
  15. package/src/schemas/v3/overlays/nts.json +68 -1
  16. package/src/schemas/v3/overlays/nts2.json +68 -1
  17. package/src/schemas/v3/overlays/ntsl.json +68 -1
  18. package/src/schemas/v3/overlays/ntsl2.json +68 -1
  19. package/src/schemas/v3/overlays/piq.json +38 -1
  20. package/src/schemas/v3/overlays/rds.json +118 -6
  21. package/src/schemas/v3/overlays/ta6.json +40 -14
  22. package/src/schemas/v3/overlays/ta6ed6.json +6436 -0
  23. package/src/schemas/v3/overlays/ta7.json +70 -6
  24. package/src/schemas/v3/overlays/ta7ed5.json +1539 -0
  25. package/src/schemas/v3/pdtf-transaction.json +4098 -615
  26. package/src/schemas/v3/skeleton.json +491 -34
  27. package/src/tests/v3/buyerCircumstances.test.js +543 -0
  28. package/src/tests/v3/extensionOverlays.test.js +436 -0
  29. package/src/tests/v3/offers.test.js +37 -40
  30. package/src/tests/v3/patternProperties.test.js +149 -30
  31. package/src/tests/v3/ta6ed6.test.js +2356 -0
  32. package/src/tests/v3/ta7ed5.test.js +567 -0
  33. package/src/utils/extractExtensionOverlays.js +104 -31
  34. package/src/utils/extractOverlay.js +87 -23
  35. package/src/utils/pathSkeleton.js +1 -1
@@ -0,0 +1,567 @@
1
+ const {
2
+ getTransactionSchema,
3
+ getSubschemaValidator,
4
+ } = require("../../../index.js");
5
+
6
+ const schemaId =
7
+ "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
8
+
9
+ const overlays = ["ta7ed5"];
10
+
11
+ // Helper: navigate to a deeply nested property in the merged schema
12
+ const getSchemaProperty = (schema, dotPath) => {
13
+ return dotPath.split(".").reduce((obj, key) => obj?.[key], schema);
14
+ };
15
+
16
+ // Base path to leasehold information (through the ownership discriminator)
17
+ // The /0/ element navigates through the array type to items, then
18
+ // leaseholdInformation is found in the Leasehold oneOf branch.
19
+ const leaseholdBase =
20
+ "/propertyPack/ownership/ownershipsToBeTransferred/0/leaseholdInformation";
21
+
22
+ describe("TA7 5th edition overlay", () => {
23
+ let schema;
24
+
25
+ beforeAll(() => {
26
+ schema = getTransactionSchema(schemaId, overlays);
27
+ });
28
+
29
+ // ---------------------------------------------------------------
30
+ // Schema structure tests
31
+ // ---------------------------------------------------------------
32
+
33
+ describe("overlay structure", () => {
34
+ test("merged schema requires address and ownership at propertyPack level", () => {
35
+ const required = schema.properties?.propertyPack?.required;
36
+ expect(required).toContain("address");
37
+ expect(required).toContain("ownership");
38
+ });
39
+
40
+ test("leasehold branch requires the 15 ta7ed5 sections", () => {
41
+ const leaseholdOneOf =
42
+ schema.properties?.propertyPack?.properties?.ownership?.properties
43
+ ?.ownershipsToBeTransferred?.items?.oneOf;
44
+
45
+ // Find the Leasehold branch
46
+ const leaseholdBranch = leaseholdOneOf?.find((branch) =>
47
+ branch.properties?.ownershipType?.enum?.includes("Leasehold")
48
+ );
49
+ expect(leaseholdBranch).toBeDefined();
50
+
51
+ const required =
52
+ leaseholdBranch.properties.leaseholdInformation.required;
53
+ expect(required).toContain("typeOfLeasehold");
54
+ expect(required).toContain("contactDetails");
55
+ expect(required).toContain("ownershipAndManagement");
56
+ expect(required).toContain("serviceCharge");
57
+ expect(required).toContain("disputes");
58
+ expect(required).toContain("buildingSafetyAct");
59
+ expect(required).toContain("enfranchisement");
60
+ expect(required).toContain("requiredDocuments");
61
+ expect(required).toContain("additionalInformation");
62
+ expect(required).toContain("confirmationOfAccuracy");
63
+ });
64
+ });
65
+
66
+ // ---------------------------------------------------------------
67
+ // typeOfLeasehold discriminator (section 2.1)
68
+ // ---------------------------------------------------------------
69
+
70
+ describe("typeOfLeasehold discriminator", () => {
71
+ const path = `${leaseholdBase}/typeOfLeasehold`;
72
+
73
+ test("valid with a standard leasehold type", () => {
74
+ const validator = getSubschemaValidator(path, schemaId, overlays);
75
+ expect(validator({ leaseholdType: "Flat" })).toBe(true);
76
+ });
77
+
78
+ test("valid with Other when otherLeaseholdType provided", () => {
79
+ const validator = getSubschemaValidator(path, schemaId, overlays);
80
+ expect(
81
+ validator({
82
+ leaseholdType: "Other",
83
+ otherLeaseholdType: "Live/work unit",
84
+ })
85
+ ).toBe(true);
86
+ });
87
+
88
+ test("invalid with Other when otherLeaseholdType missing", () => {
89
+ const validator = getSubschemaValidator(path, schemaId, overlays);
90
+ expect(validator({ leaseholdType: "Other" })).toBe(false);
91
+ const err = validator.errors.find((e) =>
92
+ e.message.includes("otherLeaseholdType")
93
+ );
94
+ expect(err).toBeDefined();
95
+ });
96
+
97
+ test("invalid when leaseholdType missing entirely", () => {
98
+ const validator = getSubschemaValidator(path, schemaId, overlays);
99
+ expect(validator({})).toBe(false);
100
+ });
101
+
102
+ test("invalid with unrecognised enum value", () => {
103
+ const validator = getSubschemaValidator(path, schemaId, overlays);
104
+ expect(validator({ leaseholdType: "Bungalow" })).toBe(false);
105
+ });
106
+ });
107
+
108
+ // ---------------------------------------------------------------
109
+ // Managing agent contact required fields (section 4.1)
110
+ // ---------------------------------------------------------------
111
+
112
+ describe("managingAgent contact required fields", () => {
113
+ const path = `${leaseholdBase}/contactDetails/contacts/managingAgent`;
114
+
115
+ test("valid managing agent with all required fields", () => {
116
+ const validator = getSubschemaValidator(path, schemaId, overlays);
117
+ expect(
118
+ validator({
119
+ contact: {
120
+ nameOrOrganisation: "ABC Property Management",
121
+ address: { line1: "1 High St", postcode: "SW1A 1AA" },
122
+ emailAddress: "info@abc.co.uk",
123
+ },
124
+ })
125
+ ).toBe(true);
126
+ });
127
+
128
+ test("invalid when contact is missing", () => {
129
+ const validator = getSubschemaValidator(path, schemaId, overlays);
130
+ expect(validator({})).toBe(false);
131
+ const err = validator.errors.find((e) =>
132
+ e.message.includes("contact")
133
+ );
134
+ expect(err).toBeDefined();
135
+ });
136
+
137
+ test("invalid when contact.emailAddress is missing", () => {
138
+ const validator = getSubschemaValidator(path, schemaId, overlays);
139
+ expect(
140
+ validator({
141
+ contact: {
142
+ nameOrOrganisation: "ABC Property Management",
143
+ address: { line1: "1 High St", postcode: "SW1A 1AA" },
144
+ },
145
+ })
146
+ ).toBe(false);
147
+ const err = validator.errors.find((e) =>
148
+ e.message.includes("emailAddress")
149
+ );
150
+ expect(err).toBeDefined();
151
+ });
152
+
153
+ test("invalid when contact.nameOrOrganisation is missing", () => {
154
+ const validator = getSubschemaValidator(path, schemaId, overlays);
155
+ expect(
156
+ validator({
157
+ contact: {
158
+ address: { line1: "1 High St", postcode: "SW1A 1AA" },
159
+ emailAddress: "info@abc.co.uk",
160
+ },
161
+ })
162
+ ).toBe(false);
163
+ });
164
+
165
+ test("telephone is not required for managing agent contact", () => {
166
+ const validator = getSubschemaValidator(path, schemaId, overlays);
167
+ // Valid without telephone
168
+ expect(
169
+ validator({
170
+ contact: {
171
+ nameOrOrganisation: "ABC Property Management",
172
+ address: { line1: "1 High St", postcode: "SW1A 1AA" },
173
+ emailAddress: "info@abc.co.uk",
174
+ },
175
+ })
176
+ ).toBe(true);
177
+ });
178
+ });
179
+
180
+ // ---------------------------------------------------------------
181
+ // ownershipAndManagement required fields (section 4)
182
+ // ---------------------------------------------------------------
183
+
184
+ describe("ownershipAndManagement required fields", () => {
185
+ const path = `${leaseholdBase}/ownershipAndManagement`;
186
+
187
+ test("requires isManagingAgentEmployed, sellerOwnership, hasTenantCompanyDissolved", () => {
188
+ const validator = getSubschemaValidator(path, schemaId, overlays);
189
+ expect(validator({})).toBe(false);
190
+
191
+ const missingFields = validator.errors
192
+ .filter((e) => e.keyword === "required")
193
+ .map((e) => e.params.missingProperty);
194
+
195
+ expect(missingFields).toContain("isManagingAgentEmployed");
196
+ expect(missingFields).toContain("sellerOwnership");
197
+ expect(missingFields).toContain("hasTenantCompanyDissolved");
198
+ });
199
+
200
+ test("isManagingAgentEmployed accepts Not applicable", () => {
201
+ const validator = getSubschemaValidator(path, schemaId, overlays);
202
+ validator({
203
+ isManagingAgentEmployed: "Not applicable",
204
+ sellerOwnership: { yesNo: "No" },
205
+ hasTenantCompanyDissolved: "No",
206
+ });
207
+ // Should not have enum errors for isManagingAgentEmployed
208
+ const enumErr = (validator.errors || []).find(
209
+ (e) =>
210
+ e.instancePath === "/isManagingAgentEmployed" &&
211
+ e.keyword === "enum"
212
+ );
213
+ expect(enumErr).toBeUndefined();
214
+ });
215
+ });
216
+
217
+ // ---------------------------------------------------------------
218
+ // collectsGroundRent discriminator (section 4.3)
219
+ // ---------------------------------------------------------------
220
+
221
+ describe("collectsGroundRent discriminator", () => {
222
+ const path = `${leaseholdBase}/contactDetails/serviceContactAssignments/collectsGroundRent`;
223
+
224
+ test("requires collector field", () => {
225
+ const validator = getSubschemaValidator(path, schemaId, overlays);
226
+ expect(validator({})).toBe(false);
227
+ const err = validator.errors.find(
228
+ (e) =>
229
+ e.keyword === "required" &&
230
+ e.params.missingProperty === "collector"
231
+ );
232
+ expect(err).toBeDefined();
233
+ });
234
+
235
+ test("valid with Landlord (no otherCollector needed)", () => {
236
+ const validator = getSubschemaValidator(path, schemaId, overlays);
237
+ expect(validator({ collector: "Landlord" })).toBe(true);
238
+ });
239
+
240
+ test("valid with Management Company (no otherCollector needed)", () => {
241
+ const validator = getSubschemaValidator(path, schemaId, overlays);
242
+ expect(validator({ collector: "Management Company" })).toBe(true);
243
+ });
244
+
245
+ test("invalid with Other when otherCollector is missing", () => {
246
+ const validator = getSubschemaValidator(path, schemaId, overlays);
247
+ expect(validator({ collector: "Other" })).toBe(false);
248
+ const err = validator.errors.find(
249
+ (e) =>
250
+ e.keyword === "required" &&
251
+ e.params.missingProperty === "otherCollector"
252
+ );
253
+ expect(err).toBeDefined();
254
+ });
255
+
256
+ test("valid with Other when otherCollector is provided", () => {
257
+ const validator = getSubschemaValidator(path, schemaId, overlays);
258
+ expect(
259
+ validator({ collector: "Other", otherCollector: "The Crown Estate" })
260
+ ).toBe(true);
261
+ });
262
+
263
+ test("invalid with Other when otherCollector is empty string", () => {
264
+ const validator = getSubschemaValidator(path, schemaId, overlays);
265
+ expect(
266
+ validator({ collector: "Other", otherCollector: "" })
267
+ ).toBe(false);
268
+ });
269
+
270
+ test("invalid with unrecognised enum value", () => {
271
+ const validator = getSubschemaValidator(path, schemaId, overlays);
272
+ expect(validator({ collector: "The Pope" })).toBe(false);
273
+ });
274
+
275
+ test("invalid when collector is wrong type", () => {
276
+ const validator = getSubschemaValidator(path, schemaId, overlays);
277
+ expect(validator({ collector: 42 })).toBe(false);
278
+ });
279
+
280
+ test("ta7ed5 overlay enum has exactly 3 values", () => {
281
+ const leaseholdOneOf =
282
+ schema.properties?.propertyPack?.properties?.ownership?.properties
283
+ ?.ownershipsToBeTransferred?.items?.oneOf;
284
+ const leaseholdBranch = leaseholdOneOf?.find((branch) =>
285
+ branch.properties?.ownershipType?.enum?.includes("Leasehold")
286
+ );
287
+ const collector =
288
+ leaseholdBranch?.properties?.leaseholdInformation?.properties
289
+ ?.contactDetails?.properties?.serviceContactAssignments?.properties
290
+ ?.collectsGroundRent?.properties?.collector;
291
+ expect(collector?.enum).toEqual([
292
+ "Landlord",
293
+ "Management Company",
294
+ "Other",
295
+ ]);
296
+ });
297
+ });
298
+
299
+ // ---------------------------------------------------------------
300
+ // collectsbuildingInsurancePremiums discriminator (section 4.2)
301
+ // ---------------------------------------------------------------
302
+
303
+ describe("collectsbuildingInsurancePremiums discriminator", () => {
304
+ const path = `${leaseholdBase}/contactDetails/serviceContactAssignments/collectsbuildingInsurancePremiums`;
305
+
306
+ test("requires collector field", () => {
307
+ const validator = getSubschemaValidator(path, schemaId, overlays);
308
+ expect(validator({})).toBe(false);
309
+ const err = validator.errors.find(
310
+ (e) =>
311
+ e.keyword === "required" &&
312
+ e.params.missingProperty === "collector"
313
+ );
314
+ expect(err).toBeDefined();
315
+ });
316
+
317
+ test("valid with Freeholder (no otherCollector needed)", () => {
318
+ const validator = getSubschemaValidator(path, schemaId, overlays);
319
+ expect(validator({ collector: "Freeholder" })).toBe(true);
320
+ });
321
+
322
+ test("valid with each non-Other enum value", () => {
323
+ const validator = getSubschemaValidator(path, schemaId, overlays);
324
+ for (const value of [
325
+ "Freeholder",
326
+ "Landlord",
327
+ "Management Company",
328
+ "Head Leaseholder",
329
+ "Seller",
330
+ ]) {
331
+ expect(validator({ collector: value })).toBe(true);
332
+ }
333
+ });
334
+
335
+ test("invalid with Other when otherCollector is missing", () => {
336
+ const validator = getSubschemaValidator(path, schemaId, overlays);
337
+ expect(validator({ collector: "Other" })).toBe(false);
338
+ const err = validator.errors.find(
339
+ (e) =>
340
+ e.keyword === "required" &&
341
+ e.params.missingProperty === "otherCollector"
342
+ );
343
+ expect(err).toBeDefined();
344
+ });
345
+
346
+ test("valid with Other when otherCollector is provided", () => {
347
+ const validator = getSubschemaValidator(path, schemaId, overlays);
348
+ expect(
349
+ validator({
350
+ collector: "Other",
351
+ otherCollector: "Residents association",
352
+ })
353
+ ).toBe(true);
354
+ });
355
+
356
+ test("invalid with Other when otherCollector is empty string", () => {
357
+ const validator = getSubschemaValidator(path, schemaId, overlays);
358
+ expect(
359
+ validator({ collector: "Other", otherCollector: "" })
360
+ ).toBe(false);
361
+ });
362
+
363
+ test("invalid with unrecognised enum value", () => {
364
+ const validator = getSubschemaValidator(path, schemaId, overlays);
365
+ expect(validator({ collector: "Managing Agent" })).toBe(false);
366
+ });
367
+
368
+ test("ta7ed5 overlay enum has exactly 6 values", () => {
369
+ const leaseholdOneOf =
370
+ schema.properties?.propertyPack?.properties?.ownership?.properties
371
+ ?.ownershipsToBeTransferred?.items?.oneOf;
372
+ const leaseholdBranch = leaseholdOneOf?.find((branch) =>
373
+ branch.properties?.ownershipType?.enum?.includes("Leasehold")
374
+ );
375
+ const collector =
376
+ leaseholdBranch?.properties?.leaseholdInformation?.properties
377
+ ?.contactDetails?.properties?.serviceContactAssignments?.properties
378
+ ?.collectsbuildingInsurancePremiums?.properties?.collector;
379
+ expect(collector?.enum).toEqual([
380
+ "Freeholder",
381
+ "Landlord",
382
+ "Management Company",
383
+ "Head Leaseholder",
384
+ "Seller",
385
+ "Other",
386
+ ]);
387
+ });
388
+ });
389
+
390
+ // ---------------------------------------------------------------
391
+ // serviceContactAssignments required fields
392
+ // ---------------------------------------------------------------
393
+
394
+ describe("serviceContactAssignments required fields", () => {
395
+ const path = `${leaseholdBase}/contactDetails/serviceContactAssignments`;
396
+
397
+ test("requires collectsGroundRent and collectsbuildingInsurancePremiums", () => {
398
+ const validator = getSubschemaValidator(path, schemaId, overlays);
399
+ expect(validator({})).toBe(false);
400
+
401
+ const missingFields = validator.errors
402
+ .filter((e) => e.keyword === "required")
403
+ .map((e) => e.params.missingProperty);
404
+
405
+ expect(missingFields).toContain("collectsGroundRent");
406
+ expect(missingFields).toContain("collectsbuildingInsurancePremiums");
407
+ });
408
+
409
+ test("valid when both collection fields provided", () => {
410
+ const validator = getSubschemaValidator(path, schemaId, overlays);
411
+ expect(
412
+ validator({
413
+ collectsGroundRent: { collector: "Landlord" },
414
+ collectsbuildingInsurancePremiums: { collector: "Management Company" },
415
+ })
416
+ ).toBe(true);
417
+ });
418
+
419
+ test("invalid when collectsGroundRent is a string instead of object", () => {
420
+ const validator = getSubschemaValidator(path, schemaId, overlays);
421
+ expect(
422
+ validator({
423
+ collectsGroundRent: "Landlord",
424
+ collectsbuildingInsurancePremiums: { collector: "Landlord" },
425
+ })
426
+ ).toBe(false);
427
+ });
428
+ });
429
+
430
+ // ---------------------------------------------------------------
431
+ // Removed properties should not exist
432
+ // ---------------------------------------------------------------
433
+
434
+ describe("removed redundant properties", () => {
435
+ test("isGroundRentCollectedByManagingAgent does not exist in merged schema", () => {
436
+ const leaseholdOneOf =
437
+ schema.properties?.propertyPack?.properties?.ownership?.properties
438
+ ?.ownershipsToBeTransferred?.items?.oneOf;
439
+ const leaseholdBranch = leaseholdOneOf?.find((branch) =>
440
+ branch.properties?.ownershipType?.enum?.includes("Leasehold")
441
+ );
442
+ const serviceContactAssignments =
443
+ leaseholdBranch?.properties?.leaseholdInformation?.properties
444
+ ?.contactDetails?.properties?.serviceContactAssignments;
445
+
446
+ expect(
447
+ serviceContactAssignments?.properties
448
+ ?.isGroundRentCollectedByManagingAgent
449
+ ).toBeUndefined();
450
+ });
451
+
452
+ test("managingAgentArrangesOrCollectsBuildingInsuranceCharges does not exist in merged schema", () => {
453
+ const leaseholdOneOf =
454
+ schema.properties?.propertyPack?.properties?.ownership?.properties
455
+ ?.ownershipsToBeTransferred?.items?.oneOf;
456
+ const leaseholdBranch = leaseholdOneOf?.find((branch) =>
457
+ branch.properties?.ownershipType?.enum?.includes("Leasehold")
458
+ );
459
+ const serviceContactAssignments =
460
+ leaseholdBranch?.properties?.leaseholdInformation?.properties
461
+ ?.contactDetails?.properties?.serviceContactAssignments;
462
+
463
+ expect(
464
+ serviceContactAssignments?.properties
465
+ ?.managingAgentArrangesOrCollectsBuildingInsuranceCharges
466
+ ).toBeUndefined();
467
+ });
468
+
469
+ test("standalone groundRentOtherCollector does not exist (absorbed into collectsGroundRent oneOf)", () => {
470
+ const leaseholdOneOf =
471
+ schema.properties?.propertyPack?.properties?.ownership?.properties
472
+ ?.ownershipsToBeTransferred?.items?.oneOf;
473
+ const leaseholdBranch = leaseholdOneOf?.find((branch) =>
474
+ branch.properties?.ownershipType?.enum?.includes("Leasehold")
475
+ );
476
+ const serviceContactAssignments =
477
+ leaseholdBranch?.properties?.leaseholdInformation?.properties
478
+ ?.contactDetails?.properties?.serviceContactAssignments;
479
+
480
+ expect(
481
+ serviceContactAssignments?.properties?.groundRentOtherCollector
482
+ ).toBeUndefined();
483
+ });
484
+
485
+ test("standalone buildingInsurancePremiumsOtherCollector does not exist (absorbed into collectsbuildingInsurancePremiums oneOf)", () => {
486
+ const leaseholdOneOf =
487
+ schema.properties?.propertyPack?.properties?.ownership?.properties
488
+ ?.ownershipsToBeTransferred?.items?.oneOf;
489
+ const leaseholdBranch = leaseholdOneOf?.find((branch) =>
490
+ branch.properties?.ownershipType?.enum?.includes("Leasehold")
491
+ );
492
+ const serviceContactAssignments =
493
+ leaseholdBranch?.properties?.leaseholdInformation?.properties
494
+ ?.contactDetails?.properties?.serviceContactAssignments;
495
+
496
+ expect(
497
+ serviceContactAssignments?.properties
498
+ ?.buildingInsurancePremiumsOtherCollector
499
+ ).toBeUndefined();
500
+ });
501
+ });
502
+
503
+ // ---------------------------------------------------------------
504
+ // Discriminator structure in the overlay
505
+ // ---------------------------------------------------------------
506
+
507
+ describe("discriminator oneOf structure", () => {
508
+ test("collectsGroundRent has discriminator on collector with two branches", () => {
509
+ const leaseholdOneOf =
510
+ schema.properties?.propertyPack?.properties?.ownership?.properties
511
+ ?.ownershipsToBeTransferred?.items?.oneOf;
512
+ const leaseholdBranch = leaseholdOneOf?.find((branch) =>
513
+ branch.properties?.ownershipType?.enum?.includes("Leasehold")
514
+ );
515
+ const collectsGroundRent =
516
+ leaseholdBranch?.properties?.leaseholdInformation?.properties
517
+ ?.contactDetails?.properties?.serviceContactAssignments?.properties
518
+ ?.collectsGroundRent;
519
+
520
+ expect(collectsGroundRent.discriminator).toEqual({
521
+ propertyName: "collector",
522
+ });
523
+ expect(collectsGroundRent.oneOf).toHaveLength(2);
524
+
525
+ // Branch 1: non-Other values
526
+ const branch1Enum =
527
+ collectsGroundRent.oneOf[0].properties.collector.enum;
528
+ expect(branch1Enum).not.toContain("Other");
529
+
530
+ // Branch 2: Other + required otherCollector
531
+ const branch2 = collectsGroundRent.oneOf[1];
532
+ expect(branch2.properties.collector.enum).toEqual(["Other"]);
533
+ expect(branch2.required).toContain("otherCollector");
534
+ expect(branch2.properties.otherCollector).toBeDefined();
535
+ expect(branch2.properties.otherCollector.type).toBe("string");
536
+ });
537
+
538
+ test("collectsbuildingInsurancePremiums has discriminator on collector with two branches", () => {
539
+ const leaseholdOneOf =
540
+ schema.properties?.propertyPack?.properties?.ownership?.properties
541
+ ?.ownershipsToBeTransferred?.items?.oneOf;
542
+ const leaseholdBranch = leaseholdOneOf?.find((branch) =>
543
+ branch.properties?.ownershipType?.enum?.includes("Leasehold")
544
+ );
545
+ const collectsInsurance =
546
+ leaseholdBranch?.properties?.leaseholdInformation?.properties
547
+ ?.contactDetails?.properties?.serviceContactAssignments?.properties
548
+ ?.collectsbuildingInsurancePremiums;
549
+
550
+ expect(collectsInsurance.discriminator).toEqual({
551
+ propertyName: "collector",
552
+ });
553
+ expect(collectsInsurance.oneOf).toHaveLength(2);
554
+
555
+ // Branch 1: non-Other values (superset of all overlay values)
556
+ const branch1Enum =
557
+ collectsInsurance.oneOf[0].properties.collector.enum;
558
+ expect(branch1Enum).toHaveLength(7);
559
+ expect(branch1Enum).not.toContain("Other");
560
+
561
+ // Branch 2: Other + required otherCollector
562
+ const branch2 = collectsInsurance.oneOf[1];
563
+ expect(branch2.properties.collector.enum).toEqual(["Other"]);
564
+ expect(branch2.required).toContain("otherCollector");
565
+ });
566
+ });
567
+ });