diff --git a/src/doov.ts b/src/doov.ts index 4efa911..5a90f07 100644 --- a/src/doov.ts +++ b/src/doov.ts @@ -131,7 +131,7 @@ export function matchAny(...values: BooleanFunction[]): BooleanFunction { return new BooleanFunction(new NaryMetadata(values.map(value => value.metadata), MATCH_ANY), (obj, ctx) => { return values.some(value => { const v = value.get(obj, ctx); - return v != null ? v : false; + return Boolean(v); }); }); } @@ -140,7 +140,7 @@ export function matchAll(...values: BooleanFunction[]): BooleanFunction { return new BooleanFunction(new NaryMetadata(values.map(value => value.metadata), MATCH_ALL), (obj, ctx) => { return values.every(value => { const v = value.get(obj, ctx); - return v != null ? v : false; + return Boolean(v); }); }); } @@ -149,7 +149,7 @@ export function matchNone(...values: BooleanFunction[]): BooleanFunction { return new BooleanFunction(new NaryMetadata(values.map(value => value.metadata), NONE_MATCH), (obj, ctx) => { return values.every(value => { const v = value.get(obj, ctx); - return v != null ? !v : false; + return !Boolean(v); }); }); } @@ -158,7 +158,7 @@ export function count(...values: BooleanFunction[]): NumberFunction { return new NumberFunction(new NaryMetadata(values.map(value => value.metadata), COUNT), (obj, ctx) => { return values.filter(value => { const v = value.get(obj, ctx); - return v != null ? v : false; + return Boolean(v); }).length; }); } @@ -167,7 +167,7 @@ export function sum(...values: NumberFunction[]): NumberFunction { return new NumberFunction(new NaryMetadata(values.map(value => value.metadata), SUM), (obj, ctx) => { return values.reduce((previous, value) => { const v = value.get(obj, ctx); - return v != null ? previous + v : previous; + return v !== undefined && v !== null && !isNaN(v) ? previous + v : previous; }, 0); }); } diff --git a/src/dsl/lang/BooleanFunction.ts b/src/dsl/lang/BooleanFunction.ts index 06a9d4f..2b4b899 100644 --- a/src/dsl/lang/BooleanFunction.ts +++ b/src/dsl/lang/BooleanFunction.ts @@ -2,7 +2,7 @@ import { condition, Function } from './Function'; import { Context } from '../Context'; import { ContextAccessor } from '../ContextAccessor'; import { UnaryMetadata } from '../meta/UnaryMetadata'; -import { AND, NOT, OR } from './DefaultOperators'; +import { AND, IS_FALSY, IS_TRUTHY, NOT, OR } from './DefaultOperators'; import { BinaryMetadata } from '../meta/BinaryMetadata'; import { ValueMetadata } from '../meta/ValueMetadata'; @@ -11,6 +11,20 @@ export class BooleanFunction extends Function { return new BooleanFunction(accessor.metadata, accessor.get, accessor.set); } + public isFalsy(): BooleanFunction { + return new BooleanFunction( + new UnaryMetadata(this.metadata, IS_FALSY), + condition(this, false, (expr: boolean) => !Boolean(expr), true) + ); + } + + public isTruthy(): BooleanFunction { + return new BooleanFunction( + new UnaryMetadata(this.metadata, IS_TRUTHY), + condition(this, false, (expr: boolean) => Boolean(expr), false) + ); + } + public not(): BooleanFunction { return new BooleanFunction( new UnaryMetadata(this.metadata, NOT), diff --git a/src/dsl/lang/DefaultOperators.ts b/src/dsl/lang/DefaultOperators.ts index 6e54c9e..089b183 100644 --- a/src/dsl/lang/DefaultOperators.ts +++ b/src/dsl/lang/DefaultOperators.ts @@ -6,6 +6,8 @@ export const VALIDATE: Operator = { readable: 'validate' }; export const NOT: Operator = { readable: 'not' }; export const AND: Operator = { readable: 'and' }; export const OR: Operator = { readable: 'or' }; +export const IS_FALSY: Operator = { readable: 'is falsy' }; +export const IS_TRUTHY: Operator = { readable: 'is truthy' }; export const THEN: Operator = { readable: 'then' }; export const ELSE: Operator = { readable: 'else' }; export const FUNCTION: Operator = { readable: 'function' }; diff --git a/test/dsl/lang/BooleanFunction.test.ts b/test/dsl/lang/BooleanFunction.test.ts index 0096708..86fe0e8 100644 --- a/test/dsl/lang/BooleanFunction.test.ts +++ b/test/dsl/lang/BooleanFunction.test.ts @@ -11,12 +11,14 @@ const falseFunction = DOOV.lift(BooleanFunction, false); const nullField = DOOV.lift(BooleanFunction, null as any); const trueField = DOOV.boolean(DOOV.field('user', 'b')); const undefinedField = DOOV.boolean(DOOV.field('user', 'a')); +const falsyField = DOOV.boolean(DOOV.field('user', 'birth')); beforeEach(() => { model = new Model(); user = new User(1); user.name = 'test'; user.b = true; + user.birth = undefined; model.user = user; }); @@ -210,4 +212,25 @@ describe('boolean function with left null with short circuit', () => { it('not null', () => { expect(nullField.not().get(model, new DefaultContext(true))).toEqual(false); }); + + it('is falsy', () => { + expect(falsyField.isFalsy().get(model, new DefaultContext(true))).toEqual(true); + expect( + trueFunction + .not() + .isFalsy() + .get(model, new DefaultContext(true)) + ).toEqual(true); + }); + + it('is truthy', () => { + const context = new DefaultContext(true); + expect(trueFunction.isTruthy().get(model, new DefaultContext(true))).toEqual(true); + expect( + falsyField + .not() + .isTruthy() + .get(model, context) + ).toEqual(true); + }); });