Skip to content

Commit

Permalink
feat(predicates): Predicates help to ensure strong domain models that…
Browse files Browse the repository at this point in the history
… can guarantee their correctnes
  • Loading branch information
jan-molak committed Feb 19, 2018
1 parent 738ee00 commit 94915c3
Show file tree
Hide file tree
Showing 48 changed files with 1,246 additions and 171 deletions.
1 change: 1 addition & 0 deletions spec/TinyType.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { expect } from './expect';
/** @test {TinyType} */
describe('TinyType', () => {

/** @test {TinyType} */
describe('definition', () => {

/** @test {TinyTypeOf} */
Expand Down
15 changes: 15 additions & 0 deletions spec/chec.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'mocha';
import { given } from 'mocha-testdata';

import { check, hasLengthOf, isArray, isDefined, isGreaterThan, isInteger, TinyType } from '../src';
import { expect } from './expect';

/** @test {check} */
describe('::check', () => {

it(`advises the developer if they've forgotten to specify the checks`, () => {
const value = 2;
expect(() => check('SomeProperty', value))
.to.throw(`Looks like you haven't specified any predicates to check the value of SomeProperty against?`);
});
});
4 changes: 4 additions & 0 deletions spec/json.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ describe('JSON', () => {
Some_Object = {k1: Some_String, k2: Some_Number},
Some_Array = [Some_String, Some_Number, Some_Boolean, Some_Object];

/** @test {JSONArray} */
describe('JSONArray', () => {
it(`describes an array that's a valid JSON`, () => {
const array: JSONArray = Some_Array;
});
});

/** @test {JSONObject} */
describe('JSONObject', () => {
it(`describes a JavaScript object serialised to JSON`, () => {
const object: JSONObject = {
Expand All @@ -28,6 +30,7 @@ describe('JSON', () => {
});
});

/** @test {JSONPrimitive} */
describe('JSONPrimitive', () => {
it(`describes any primitive that can be part of JSON`, () => {
const s: JSONPrimitive = 'string',
Expand All @@ -37,6 +40,7 @@ describe('JSON', () => {
});
});

/** @test {JSONValue} */
describe('JSONValue', () => {
it('describes any value that can be represented as JSON', () => {
const
Expand Down
3 changes: 2 additions & 1 deletion spec/match.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { match, TinyType } from '../src';
import { IdentityMatcher, ObjectMatcher, StringMatcher } from '../src/pattern-matching';
import { expect } from './expect';

describe(`match`, () => {
/** @test {match} */
describe(`::match`, () => {

abstract class DomainEvent {
constructor(public readonly timestamp: Date = new Date()) {
Expand Down
78 changes: 40 additions & 38 deletions spec/pattern-matching/IdentityMatcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,45 @@ import { given } from 'mocha-testdata';
import { IdentityMatcher } from '../../src/pattern-matching';
import { expect } from '../expect';

describe('IdentityMatcher', () => {
given(
[true, 'received "true"'],
[false, 'received "false"'],
).it('matches a boolean', (input: boolean, expected_result: string) => {

const result = new IdentityMatcher(input)
.when(true, _ => `received "true"`)
.else(_ => `received "false"`);

expect(result).to.equal(expected_result);
});

given(
[-1, 'received "-1"'],
[0.1, 'received "0.1"'],
[5, 'else, received "5"'],
// [NaN, 'received "NaN"'],
[Infinity, 'to infinity and beyond!'],
).it('matches a number', (input: number, expected_result: string) => {

const result = new IdentityMatcher(input)
.when(-1, _ => `received "-1"`)
.when(0.1, _ => `received "0.1"`)
.when(Infinity, _ => `to infinity and beyond!`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal(expected_result);
});

it('matches a symbol', () => {
const s = Symbol('some symbol');

const result = new IdentityMatcher(s)
.when(s, _ => `received "some symbol"`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal('received "some symbol"');
describe('pattern-matching', () => {
describe('IdentityMatcher', () => {
given(
[true, 'received "true"'],
[false, 'received "false"'],
).it('matches a boolean', (input: boolean, expected_result: string) => {

const result = new IdentityMatcher(input)
.when(true, _ => `received "true"`)
.else(_ => `received "false"`);

expect(result).to.equal(expected_result);
});

given(
[-1, 'received "-1"'],
[0.1, 'received "0.1"'],
[5, 'else, received "5"'],
// [NaN, 'received "NaN"'],
[Infinity, 'to infinity and beyond!'],
).it('matches a number', (input: number, expected_result: string) => {

const result = new IdentityMatcher(input)
.when(-1, _ => `received "-1"`)
.when(0.1, _ => `received "0.1"`)
.when(Infinity, _ => `to infinity and beyond!`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal(expected_result);
});

it('matches a symbol', () => {
const s = Symbol('some symbol');

const result = new IdentityMatcher(s)
.when(s, _ => `received "some symbol"`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal('received "some symbol"');
});
});
});
228 changes: 113 additions & 115 deletions spec/pattern-matching/ObjectMatcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,134 +5,132 @@ import { TinyType } from '../../src';
import { ObjectMatcher } from '../../src/pattern-matching';
import { expect } from '../expect';

describe('ObjectMatcher', () => {
describe('pattern-matching', () => {
describe('ObjectMatcher', () => {

describe('when working with Tiny Types', () => {
describe('when working with Tiny Types', () => {

class Name extends TinyType {
constructor(public readonly value: string) {
super();
class Name extends TinyType {
constructor(public readonly value: string) {
super();
}
}
}
class EmailAddress extends TinyType {
constructor(public readonly value: string) {
super();
}
}

given(
[ new Name('Jan'), `matched "Jan"` ],
[ new Name('John'), `matched "John"` ],
[ new Name('Sara'), `else, received "Name(value=Sara)"` ],
).
it('matches equal objects', (input: Name, expectedMessage: string) => {
const result = new ObjectMatcher<TinyType, string>(input)
.when(new Name('Jan'), _ => `matched "Jan"`)
.when(new Name('John'), _ => `matched "John"`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal(expectedMessage);
});

it('matches identical objects', () => {
const input = { field: 'value' };

const result = new ObjectMatcher(input)
.when(input, _ => `matched by identity`)
.else(_ => `else, received "${_}"`);
class EmailAddress extends TinyType {
constructor(public readonly value: string) {
super();
}
}

expect(result).to.equal(`matched by identity`);
given(
[new Name('Jan'), `matched "Jan"`],
[new Name('John'), `matched "John"`],
[new Name('Sara'), `else, received "Name(value=Sara)"`],
).it('matches equal objects', (input: Name, expectedMessage: string) => {
const result = new ObjectMatcher<TinyType, string>(input)
.when(new Name('Jan'), _ => `matched "Jan"`)
.when(new Name('John'), _ => `matched "John"`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal(expectedMessage);
});

it('matches identical objects', () => {
const input = {field: 'value'};

const result = new ObjectMatcher(input)
.when(input, _ => `matched by identity`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal(`matched by identity`);
});

given(
[new Name('Jan'), `matched by equality`],
[new Name('John'), `matched by type`],
[new EmailAddress('jan@example.com'), `else, received "EmailAddress(value=jan@example.com)"`],
).it('can be mixed', (input: Name, expectedMessage: string) => {
const result = new ObjectMatcher<TinyType, string>(input)
.when(new Name('Jan'), _ => `matched by equality`)
.when(Name, _ => `matched by type`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal(expectedMessage);
});
});

given(
[ new Name('Jan'), `matched by equality` ],
[ new Name('John'), `matched by type` ],
[ new EmailAddress('jan@example.com'), `else, received "EmailAddress(value=jan@example.com)"` ],
).
it('can be mixed', (input: Name, expectedMessage: string) => {
const result = new ObjectMatcher<TinyType, string>(input)
.when(new Name('Jan'), _ => `matched by equality`)
.when(Name, _ => `matched by type`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal(expectedMessage);
});
});
describe('when working with regular classes', () => {
abstract class DomainEvent {
constructor(public readonly timestamp: Date) {
}
}

describe('when working with regular classes', () => {
abstract class DomainEvent {
constructor(public readonly timestamp: Date) {
class AccountCreated extends DomainEvent {
constructor(public readonly account_name: string, timestamp: Date) {
super(timestamp);
}
}
}

class AccountCreated extends DomainEvent {
constructor(public readonly account_name: string, timestamp: Date) {
super(timestamp);
class AccountConfirmed extends DomainEvent {
constructor(public readonly account_name: string,
public readonly email: string,
timestamp: Date,) {
super(timestamp);
}
}
}

class AccountConfirmed extends DomainEvent {
constructor(public readonly account_name: string,
public readonly email: string,
timestamp: Date,
) {
super(timestamp);

class UnclassifiedEvent extends DomainEvent {
}
}

class UnclassifiedEvent extends DomainEvent {
}

given(
[
new AccountCreated('jan-molak', new Date()),
`AccountCreated`,
],
[
new AccountConfirmed('jan-molak', 'jan.molak@serenity.io', new Date()),
`AccountConfirmed`,
],
[
new UnclassifiedEvent(new Date()),
`UnclassifiedEvent`,
],
).
it('matches object by constructor function', (input: DomainEvent, expected_result: string) => {

const result = new ObjectMatcher<DomainEvent, string>(input)
.when(AccountCreated, _ => `AccountCreated`)
.when(AccountConfirmed, _ => `AccountConfirmed`)
.when(DomainEvent, _ => `UnclassifiedEvent`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal(expected_result);
});

// todo: mixed constructor/tiny?

given(
[
new AccountCreated('jan-molak', new Date()),
`Account created for jan-molak`,
],
[
new AccountConfirmed('jan-molak', 'jan.molak@serenity.io', new Date()),
`Account confirmed for jan-molak at jan.molak@serenity.io`,
],
[
new UnclassifiedEvent(new Date()),
`Some DomainEvent received`,
],
).
it('matches object by constructor function', (input: DomainEvent, expected_result: string) => {

const result = new ObjectMatcher<DomainEvent, string>(input)
.when(AccountCreated, ({ account_name }) => `Account created for ${ account_name }`)
.when(AccountConfirmed, ({ account_name, email }) => `Account confirmed for ${ account_name } at ${ email }`)
.when(DomainEvent, ({ timestamp }) => `Some DomainEvent received`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal(expected_result);
given(
[
new AccountCreated('jan-molak', new Date()),
`AccountCreated`,
],
[
new AccountConfirmed('jan-molak', 'jan.molak@serenity.io', new Date()),
`AccountConfirmed`,
],
[
new UnclassifiedEvent(new Date()),
`UnclassifiedEvent`,
],
).it('matches object by constructor function', (input: DomainEvent, expected_result: string) => {

const result = new ObjectMatcher<DomainEvent, string>(input)
.when(AccountCreated, _ => `AccountCreated`)
.when(AccountConfirmed, _ => `AccountConfirmed`)
.when(DomainEvent, _ => `UnclassifiedEvent`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal(expected_result);
});

// todo: mixed constructor/tiny?

given(
[
new AccountCreated('jan-molak', new Date()),
`Account created for jan-molak`,
],
[
new AccountConfirmed('jan-molak', 'jan.molak@serenity.io', new Date()),
`Account confirmed for jan-molak at jan.molak@serenity.io`,
],
[
new UnclassifiedEvent(new Date()),
`Some DomainEvent received`,
],
).it('matches object by constructor function', (input: DomainEvent, expected_result: string) => {

const result = new ObjectMatcher<DomainEvent, string>(input)
.when(AccountCreated, ({account_name}) => `Account created for ${ account_name }`)
.when(AccountConfirmed, ({account_name, email}) => `Account confirmed for ${ account_name } at ${ email }`)
.when(DomainEvent, ({timestamp}) => `Some DomainEvent received`)
.else(_ => `else, received "${_}"`);

expect(result).to.equal(expected_result);
});
});
});
});
Loading

0 comments on commit 94915c3

Please sign in to comment.