Skip to content

Commit

Permalink
new(bool): Add onlyFalse() and onlyTrue() methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
milesj committed Dec 29, 2019
1 parent 0681970 commit e4ed3bf
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 46 deletions.
5 changes: 4 additions & 1 deletion docs/predicates.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ optimal(
## Boolean

The `bool(default?: boolean)` predicate verifies a value is a boolean. Defaults to `false` but can
be customized with the 1st argument.
be customized with the 1st argument. Boolean builder supports the following additional methods:

- `onlyFalse()` - Validate the value is only ever `false` (or undefined).
- `onlyTrue()` - Validate the value is only ever `true` (or undefined).

```ts
optimal(
Expand Down
37 changes: 37 additions & 0 deletions src/BooleanBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Builder from './Builder';

export default class BooleanBuilder<T extends boolean = boolean> extends Builder<T> {
constructor(defaultValue?: T) {
super('boolean', defaultValue || (false as T));
}

onlyFalse(): BooleanBuilder<false> {
this.defaultValue = false as T;
this.addCheck(this.checkOnlyFalse);

return (this as unknown) as BooleanBuilder<false>;
}

checkOnlyFalse(path: string, value: T) {
if (__DEV__) {
this.invariant(value === false, 'May only be `false`.', path);
}
}

onlyTrue(): BooleanBuilder<true> {
this.defaultValue = true as T;
this.addCheck(this.checkOnlyTrue);

return (this as unknown) as BooleanBuilder<true>;
}

checkOnlyTrue(path: string, value: T) {
if (__DEV__) {
this.invariant(value === true, 'May only be `true`.', path);
}
}
}

export function bool(defaultValue: boolean = false) /* infer */ {
return new BooleanBuilder<boolean>(defaultValue);
}
4 changes: 0 additions & 4 deletions src/Builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,10 +406,6 @@ export default class Builder<T> {
}
}

export function bool(defaultValue: boolean = false) /* infer */ {
return new Builder<boolean>('boolean', defaultValue);
}

export function custom<T>(callback: CustomCallback<T>, defaultValue: T) /* infer */ {
return new Builder<T>('custom', defaultValue).custom(callback);
}
Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
*/

import optimal from './optimal';
import Builder, { bool, custom, func } from './Builder';
import Builder, { custom, func } from './Builder';
import ArrayBuilder, { array } from './ArrayBuilder';
import BooleanBuilder, { bool } from './BooleanBuilder';
import InstanceBuilder, { builder, instance, date, regex } from './InstanceBuilder';
import NumberBuilder, { number } from './NumberBuilder';
import ObjectBuilder, { object, blueprint } from './ObjectBuilder';
Expand Down Expand Up @@ -50,6 +51,7 @@ export {
export {
Builder,
ArrayBuilder,
BooleanBuilder,
InstanceBuilder,
NumberBuilder,
ObjectBuilder,
Expand Down
107 changes: 107 additions & 0 deletions tests/BooleanBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import BooleanBuilder, { bool } from '../src/BooleanBuilder';

describe('BooleanBuilder', () => {
let builder: BooleanBuilder<boolean>;

beforeEach(() => {
builder = bool();
});

describe('bool()', () => {
it('returns a builder', () => {
expect(bool(true)).toBeInstanceOf(BooleanBuilder);
});

it('sets type and default value', () => {
builder = bool(true);

expect(builder.type).toBe('boolean');
expect(builder.defaultValue).toBe(true);
});

it('errors if a non-boolean value is used', () => {
expect(() => {
bool().runChecks(
'key',
// @ts-ignore Test invalid type
123,
{},
);
}).toThrowErrorMatchingSnapshot();
});

it('returns the type alias', () => {
expect(bool().typeAlias()).toBe('boolean');
});
});

describe('onlyFalse()', () => {
it('adds a checker', () => {
builder.onlyFalse();

expect(builder.checks[1]).toEqual({
callback: builder.checkOnlyFalse,
args: [],
});
});

it('errors if value is `true`', () => {
builder.onlyFalse();

expect(() => {
builder.runChecks('key', true, { key: true });
}).toThrowErrorMatchingSnapshot();
});

it('passes if value is `false`', () => {
builder.onlyFalse();

expect(() => {
expect(builder.runChecks('key', false, { key: false })).toBe(false);
}).not.toThrow();
});

it('passes if value is undefined', () => {
builder.onlyFalse();

expect(() => {
expect(builder.runChecks('key', undefined, { key: undefined })).toBe(false);
}).not.toThrow();
});
});

describe('onlyTrue()', () => {
it('adds a checker', () => {
builder.onlyTrue();

expect(builder.checks[1]).toEqual({
callback: builder.checkOnlyTrue,
args: [],
});
});

it('errors if value is `false`', () => {
builder.onlyTrue();

expect(() => {
builder.runChecks('key', false, { key: false });
}).toThrowErrorMatchingSnapshot();
});

it('passes if value is `true`', () => {
builder.onlyTrue();

expect(() => {
expect(builder.runChecks('key', true, { key: true })).toBe(true);
}).not.toThrow();
});

it('passes if value is undefined', () => {
builder.onlyTrue();

expect(() => {
expect(builder.runChecks('key', undefined, { key: undefined })).toBe(true);
}).not.toThrow();
});
});
});
42 changes: 6 additions & 36 deletions tests/Builder.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Builder, { bool, custom, func } from '../src/Builder';
import Builder, { custom, func } from '../src/Builder';

describe('Builder', () => {
let builder: Builder<unknown>;
Expand Down Expand Up @@ -215,16 +215,12 @@ describe('Builder', () => {
describe('custom()', () => {
it('errors if no callback', () => {
// @ts-ignore
expect(() => builder.custom()).toThrow(
'Custom blueprints require a validation function.',
);
expect(() => builder.custom()).toThrow('Custom blueprints require a validation function.');
});

it('errors if callback is not a function', () => {
// @ts-ignore
expect(() => builder.custom(123)).toThrow(
'Custom blueprints require a validation function.',
);
expect(() => builder.custom(123)).toThrow('Custom blueprints require a validation function.');
});
});

Expand Down Expand Up @@ -450,7 +446,9 @@ describe('Builder', () => {
builder.deprecate('Use something else.');
builder.runChecks('key', undefined, {});

expect(console.info).not.toHaveBeenCalledWith('Field "key" is deprecated. Use something else.');
expect(console.info).not.toHaveBeenCalledWith(
'Field "key" is deprecated. Use something else.',
);
});
});
});
Expand Down Expand Up @@ -588,34 +586,6 @@ describe('Builder', () => {
});
});

describe('bool()', () => {
it('returns a builder', () => {
expect(bool(true)).toBeInstanceOf(Builder);
});

it('sets type and default value', () => {
const builder = bool(true);

expect(builder.type).toBe('boolean');
expect(builder.defaultValue).toBe(true);
});

it('errors if a non-boolean value is used', () => {
expect(() => {
bool().runChecks(
'key',
// @ts-ignore Test invalid type
123,
{},
);
}).toThrowErrorMatchingSnapshot();
});

it('returns the type alias', () => {
expect(bool().typeAlias()).toBe('boolean');
});
});

describe('custom()', () => {
it('returns a builder', () => {
expect(custom(() => {}, '')).toBeInstanceOf(Builder);
Expand Down
2 changes: 1 addition & 1 deletion tests/ShapeBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import ShapeBuilder, { shape } from '../src/ShapeBuilder';
import { bool } from '../src/Builder';
import { bool } from '../src/BooleanBuilder';
import { number } from '../src/NumberBuilder';
import { string } from '../src/StringBuilder';

Expand Down
3 changes: 2 additions & 1 deletion tests/UnionBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import UnionBuilder, { union } from '../src/UnionBuilder';
import { bool, custom } from '../src/Builder';
import { custom } from '../src/Builder';
import { array } from '../src/ArrayBuilder';
import { bool } from '../src/BooleanBuilder';
import { object } from '../src/ObjectBuilder';
import { instance } from '../src/InstanceBuilder';
import { number } from '../src/NumberBuilder';
Expand Down
7 changes: 7 additions & 0 deletions tests/__snapshots__/BooleanBuilder.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`BooleanBuilder bool() errors if a non-boolean value is used 1`] = `"Invalid field \\"key\\". Must be a boolean."`;

exports[`BooleanBuilder onlyFalse() errors if value is \`true\` 1`] = `"Invalid field \\"key\\". May only be \`false\`."`;

exports[`BooleanBuilder onlyTrue() errors if value is \`false\` 1`] = `"Invalid field \\"key\\". May only be \`true\`."`;
2 changes: 0 additions & 2 deletions tests/__snapshots__/Builder.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,4 @@ exports[`Builder runChecks() uses custom message 1`] = `"Invalid field \\"key\\"

exports[`Builder xor() errors if no keys are defined 1`] = `"XOR requires a list of field names."`;

exports[`bool() errors if a non-boolean value is used 1`] = `"Invalid field \\"key\\". Must be a boolean."`;

exports[`func() errors if a non-function value is used 1`] = `"Invalid field \\"key\\". Must be a function."`;

0 comments on commit e4ed3bf

Please sign in to comment.