From edfe6acde9e11ec2bfe2ad41aad867daae7041ce Mon Sep 17 00:00:00 2001 From: Fedor Sherbakov Date: Thu, 6 Apr 2023 04:03:29 +0400 Subject: [PATCH] feat: respect context for object's children (#1971) Pass context to nested elements for both `getDefault` and `cast` That solves the issue where conditional defaults of nested elements were not resolving correctly if they had context dependency --- src/object.ts | 8 +++++--- src/schema.ts | 10 ++++++---- test/object.ts | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/object.ts b/src/object.ts index f290aa7a7..4b534e73a 100644 --- a/src/object.ts +++ b/src/object.ts @@ -156,7 +156,7 @@ export default class ObjectSchema< let value = super._cast(_value, options); //should ignore nulls here - if (value === undefined) return this.getDefault(); + if (value === undefined) return this.getDefault(options); if (!this._typeCheck(value)) return value; @@ -320,7 +320,9 @@ export default class ObjectSchema< ); } - protected _getDefault() { + protected _getDefault( + options?: ResolveOptions, + ) { if ('default' in this.spec) { return super._getDefault(); } @@ -334,7 +336,7 @@ export default class ObjectSchema< this._nodes.forEach((key) => { const field = this.fields[key] as any; dft[key] = - field && 'getDefault' in field ? field.getDefault() : undefined; + field && 'getDefault' in field ? field.getDefault(options) : undefined; }); return dft; diff --git a/src/schema.ts b/src/schema.ts index 363bec36f..322edcb3e 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -384,7 +384,7 @@ export default abstract class Schema< return result; } - protected _cast(rawValue: any, _options: CastOptions): any { + protected _cast(rawValue: any, options: CastOptions): any { let value = rawValue === undefined ? rawValue @@ -394,7 +394,7 @@ export default abstract class Schema< ); if (value === undefined) { - value = this.getDefault(); + value = this.getDefault(options); } return value; @@ -607,7 +607,9 @@ export default abstract class Schema< } } - protected _getDefault() { + protected _getDefault( + _options?: ResolveOptions, + ) { let defaultValue = this.spec.default; if (defaultValue == null) { @@ -624,7 +626,7 @@ export default abstract class Schema< // If schema is defaulted we know it's at least not undefined ): TDefault { let schema = this.resolve(options || {}); - return schema._getDefault(); + return schema._getDefault(options); } default(def: Thunk): any { diff --git a/test/object.ts b/test/object.ts index 6aaece3f7..8b859f818 100644 --- a/test/object.ts +++ b/test/object.ts @@ -335,6 +335,53 @@ describe('Object types', () => { other: { x: { b: undefined } }, }); }); + + it('should pass options to children', () => { + const objectWithConditions = object({ + child: string().when('$variable', { + is: 'foo', + then: (s) => s.default('is foo'), + otherwise: (s) => s.default('not foo'), + }), + }); + + expect( + objectWithConditions.getDefault({ context: { variable: 'foo' } })) + .toEqual({ child: 'is foo' }, + ); + + expect( + objectWithConditions.getDefault({ context: { variable: 'somethingElse' } })) + .toEqual({ child: 'not foo' }, + ); + + expect( + objectWithConditions.getDefault()) + .toEqual({ child: 'not foo' }, + ); + }); + + it('should respect options when casting to default', () => { + const objectWithConditions = object({ + child: string().when('$variable', { + is: 'foo', + then: (s) => s.default('is foo'), + otherwise: (s) => s.default('not foo'), + }), + }); + + expect( + objectWithConditions.cast(undefined, { context: { variable: 'foo' } }) + ).toEqual({ child: 'is foo' }); + + expect( + objectWithConditions.cast(undefined, { context: { variable: 'somethingElse' } }) + ).toEqual({ child: 'not foo' }); + + expect( + objectWithConditions.cast(undefined) + ).toEqual({ child: 'not foo' }); + }); }); it('should handle empty keys', () => {