├── .gitignore ├── .npmignore ├── .travis.yml ├── .zuul.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── dist ├── bspec.js └── bspec.min.js ├── examples ├── barrier-callback.js ├── barrier-promise.js ├── barrier-sync.js ├── customer.js ├── has-name.js ├── order.js └── passengers.js ├── gulpfile.js ├── index.js ├── lib ├── bspec.js └── detail │ ├── callback.js │ ├── promise.js │ ├── sync.js │ └── util.js ├── package.json └── test ├── and.spec.js ├── composite.spec.js ├── explain.spec.js ├── from-func-callback.spec.js ├── from-func-promise.spec.js ├── from-func-sync.spec.js ├── from-spec-callback.spec.js ├── from-spec-like-callback.spec.js ├── from-spec-like-promise.spec.js ├── from-spec-like-sync.spec.js ├── from-spec-promise.spec.js ├── from-spec-sync.spec.js ├── incomplete.spec.js ├── no-func-name.spec.js ├── not.spec.js └── or.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | .idea 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | .idea 4 | examples 5 | test 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "iojs" 5 | script: 6 | - "npm run coveralls || echo 'not running on coveralls'" 7 | - "test $SAUCE_USERNAME && npm run zuul || echo 'not running on saucelabs'" 8 | - npm test 9 | env: 10 | global: 11 | - secure: X4sfFLSN2ViCFVAsIZ0IzUW6z7H+V0MPiB3iw0wLqEQmuoE1PfIaVHFVXRV61Jy1WpWOJIfPAmC0oVqcQlkStSVxdO8iLcarYA4R2dXaUxvjPWEYigd+mRE5irI2i7fx37XG+2X5Gk7lTOlkOjdhXbdi1/lvjreuEV1JzlXNbgU= 12 | - secure: Xovq8l0iwXjzW2mT6bP28hQgL+sw0cb+jRkzKzL0ZH9rJCGcNKpwn2O2uL01BtwJaISnOBwM2gSpZrcahLpNzVOm+NHL6Rv4d8rhBpB+x0jPPNpJ4vsfgMJtaorCdAeP9zeIgUj0nkVEVaMozpvE6hd2UgNsdvtod4eEht9ukZI= 13 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: mocha-bdd 2 | browsers: 3 | - name: chrome 4 | version: 39..latest 5 | - name: ie 6 | version: 9..latest 7 | - name: firefox 8 | version: 34..latest 9 | - name: safari 10 | version: 6..latest -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.0.1 2 | 3 | - Update dependencies. 4 | 5 | # v1.0.0 6 | 7 | No breaking changes, complying with semver. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Grigoriy Chudnov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![bspec logo](http://i.imgur.com/5zhNRSp.png) 2 | 3 | [![Build Status](https://travis-ci.org/gchudnov/bspec.svg)](https://travis-ci.org/gchudnov/bspec) [![Coverage Status](https://coveralls.io/repos/gchudnov/bspec/badge.svg)](https://coveralls.io/r/gchudnov/bspec) 4 | 5 | [![Sauce Test Status](https://saucelabs.com/browser-matrix/bspec.svg)](https://saucelabs.com/u/bspec) 6 | 7 | > A JavaScript library for structuring business rules. 8 | 9 | ## Overview 10 | 11 | [Structuring and validating business rules in JavaScript](https://medium.com/@gchudnov/structuring-and-validating-business-rules-in-javascript-fbaa1019902a) 12 | 13 | ## Example 14 | 15 | Consider the following scenario: 16 | 17 | On entering a train station, passengers are required to insert a ticket into a slot beside a closed barrier. An entry barrier opens only if the ticket meets all of the following criteria: 18 | 19 | 1. it is valid for travel from that station; 20 | 2. it has not expired; 21 | 3. it has not already been used for the maximum number of journeys allowed. 22 | 23 | ```javascript 24 | 'use strict'; 25 | 26 | // use promise-based specifications 27 | var Spec = require('bspec').PromiseSpec; 28 | 29 | // hardcode the `today` date for the sake of consistent results 30 | var TODAY = new Date(2015, 2, 1); 31 | 32 | // Is the ticket expired? 33 | var isTicketExpired = function isTicketExpired(ticket) { 34 | return Promise.resolve(TODAY > ticket.expires_at); 35 | }; 36 | 37 | // Is the ticket has been used for the maximum number of journeys allowed? 38 | var isMaxJourneys = function isMaxJourneys(ticket) { 39 | return Promise.resolve(ticket.cur_journeys >= ticket.max_journeys); 40 | }; 41 | 42 | // Is the ticket valid for travel from `name` station? 43 | var isValidFromStation = function isValidFromStation(name, ticket) { 44 | return Promise.resolve(ticket.stations.indexOf(name) !== -1); 45 | }; 46 | 47 | // Rule implementation for the `Riva` station barrier 48 | var barrierSpec = Spec(isValidFromStation.bind(null, 'Riva')) 49 | .and(Spec(isTicketExpired).not()) 50 | .and(Spec(isMaxJourneys).not()); 51 | 52 | // A ticket we would like to check 53 | var ticket = { 54 | stations: [ 'Riva' ], 55 | expires_at: new Date(2015, 2, 6), 56 | max_journeys: 30, 57 | cur_journeys: 11 58 | }; 59 | 60 | // Verify the ticket satisfies the created specification 61 | barrierSpec.isSatisfiedBy(ticket) 62 | .then(function(result) { 63 | console.log('Is the ticket can be used to enter the Riva station:', result); 64 | }) 65 | .catch(function(err) { 66 | throw err; 67 | }); 68 | ``` 69 | 70 | ## Installation 71 | 72 | installing with npm: 73 | ```bash 74 | $ npm install bspec --save 75 | ``` 76 | 77 | ## In browser 78 | 79 | To use _bspec_ in a browser, use the `bspec.js` file in the `/dist` directory of this repository, or build it manually. To build a fresh version: 80 | 81 | ```bash 82 | $ npm install 83 | $ npm run browser 84 | ``` 85 | 86 | installing with bower: 87 | ```bash 88 | $ bower install bspec 89 | ``` 90 | 91 | ## Usage 92 | The essential part of the library is a *specification* -- an object with the following properties: 93 | * it can be combined with other *specification*-objects using `.and()`, `.or()` and `.not()` methods to form a composite specification and express more complex rules. 94 | * it implements `isSatisfiedBy` method -- a predicate that determines whether a *candidate* object does or does not satisfy some criteria. 95 | 96 | The library supports the following specifications: 97 | * Synchronous -- `SyncSpec` 98 | * Callback-based -- `CallbackSpec` 99 | * Promise-based -- `PromiseSpec` 100 | 101 | ```javascript 102 | var Spec = require('bspec').SyncSpec; 103 | ``` 104 | ```javascript 105 | var Spec = require('bspec').CallbackSpec; 106 | ``` 107 | ```javascript 108 | var Spec = require('bspec').PromiseSpec; 109 | ``` 110 | 111 | To create a composable *specification*, make an instance of `Spec` and define the `isSatisfiedBy` method to check for some condition. `isSatisfiedBy` method [signature](#issatisfiedby) depends on the type of specification (see the [API](#api) section below). 112 | 113 | There are several ways you can define the `isSatisfiedBy` method: 114 | * Write a predicate-function and wrap it in a `Spec` object; 115 | * Create an object with the `isSatisfiedBy` property and wrap it in a `Spec` object; 116 | * Derive a new object from `Spec` and implement the `isSatisfiedBy` function. 117 | 118 | #### Writing a predicate-function and wrapping it in a `Spec` object 119 | ```javascript 120 | var Spec = require('bspec').SyncSpec; 121 | 122 | function isExpired(order) { 123 | return (new Date() > order.date); 124 | } 125 | 126 | var expiredSpec = new Spec(isExpired); 127 | console.log(expiredSpec.isSatisfiedBy({ date: new Date(2015, 1, 5) })); 128 | ``` 129 | 130 | #### Creating an object with the `isSatisfiedBy` property and wrapping it in a `Spec` 131 | ```javascript 132 | var Spec = require('bspec').SyncSpec; 133 | 134 | var isExpired = { 135 | isSatisfiedBy: function(order) { 136 | return (new Date() > order.date); 137 | } 138 | }; 139 | 140 | var expiredSpec = new Spec(isExpired); 141 | console.log(expiredSpec.isSatisfiedBy({ date: new Date(2015, 1, 5) })); 142 | ``` 143 | 144 | #### Deriving a new object from `Spec` and implementing the `isSatisfiedBy` function 145 | ```javascript 146 | var Spec = require('bspec').SyncSpec; 147 | var util = require('util'); 148 | 149 | function IsExpiredSpec() { } 150 | 151 | util.inherits(IsExpiredSpec, Spec); 152 | 153 | IsExpiredSpec.prototype.isSatisfiedBy = function isSatisfiedBy(order) { 154 | return (new Date() > order.date); 155 | }; 156 | 157 | var expiredSpec = new IsExpiredSpec(); 158 | console.log(expiredSpec.isSatisfiedBy({ date: new Date(2015, 1, 5) })); 159 | ``` 160 | 161 | OR with **ES6 classes**: 162 | ```javascript 163 | var Spec = require('bspec').SyncSpec; 164 | 165 | class IsExpiredSpec extends Spec { 166 | isSatisfiedBy(order) { 167 | return (new Date() > order.date); 168 | } 169 | } 170 | 171 | var expiredSpec = new IsExpiredSpec(); 172 | console.log(expiredSpec.isSatisfiedBy({ date: new Date(2015, 1, 5) })); 173 | ``` 174 | 175 | ## API 176 | 177 | ### .and(otherSpec) 178 | the _and_ of a set of specifications is *true* if and only if all of its operands are *true*. 179 | ```javascript 180 | var spec = spec1.and(spec2); 181 | ``` 182 | 183 | ### .or(otherSpec) 184 | the _or_ of a set of specifications is *true* if and only if one or more of its operands is *true* 185 | ```javascript 186 | var spec = spec1.or(spec2); 187 | ``` 188 | 189 | ### .not() 190 | _not_ negates the specification 191 | ```javascript 192 | var spec = spec1.not(); 193 | ``` 194 | 195 | ### .explain() 196 | returns a string that describes a composite specification 197 | ```javascript 198 | console.log(someSpec.explain()); 199 | // ((ValidOrderSpec AND (NOT OverDueOrderSpec)) AND (NOT OrderProcessed)) 200 | ``` 201 | **NOTE:** meaningful names will be printed only for specifications derived from a `Spec` object. 202 | 203 | ### .isSatisfiedBy(...) 204 | checks whether some _candidate_ object satisfies the specification. 205 | _isSatisfiedBy_ method signature depends on the specification type: 206 | 207 | #### SyncSpec (synchronous specification) 208 | ```javascript 209 | // signature: 210 | function isSatisfiedBy(candidate: any): boolean; 211 | 212 | // usage: 213 | var result = spec.isSatisfiedBy(obj); 214 | 215 | // `result` true|false value 216 | // should throw an exception in case of an error 217 | ``` 218 | 219 | #### CallbackSpec (callback-based specification) 220 | ```javascript 221 | // signature: 222 | function isSatisfiedBy(candidate: any, cb: (err: Error, result: boolean): void): void; 223 | 224 | // usage: 225 | spec.isSatisfiedBy(obj, function(err, result) { 226 | // `err` contains an error if any 227 | // `result` true|false value 228 | }); 229 | ``` 230 | 231 | #### PromiseSpec (promise-based specification) 232 | ```javascript 233 | // signature: 234 | function isSatisfiedBy(candidate: any): Promise; 235 | 236 | // usage: 237 | spec.isSatisfiedBy(obj) 238 | .then(function(result) { 239 | // `result` true|false value 240 | }).catch(function(err) { 241 | // `err` contains an error if any 242 | }); 243 | ``` 244 | **NOTE:** To use [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)-based specifications you need ES6 Promise to be implemented in your environment, e.g. `io.js`, a modern browser or a polyfill like [es6-promise](https://github.com/jakearchibald/es6-promise). 245 | 246 | For the details of usage, take a look at the [examples](/examples) section in the project. 247 | 248 | ## Tests 249 | 250 | To run the tests for _bspec_: 251 | ```bash 252 | $ npm test 253 | ``` 254 | 255 | ## Contact 256 | 257 | [Grigoriy Chudnov] (mailto:g.chudnov@gmail.com) 258 | 259 | 260 | ## License 261 | 262 | Distributed under the [The MIT License (MIT)](https://github.com/gchudnov/bspec/blob/master/LICENSE). 263 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bspec", 3 | "main": "./dist/bspec.js", 4 | "version": "1.0.0", 5 | "homepage": "https://github.com/gchudnov/bspec", 6 | "authors": [ 7 | "Grigoriy Chudnov " 8 | ], 9 | "description": "A JavaScript library for structuring business rules", 10 | "moduleType": [ 11 | "node" 12 | ], 13 | "keywords": [ 14 | "specification", 15 | "ddd", 16 | "pattern", 17 | "business", 18 | "rules" 19 | ], 20 | "license": "MIT", 21 | "ignore": [ 22 | "**/.*", 23 | "coverage", 24 | "node_modules", 25 | "bower_components", 26 | "test", 27 | "tests", 28 | "examples", 29 | "lib", 30 | "gulpfile.js", 31 | "index.js", 32 | "package.json" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /dist/bspec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * bspec - A JavaScript library for structuring business rules 3 | * @version v1.0.1 4 | * @author Grigoriy Chudnov (https://github.com/gchudnov) 5 | * @link https://github.com/gchudnov/bspec 6 | * @license MIT 7 | */ 8 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.bspec = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o (https://github.com/gchudnov) 5 | * @link https://github.com/gchudnov/bspec 6 | * @license MIT 7 | */ 8 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i;i="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,i.bspec=t()}}(function(){return function t(i,n,e){function r(s,u){if(!n[s]){if(!i[s]){var f="function"==typeof require&&require;if(!u&&f)return f(s,!0);if(o)return o(s,!0);var p=new Error("Cannot find module '"+s+"'");throw p.code="MODULE_NOT_FOUND",p}var c=n[s]={exports:{}};i[s][0].call(c.exports,function(t){var n=i[s][1][t];return r(n?n:t)},c,c.exports,t,i,n,e)}return n[s].exports}for(var o="function"==typeof require&&require,s=0;s ticket.expiresAt; 17 | cb(null, result); 18 | }; 19 | 20 | var isMaxJourneys = function isMaxJourneys(ticket, cb) { 21 | var result = ticket.cur_journeys >= ticket.max_journeys; 22 | cb(null, result); 23 | }; 24 | 25 | var isValidFromStation = function isValidFromStation(name, ticket, cb) { 26 | var result = (ticket.stations.indexOf(name) !== -1); 27 | cb(null, result); 28 | }; 29 | 30 | function makeBarrierSpec(stationName) { 31 | return Spec(isValidFromStation.bind(null, stationName)) 32 | .and(Spec(isTicketExpired).not()) 33 | .and(Spec(isMaxJourneys).not()); 34 | } 35 | 36 | var lowangenBarrier = makeBarrierSpec('Lowangen'); 37 | var rivaBarrier = makeBarrierSpec('Riva'); 38 | 39 | var ticket = { 40 | stations: [ 'Lowangen' ], 41 | expiresAt: new Date(2015, 2, 6), 42 | max_journeys: 30, 43 | cur_journeys: 11 44 | }; 45 | 46 | lowangenBarrier.isSatisfiedBy(ticket, function(err, result) { 47 | if(err) { 48 | throw err; 49 | } 50 | console.log('The ticket can be used to enter the Lowangen station:', result); 51 | }); 52 | 53 | rivaBarrier.isSatisfiedBy(ticket, function(err, result) { 54 | if(err) { 55 | throw err; 56 | } 57 | console.log('The ticket can be used to enter the Riva station:', result); 58 | }); 59 | -------------------------------------------------------------------------------- /examples/barrier-promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * An entry barrier opens only if the ticket meets all of the following criteria: 5 | * 6 | * 1. it is valid for travel from that station; 7 | * 2. it has not expired; 8 | * 3. it has not already been used for the maximum number of journeys allowed. 9 | */ 10 | 11 | require('es6-promise').polyfill(); 12 | var Spec = require('./../lib/bspec').PromiseSpec; 13 | 14 | var TODAY = new Date(2015, 2, 1); 15 | 16 | var isTicketExpired = function isTicketExpired(ticket) { 17 | return Promise.resolve(TODAY > ticket.expiresAt); 18 | }; 19 | 20 | var isMaxJourneys = function isMaxJourneys(ticket) { 21 | return Promise.resolve(ticket.cur_journeys >= ticket.max_journeys); 22 | }; 23 | 24 | var isValidFromStation = function isValidFromStation(name, ticket) { 25 | return Promise.resolve(ticket.stations.indexOf(name) !== -1); 26 | }; 27 | 28 | function makeBarrierSpec(stationName) { 29 | return Spec(isValidFromStation.bind(null, stationName)) 30 | .and(Spec(isTicketExpired).not()) 31 | .and(Spec(isMaxJourneys).not()); 32 | } 33 | 34 | var lowangenBarrier = makeBarrierSpec('Lowangen'); 35 | var rivaBarrier = makeBarrierSpec('Riva'); 36 | 37 | var ticket = { 38 | stations: [ 'Lowangen' ], 39 | expiresAt: new Date(2015, 2, 6), 40 | max_journeys: 30, 41 | cur_journeys: 11 42 | }; 43 | 44 | lowangenBarrier.isSatisfiedBy(ticket) 45 | .then(function(result) { 46 | console.log('The ticket can be used to enter the Lowangen station:', result); 47 | }) 48 | .catch(function(err) { 49 | throw err; 50 | }); 51 | 52 | rivaBarrier.isSatisfiedBy(ticket) 53 | .then(function(result) { 54 | console.log('The ticket can be used to enter the Riva station:', result); 55 | }) 56 | .catch(function(err) { 57 | throw err; 58 | }); 59 | -------------------------------------------------------------------------------- /examples/barrier-sync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * An entry barrier opens only if the ticket meets all of the following criteria: 5 | * 6 | * 1. it is valid for travel from that station; 7 | * 2. it has not expired; 8 | * 3. it has not already been used for the maximum number of journeys allowed. 9 | */ 10 | 11 | var Spec = require('./../lib/bspec').SyncSpec; 12 | 13 | var TODAY = new Date(2015, 2, 1); 14 | 15 | var isTicketExpired = function isTicketExpired(ticket) { 16 | return (TODAY > ticket.expiresAt); 17 | }; 18 | 19 | var isMaxJourneys = function isMaxJourneys(ticket) { 20 | return (ticket.cur_journeys >= ticket.max_journeys); 21 | }; 22 | 23 | var isValidFromStation = function isValidFromStation(name, ticket) { 24 | return (ticket.stations.indexOf(name) !== -1); 25 | }; 26 | 27 | function makeBarrierSpec(stationName) { 28 | return Spec(isValidFromStation.bind(null, stationName)) 29 | .and(Spec(isTicketExpired).not()) 30 | .and(Spec(isMaxJourneys).not()); 31 | } 32 | 33 | var lowangenBarrier = makeBarrierSpec('Lowangen'); 34 | var rivaBarrier = makeBarrierSpec('Riva'); 35 | 36 | var ticket = { 37 | stations: [ 'Lowangen' ], 38 | expiresAt: new Date(2015, 2, 6), 39 | max_journeys: 30, 40 | cur_journeys: 11 41 | }; 42 | 43 | console.log('The ticket can be used to enter the Lowangen station:', lowangenBarrier.isSatisfiedBy(ticket)); 44 | console.log('The ticket can be used to enter the Riva station:', rivaBarrier.isSatisfiedBy(ticket)); 45 | -------------------------------------------------------------------------------- /examples/customer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bspec = require('./../lib/bspec'); 4 | 5 | /** 6 | * Constraint that only a customer who has specified a first given name can specify a second given name 7 | */ 8 | var hasFirstName = bspec.SyncSpec(function(customer) { 9 | return !!(customer && customer.first_name); 10 | }); 11 | 12 | var hasSecondName = bspec.SyncSpec(function(customer) { 13 | return !!(customer && customer.second_name); 14 | }); 15 | 16 | var customer1 = { first_name: 'Bob' }; 17 | var customer2 = { second_name: 'Pablo' }; 18 | var customer3 = { first_name: 'Juan', second_name: 'Pablo' }; 19 | 20 | var isCustomerNameValid = (hasSecondName.not()).or(hasFirstName); 21 | 22 | console.log('Only a customer who has specified a first given name can specify a second given name:'); 23 | console.log('customer1 (first_name: `Bob`): ', isCustomerNameValid.isSatisfiedBy(customer1)); // true 24 | console.log('customer2 (second_name: `Pablo`): ', isCustomerNameValid.isSatisfiedBy(customer2)); // false 25 | console.log('customer3 (first_name: `Juan`, second_name: `Pablo`): ', isCustomerNameValid.isSatisfiedBy(customer3)); // true 26 | -------------------------------------------------------------------------------- /examples/has-name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var SyncSpec = require('./../lib/bspec').SyncSpec; 4 | var util = require('util'); 5 | 6 | function HasNameSpec() { } 7 | util.inherits(HasNameSpec, SyncSpec); 8 | 9 | HasNameSpec.prototype.isSatisfiedBy = function(user) { 10 | return user && user.hasOwnProperty('name'); 11 | }; 12 | 13 | var user1 = { name: 'Bob' }; 14 | var user2 = { }; 15 | 16 | var spec = new HasNameSpec(); 17 | 18 | console.log('test that a user has `name` property'); 19 | 20 | console.log('user1 (named Bob): ', spec.isSatisfiedBy(user1)); // true 21 | console.log('user2 (no name property): ', spec.isSatisfiedBy(user2)); // false 22 | -------------------------------------------------------------------------------- /examples/order.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var SyncSpec = require('./../lib/bspec').SyncSpec; 4 | var util = require('util'); 5 | 6 | // is expired 7 | function OrderExpiredSpec() { } 8 | 9 | util.inherits(OrderExpiredSpec, SyncSpec); 10 | 11 | OrderExpiredSpec.prototype.isSatisfiedBy = function isSatisfiedBy(order) { 12 | return (new Date() > order.date); 13 | }; 14 | 15 | // is valid 16 | function OrderValidSpec() { } 17 | 18 | util.inherits(OrderValidSpec, SyncSpec); 19 | 20 | OrderValidSpec.prototype.isSatisfiedBy = function isSatisfiedBy(order) { 21 | return order && order.hasOwnProperty('number') && order.hasOwnProperty('date'); 22 | }; 23 | 24 | // is processed 25 | var processed = {}; 26 | 27 | function OrderProcessed() { } 28 | 29 | util.inherits(OrderProcessed, SyncSpec); 30 | 31 | OrderProcessed.prototype.isSatisfiedBy = function isSatisfiedBy(order) { 32 | return !!processed[order.number]; 33 | }; 34 | 35 | 36 | var isExpired = new OrderExpiredSpec(); 37 | var isValid = new OrderValidSpec(); 38 | var isProcessed = new OrderProcessed(); 39 | 40 | var spec = isValid.and(isExpired.not()).and(isProcessed.not()); 41 | 42 | var order = { number: 'Z10', date: new Date('2014-05-06') }; 43 | 44 | console.log('check that the order is `valid`, `not expired` and `not processed`:'); 45 | 46 | if(spec.isSatisfiedBy(order)) { 47 | console.log(' - true'); 48 | } else { 49 | console.log(' - false'); 50 | } 51 | 52 | // inspect composite specification 53 | console.log('explain composite rule:', spec.explain()); // ((ValidOrderSpec AND (NOT OverDueOrderSpec)) AND (NOT OrderProcessed)) 54 | -------------------------------------------------------------------------------- /examples/passengers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bspec = require('./../lib/bspec'); 4 | 5 | /** 6 | * A method to ensure that the number of passengers specified in a flight booking request is eight or less 7 | */ 8 | var passengersPerRequest = bspec.SyncSpec(function(req) { 9 | return req && req.passenger_count <= 8; 10 | }); 11 | 12 | var req1 = { }; // invalid request 13 | var req2 = { passenger_count: 12 }; 14 | var req3 = { passenger_count: 6 }; 15 | 16 | console.log('Ensure that the number of passengers specified in a flight booking request is eight or less:'); 17 | 18 | console.log('1st request (invalid):', passengersPerRequest.isSatisfiedBy(req1)); // false 19 | console.log('2nd request (12 passengers within):', passengersPerRequest.isSatisfiedBy(req2)); // false 20 | console.log('3rd request (6 passengers):', passengersPerRequest.isSatisfiedBy(req3)); // true 21 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var $ = require('gulp-load-plugins')({lazy: false}); 5 | var browserify = require('browserify'); 6 | var source = require('vinyl-source-stream2'); 7 | var path = require('path'); 8 | 9 | 10 | var pkg = require('./package.json'); 11 | var banner = [ 12 | '/*', 13 | ' * <%= pkg.name %> - <%= pkg.description %>', 14 | ' * @version v<%= pkg.version %>', 15 | ' * @author <%= pkg.author %>', 16 | ' * @link <%= pkg.homepage %>', 17 | ' * @license <%= pkg.license %>', 18 | ' */', 19 | ''].join('\n'); 20 | 21 | gulp.task('script', function() { 22 | var bundleStream = browserify({ 23 | entries: './lib/bspec.js', 24 | builtins: null, 25 | insertGlobals: false, 26 | detectGlobals: false, 27 | standalone: 'bspec', 28 | fullPaths: false 29 | }) 30 | .bundle(); 31 | 32 | return bundleStream 33 | .pipe(source('bspec.js')) 34 | .pipe($.header(banner, {pkg: pkg})) 35 | .pipe(gulp.dest('./dist')) 36 | .pipe($.uglify()) 37 | .pipe($.header(banner, {pkg: pkg})) 38 | .pipe($.rename('bspec.min.js')) 39 | .pipe(gulp.dest('./dist')); 40 | }); 41 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/bspec'); 2 | -------------------------------------------------------------------------------- /lib/bspec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PromiseSpec = require('./detail/promise'); 4 | var CallbackSpec = require('./detail/callback'); 5 | var SyncSpec = require('./detail/sync'); 6 | 7 | module.exports.PromiseSpec = PromiseSpec; 8 | module.exports.CallbackSpec = CallbackSpec; 9 | module.exports.SyncSpec = SyncSpec; 10 | -------------------------------------------------------------------------------- /lib/detail/callback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | 5 | 6 | /** 7 | * Specification base 8 | * @returns {*} 9 | * @constructor 10 | */ 11 | function Spec() { 12 | if(arguments.length) { 13 | return util.ensureSpec(arguments[0], Spec); 14 | } 15 | } 16 | 17 | Spec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate, cb) { 18 | cb(new Error('isSatisfiedBy not implemented')); 19 | }; 20 | 21 | Spec.prototype.and = function and(other) { 22 | other = util.ensureSpec(other, Spec); 23 | return new AndSpec(this, other); 24 | }; 25 | 26 | Spec.prototype.or = function or(other) { 27 | other = util.ensureSpec(other, Spec); 28 | return new OrSpec(this, other); 29 | }; 30 | 31 | Spec.prototype.not = function not() { 32 | return new NotSpec(this); 33 | }; 34 | 35 | Spec.prototype.explain = function explain() { 36 | return util.functionName(this.constructor); 37 | }; 38 | 39 | 40 | /** 41 | * AND Specification 42 | * @param lhs 43 | * @param rhs 44 | * @constructor 45 | */ 46 | function AndSpec(lhs, rhs) { 47 | Spec.call(this); 48 | this.lhs = lhs; 49 | this.rhs = rhs; 50 | } 51 | 52 | util.inherits(AndSpec, Spec); 53 | 54 | AndSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate, cb) { 55 | var self = this; 56 | self.lhs.isSatisfiedBy(candidate, function(err, flag) { 57 | if(err) { 58 | return cb(err); 59 | } 60 | 61 | if(!flag) { 62 | return cb(null, flag); 63 | } 64 | 65 | self.rhs.isSatisfiedBy(candidate, function(err, flag) { 66 | if(err) { 67 | return cb(err); 68 | } 69 | 70 | return cb(null, flag); 71 | }); 72 | }); 73 | }; 74 | 75 | AndSpec.prototype.explain = function explain() { 76 | return '(' + this.lhs.explain() + ' AND ' + this.rhs.explain() + ')'; 77 | }; 78 | 79 | 80 | /** 81 | * OR Specification 82 | * @param lhs 83 | * @param rhs 84 | * @constructor 85 | */ 86 | function OrSpec(lhs, rhs) { 87 | Spec.call(this); 88 | this.lhs = lhs; 89 | this.rhs = rhs; 90 | } 91 | 92 | util.inherits(OrSpec, Spec); 93 | 94 | OrSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate, cb) { 95 | var self = this; 96 | self.lhs.isSatisfiedBy(candidate, function(err, flag) { 97 | if(err) { 98 | return cb(err); 99 | } 100 | 101 | if(flag) { 102 | return cb(null, flag); 103 | } 104 | 105 | self.rhs.isSatisfiedBy(candidate, function(err, flag) { 106 | if(err) { 107 | return cb(err); 108 | } 109 | 110 | return cb(null, flag); 111 | }); 112 | }); 113 | }; 114 | 115 | OrSpec.prototype.explain = function explain() { 116 | return '(' + this.lhs.explain() + ' OR ' + this.rhs.explain() + ')'; 117 | }; 118 | 119 | 120 | /** 121 | * NOT Specification 122 | * @param other 123 | * @constructor 124 | */ 125 | function NotSpec(other) { 126 | Spec.call(this); 127 | this.other = other; 128 | } 129 | 130 | util.inherits(NotSpec, Spec); 131 | 132 | NotSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate, cb) { 133 | this.other.isSatisfiedBy(candidate, function(err, flag) { 134 | if(err) { 135 | return cb(err); 136 | } 137 | 138 | return cb(null, !flag); 139 | }); 140 | }; 141 | 142 | NotSpec.prototype.explain = function explain() { 143 | return '(NOT ' + this.other.explain() + ')'; 144 | }; 145 | 146 | 147 | module.exports = Spec; 148 | -------------------------------------------------------------------------------- /lib/detail/promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | 5 | 6 | /** 7 | * Specification base 8 | * @constructor 9 | */ 10 | function Spec() { 11 | if(arguments.length) { 12 | return util.ensureSpec(arguments[0], Spec); 13 | } 14 | } 15 | 16 | Spec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate) { 17 | return Promise.reject(new Error('some error')); 18 | }; 19 | 20 | Spec.prototype.and = function and(other) { 21 | other = util.ensureSpec(other, Spec); 22 | return new AndSpec(this, other); 23 | }; 24 | 25 | Spec.prototype.or = function or(other) { 26 | other = util.ensureSpec(other, Spec); 27 | return new OrSpec(this, other); 28 | }; 29 | 30 | Spec.prototype.not = function not() { 31 | return new NotSpec(this); 32 | }; 33 | 34 | Spec.prototype.explain = function explain() { 35 | return util.functionName(this.constructor); 36 | }; 37 | 38 | 39 | /** 40 | * AND Specification 41 | * @param lhs 42 | * @param rhs 43 | * @constructor 44 | */ 45 | function AndSpec(lhs, rhs) { 46 | Spec.call(this); 47 | this.lhs = lhs; 48 | this.rhs = rhs; 49 | } 50 | 51 | util.inherits(AndSpec, Spec); 52 | 53 | AndSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate) { 54 | return Promise.all([this.lhs.isSatisfiedBy(candidate), this.rhs.isSatisfiedBy(candidate)]).then(function(values) { 55 | return values[0] && values[1]; 56 | }); 57 | }; 58 | 59 | AndSpec.prototype.explain = function explain() { 60 | return '(' + this.lhs.explain() + ' AND ' + this.rhs.explain() + ')'; 61 | }; 62 | 63 | 64 | /** 65 | * OR Specification 66 | * @param lhs 67 | * @param rhs 68 | * @constructor 69 | */ 70 | function OrSpec(lhs, rhs) { 71 | Spec.call(this); 72 | this.lhs = lhs; 73 | this.rhs = rhs; 74 | } 75 | 76 | util.inherits(OrSpec, Spec); 77 | 78 | OrSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate) { 79 | return Promise.all([this.lhs.isSatisfiedBy(candidate), this.rhs.isSatisfiedBy(candidate)]).then(function(values) { 80 | return values[0] || values[1]; 81 | }); 82 | }; 83 | 84 | OrSpec.prototype.explain = function explain() { 85 | return '(' + this.lhs.explain() + ' OR ' + this.rhs.explain() + ')'; 86 | }; 87 | 88 | 89 | /** 90 | * NOT Specification 91 | * @param other 92 | * @constructor 93 | */ 94 | function NotSpec(other) { 95 | Spec.call(this); 96 | this.other = other; 97 | } 98 | 99 | util.inherits(NotSpec, Spec); 100 | 101 | NotSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate) { 102 | return Promise.resolve(this.other.isSatisfiedBy(candidate)).then(function(value) { 103 | return !value; 104 | }); 105 | }; 106 | 107 | NotSpec.prototype.explain = function explain() { 108 | return '(NOT ' + this.other.explain() + ')'; 109 | }; 110 | 111 | 112 | module.exports = Spec; 113 | -------------------------------------------------------------------------------- /lib/detail/sync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./util'); 4 | 5 | 6 | /** 7 | * Specification base 8 | * @constructor 9 | */ 10 | function Spec() { 11 | if(arguments.length) { 12 | return util.ensureSpec(arguments[0], Spec); 13 | } 14 | } 15 | 16 | Spec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate) { 17 | throw new Error('isSatisfiedBy not implemented'); 18 | }; 19 | 20 | Spec.prototype.and = function and(other) { 21 | other = util.ensureSpec(other, Spec); 22 | return new AndSpec(this, other); 23 | }; 24 | 25 | Spec.prototype.or = function or(other) { 26 | other = util.ensureSpec(other, Spec); 27 | return new OrSpec(this, other); 28 | }; 29 | 30 | Spec.prototype.not = function not() { 31 | return new NotSpec(this); 32 | }; 33 | 34 | Spec.prototype.explain = function explain() { 35 | return util.functionName(this.constructor); 36 | }; 37 | 38 | 39 | /** 40 | * AND Specification 41 | * @param lhs 42 | * @param rhs 43 | * @constructor 44 | */ 45 | function AndSpec(lhs, rhs) { 46 | Spec.call(this); 47 | this.lhs = lhs; 48 | this.rhs = rhs; 49 | } 50 | 51 | util.inherits(AndSpec, Spec); 52 | 53 | AndSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate) { 54 | return this.lhs.isSatisfiedBy(candidate) && this.rhs.isSatisfiedBy(candidate); 55 | }; 56 | 57 | AndSpec.prototype.explain = function explain() { 58 | return '(' + this.lhs.explain() + ' AND ' + this.rhs.explain() + ')'; 59 | }; 60 | 61 | 62 | /** 63 | * OR Specification 64 | * @param lhs 65 | * @param rhs 66 | * @constructor 67 | */ 68 | function OrSpec(lhs, rhs) { 69 | Spec.call(this); 70 | this.lhs = lhs; 71 | this.rhs = rhs; 72 | } 73 | 74 | util.inherits(OrSpec, Spec); 75 | 76 | OrSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate) { 77 | return this.lhs.isSatisfiedBy(candidate) || this.rhs.isSatisfiedBy(candidate); 78 | }; 79 | 80 | OrSpec.prototype.explain = function explain() { 81 | return '(' + this.lhs.explain() + ' OR ' + this.rhs.explain() + ')'; 82 | }; 83 | 84 | 85 | /** 86 | * NOT Specification 87 | * @param other 88 | * @constructor 89 | */ 90 | function NotSpec(other) { 91 | Spec.call(this); 92 | this.other = other; 93 | } 94 | 95 | util.inherits(NotSpec, Spec); 96 | 97 | NotSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate) { 98 | return !this.other.isSatisfiedBy(candidate); 99 | }; 100 | 101 | NotSpec.prototype.explain = function explain() { 102 | return '(NOT ' + this.other.explain() + ')'; 103 | }; 104 | 105 | 106 | module.exports = Spec; 107 | -------------------------------------------------------------------------------- /lib/detail/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.functionName = functionName; 4 | exports.inherits = inherits; 5 | exports.ensureSpec = ensureSpec; 6 | 7 | 8 | var functionNameRx = /^\s*function\s*(\S*)\s*\(/; 9 | 10 | /** 11 | * get the function name 12 | * @param f 13 | * @returns {*} 14 | */ 15 | function functionName(f) { 16 | if(f.name) { 17 | return f.name; 18 | } 19 | return f.toString().match(functionNameRx)[1]; 20 | } 21 | 22 | /** 23 | * Inherit the prototype methods from one constructor into another. 24 | * @param ctor 25 | * @param superCtor 26 | */ 27 | function inherits(ctor, superCtor) { 28 | ctor.super_ = superCtor; 29 | ctor.prototype = Object.create(superCtor.prototype, { 30 | constructor: { 31 | value: ctor, 32 | enumerable: false, 33 | writable: true, 34 | configurable: true 35 | } 36 | }); 37 | } 38 | 39 | /** 40 | * Check that argument is a function 41 | * @param arg 42 | * @returns {boolean} 43 | */ 44 | function isFunction(arg) { 45 | return typeof arg === 'function'; 46 | } 47 | 48 | /** 49 | * Check that argument is an object 50 | * @param arg 51 | * @returns {boolean} 52 | */ 53 | function isObject(arg) { 54 | return arg !== null && typeof arg === 'object'; 55 | } 56 | 57 | /** 58 | * Check whether the provided argument can be used in place of a specification 59 | * @param arg 60 | * @param BaseSpec 61 | */ 62 | function isSpecLike(arg, BaseSpec) { 63 | return (arg && (isFunction(arg) || (isObject(arg) && (('isSatisfiedBy' in arg) && isFunction(arg.isSatisfiedBy)) || (arg instanceof BaseSpec)))); 64 | } 65 | 66 | /** 67 | * Create a wrapper for a func 68 | * @param func 69 | * @param BaseSpec 70 | */ 71 | function makeWrapper(func, BaseSpec) { 72 | function WrapperSpec() { 73 | BaseSpec.call(this); 74 | } 75 | 76 | inherits(WrapperSpec, BaseSpec); 77 | 78 | WrapperSpec.prototype.isSatisfiedBy = ('isSatisfiedBy' in func) ? func.isSatisfiedBy.bind(func) : func; 79 | 80 | return new WrapperSpec(); 81 | } 82 | 83 | /** 84 | * Ensure the provided function is a specification-like object 85 | * @param func 86 | * @param BaseSpec 87 | * @returns {*} 88 | */ 89 | function ensureSpec(func, BaseSpec) { 90 | if(!isSpecLike(func, BaseSpec)) { 91 | throw new Error('invalid argument: must be a function, spec-like or a spec object'); 92 | } 93 | 94 | if(func instanceof BaseSpec) { 95 | return func; 96 | } 97 | 98 | return makeWrapper(func, BaseSpec); 99 | } 100 | 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bspec", 3 | "version": "1.0.1", 4 | "description": "A JavaScript library for structuring business rules", 5 | "main": "./lib/bspec.js", 6 | "scripts": { 7 | "test": "mocha", 8 | "browser": "gulp script", 9 | "zuul": "zuul -- ./test/*.spec.js", 10 | "zuul-local": "zuul --local 8080 --ui mocha-bdd -- ./test/*.spec.js", 11 | "istanbul-local": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && rm -rf ./coverage", 12 | "coveralls": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/gchudnov/bspec.git" 17 | }, 18 | "keywords": [ 19 | "specification", 20 | "ddd", 21 | "pattern", 22 | "business", 23 | "rules" 24 | ], 25 | "author": "Grigoriy Chudnov (https://github.com/gchudnov)", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/gchudnov/bspec/issues" 29 | }, 30 | "homepage": "https://github.com/gchudnov/bspec", 31 | "devDependencies": { 32 | "browserify": "^13.0.0", 33 | "coveralls": "^2.11.8", 34 | "es6-promise": "^3.1.2", 35 | "gulp": "^3.9.1", 36 | "gulp-header": "^1.7.1", 37 | "gulp-load-plugins": "^1.2.0", 38 | "gulp-rename": "^1.2.2", 39 | "gulp-uglify": "^1.5.3", 40 | "gulp-util": "^3.0.7", 41 | "istanbul": "^0.4.2", 42 | "mocha": "^2.4.5", 43 | "should": "^8.3.0", 44 | "vinyl-source-stream2": "^0.1.1", 45 | "zuul": "^3.10.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/and.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('es6-promise').polyfill(); 4 | 5 | var should = require('should'); 6 | var util = require('util'); 7 | var bspec = require('./../lib/bspec'); 8 | 9 | var CallbackSpec = bspec.CallbackSpec; 10 | var SyncSpec = bspec.SyncSpec; 11 | var PromiseSpec = bspec.PromiseSpec; 12 | 13 | 14 | describe('AND Specification', function() { 15 | 16 | describe('Callback', function() { 17 | 18 | // TRUE 19 | function TrueSpec() { 20 | } 21 | 22 | util.inherits(TrueSpec, CallbackSpec); 23 | 24 | TrueSpec.prototype.isSatisfiedBy = function(dummy, cb) { 25 | return cb(null, true); 26 | }; 27 | 28 | // FALSE 29 | function FalseSpec() { 30 | } 31 | 32 | util.inherits(FalseSpec, CallbackSpec); 33 | 34 | FalseSpec.prototype.isSatisfiedBy = function(dummy, cb) { 35 | return cb(null, false); 36 | }; 37 | 38 | // ERROR 39 | function ErrorSpec() { 40 | } 41 | 42 | util.inherits(ErrorSpec, CallbackSpec); 43 | 44 | ErrorSpec.prototype.isSatisfiedBy = function(dummy, cb) { 45 | return cb(new Error('cannot satisfy the spec')); 46 | }; 47 | 48 | var alwaysTrue = new TrueSpec(); 49 | var alwaysFalse = new FalseSpec(); 50 | var alwaysError = new ErrorSpec(); 51 | 52 | 53 | it('can be validated for true-true', function(done) { 54 | alwaysTrue.and(alwaysTrue).isSatisfiedBy({}, function(err, flag) { 55 | should.not.exist(err); 56 | flag.should.be.true; 57 | done(); 58 | }); 59 | }); 60 | 61 | it('can be validated for true-false', function(done) { 62 | alwaysTrue.and(alwaysFalse).isSatisfiedBy({}, function(err, flag) { 63 | should.not.exist(err); 64 | flag.should.be.false; 65 | done(); 66 | }); 67 | }); 68 | 69 | it('can be validated for false-true', function(done) { 70 | alwaysFalse.and(alwaysTrue).isSatisfiedBy({}, function(err, flag) { 71 | should.not.exist(err); 72 | flag.should.be.false; 73 | done(); 74 | }); 75 | }); 76 | 77 | it('can be validated for false-false', function(done) { 78 | alwaysFalse.and(alwaysFalse).isSatisfiedBy({}, function(err, flag) { 79 | should.not.exist(err); 80 | flag.should.be.false; 81 | done(); 82 | }); 83 | }); 84 | 85 | it('can be validated for true-true-true', function(done) { 86 | alwaysTrue.and(alwaysTrue).and(alwaysTrue).isSatisfiedBy({}, function(err, flag) { 87 | should.not.exist(err); 88 | flag.should.be.true; 89 | done(); 90 | }); 91 | }); 92 | 93 | it('can be validated for true-true-false', function(done) { 94 | alwaysTrue.and(alwaysTrue).and(alwaysFalse).isSatisfiedBy({}, function(err, flag) { 95 | should.not.exist(err); 96 | flag.should.be.false; 97 | done(); 98 | }); 99 | }); 100 | 101 | it('should return an error for error-true', function(done) { 102 | alwaysError.and(alwaysTrue).isSatisfiedBy({}, function(err) { 103 | should.exist(err); 104 | done(); 105 | }); 106 | }); 107 | 108 | it('should return an error for error-false', function(done) { 109 | alwaysError.and(alwaysFalse).isSatisfiedBy({}, function(err) { 110 | should.exist(err); 111 | done(); 112 | }); 113 | }); 114 | 115 | it('should return an error for true-error', function(done) { 116 | alwaysTrue.and(alwaysError).isSatisfiedBy({}, function(err) { 117 | should.exist(err); 118 | done(); 119 | }); 120 | }); 121 | 122 | it('should return false for false-error', function(done) { 123 | alwaysFalse.and(alwaysError).isSatisfiedBy({}, function(err, flag) { 124 | should.not.exist(err); 125 | flag.should.be.false; 126 | done(); 127 | }); 128 | }); 129 | 130 | }); 131 | 132 | describe('Sync', function() { 133 | 134 | // TRUE 135 | function TrueSpec() { 136 | } 137 | 138 | util.inherits(TrueSpec, SyncSpec); 139 | 140 | TrueSpec.prototype.isSatisfiedBy = function() { 141 | return true; 142 | }; 143 | 144 | // FALSE 145 | function FalseSpec() { 146 | } 147 | 148 | util.inherits(FalseSpec, SyncSpec); 149 | 150 | FalseSpec.prototype.isSatisfiedBy = function() { 151 | return false; 152 | }; 153 | 154 | var alwaysTrue = new TrueSpec(); 155 | var alwaysFalse = new FalseSpec(); 156 | 157 | it('can be validated for true-true', function(done) { 158 | var flag = alwaysTrue.and(alwaysTrue).isSatisfiedBy({}); 159 | flag.should.be.true; 160 | done(); 161 | }); 162 | 163 | it('can be validated for true-false', function(done) { 164 | var flag = alwaysTrue.and(alwaysFalse).isSatisfiedBy({}); 165 | flag.should.be.false; 166 | done(); 167 | }); 168 | 169 | it('can be validated for false-true', function(done) { 170 | var flag = alwaysFalse.and(alwaysTrue).isSatisfiedBy({}); 171 | flag.should.be.false; 172 | done(); 173 | }); 174 | 175 | it('can be validated for false-false', function(done) { 176 | var flag = alwaysFalse.and(alwaysFalse).isSatisfiedBy({}); 177 | flag.should.be.false; 178 | done(); 179 | }); 180 | 181 | it('can be validated for true-true-true', function(done) { 182 | var flag = alwaysTrue.and(alwaysTrue).and(alwaysTrue).isSatisfiedBy({}); 183 | flag.should.be.true; 184 | done(); 185 | }); 186 | 187 | it('can be validated for true-true-false', function(done) { 188 | var flag = alwaysTrue.and(alwaysTrue).and(alwaysFalse).isSatisfiedBy({}); 189 | flag.should.be.false; 190 | done(); 191 | }); 192 | 193 | }); 194 | 195 | describe('Promise', function() { 196 | 197 | // TRUE 198 | function TrueSpec() { 199 | } 200 | 201 | util.inherits(TrueSpec, PromiseSpec); 202 | 203 | TrueSpec.prototype.isSatisfiedBy = function() { 204 | return Promise.resolve(true); 205 | }; 206 | 207 | // FALSE 208 | function FalseSpec() { 209 | } 210 | 211 | util.inherits(FalseSpec, PromiseSpec); 212 | 213 | FalseSpec.prototype.isSatisfiedBy = function() { 214 | return Promise.resolve(false); 215 | }; 216 | 217 | var alwaysTrue = new TrueSpec(); 218 | var alwaysFalse = new FalseSpec(); 219 | 220 | it('can be validated for true-true', function(done) { 221 | alwaysTrue.and(alwaysTrue).isSatisfiedBy({}).then(function(flag) { 222 | flag.should.be.true; 223 | done(); 224 | }, function(reason) { 225 | throw new Error(reason); 226 | }); 227 | }); 228 | 229 | it('can be validated for true-false', function(done) { 230 | alwaysTrue.and(alwaysFalse).isSatisfiedBy({}).then(function(flag) { 231 | flag.should.be.false; 232 | done(); 233 | }, function(reason) { 234 | throw new Error(reason); 235 | }); 236 | }); 237 | 238 | it('can be validated for false-true', function(done) { 239 | alwaysFalse.and(alwaysTrue).isSatisfiedBy({}).then(function(flag) { 240 | flag.should.be.false; 241 | done(); 242 | }, function(reason) { 243 | throw new Error(reason); 244 | }); 245 | }); 246 | 247 | it('can be validated for false-false', function(done) { 248 | alwaysFalse.and(alwaysFalse).isSatisfiedBy({}).then(function(flag) { 249 | flag.should.be.false; 250 | done(); 251 | }, function(reason) { 252 | throw new Error(reason); 253 | }); 254 | }); 255 | 256 | it('can be validated for true-true-true', function(done) { 257 | alwaysTrue.and(alwaysTrue).and(alwaysTrue).isSatisfiedBy({}).then(function(flag) { 258 | flag.should.be.true; 259 | done(); 260 | }, function(reason) { 261 | throw new Error(reason); 262 | }); 263 | }); 264 | 265 | it('can be validated for true-true-false', function(done) { 266 | alwaysTrue.and(alwaysTrue).and(alwaysFalse).isSatisfiedBy({}).then(function(flag) { 267 | flag.should.be.false; 268 | done(); 269 | }, function(reason) { 270 | throw new Error(reason); 271 | }); 272 | }); 273 | 274 | }); 275 | 276 | }); 277 | -------------------------------------------------------------------------------- /test/composite.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var util = require('util'); 5 | var bspec = require('./../lib/bspec'); 6 | 7 | var CallbackSpec = bspec.CallbackSpec; 8 | 9 | 10 | describe('Composite Specification', function() { 11 | 12 | // TRUE 13 | function TrueSpec() { 14 | } 15 | 16 | util.inherits(TrueSpec, CallbackSpec); 17 | 18 | TrueSpec.prototype.isSatisfiedBy = function(dummy, cb) { 19 | return cb(null, true); 20 | }; 21 | 22 | // FALSE 23 | function FalseSpec() { 24 | } 25 | 26 | util.inherits(FalseSpec, CallbackSpec); 27 | 28 | FalseSpec.prototype.isSatisfiedBy = function(dummy, cb) { 29 | return cb(null, false); 30 | }; 31 | 32 | var alwaysTrue = new TrueSpec(); 33 | var alwaysFalse = new FalseSpec(); 34 | 35 | 36 | it('can be validated for an expression', function(done) { 37 | alwaysTrue.and(alwaysFalse).or(alwaysTrue).isSatisfiedBy({}, function(err, flag) { 38 | should.not.exist(err); 39 | flag.should.be.true; 40 | done(); 41 | }); 42 | }); 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /test/explain.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('es6-promise').polyfill(); 4 | 5 | var should = require('should'); 6 | var util = require('util'); 7 | var bspec = require('./../lib/bspec'); 8 | 9 | var CallbackSpec = bspec.CallbackSpec; 10 | var SyncSpec = bspec.SyncSpec; 11 | var PromiseSpec = bspec.PromiseSpec; 12 | 13 | 14 | describe('Explain', function() { 15 | 16 | it('can be used to print Callback-style specifications', function(done) { 17 | 18 | // 19 | function OverDueSpec() { 20 | } 21 | 22 | util.inherits(OverDueSpec, CallbackSpec); 23 | 24 | OverDueSpec.prototype.isSatisfiedBy = function(invoice, cb) { 25 | return cb(null, true); 26 | }; 27 | 28 | // 29 | function NoticeSentSpec() { 30 | } 31 | 32 | util.inherits(NoticeSentSpec, CallbackSpec); 33 | 34 | NoticeSentSpec.prototype.isSatisfiedBy = function(invoice, cb) { 35 | return cb(null, true); 36 | }; 37 | 38 | // 39 | function InCollectionSpec() { 40 | } 41 | 42 | util.inherits(InCollectionSpec, CallbackSpec); 43 | 44 | InCollectionSpec.prototype.isSatisfiedBy = function(invoice, cb) { 45 | return cb(null, false); 46 | }; 47 | 48 | var overDue = new OverDueSpec(); 49 | var noticeSent = new NoticeSentSpec(); 50 | var inCol = new InCollectionSpec(); 51 | 52 | var composite = overDue.and(noticeSent).or(inCol.not()); 53 | 54 | var str = composite.explain(); 55 | str.should.be.not.empty; 56 | 57 | composite.isSatisfiedBy({}, function(err, flag) { 58 | should.not.exist(err); 59 | flag.should.be.true; 60 | 61 | done(); 62 | }); 63 | 64 | }); 65 | 66 | it('can be used to print sync specifications', function(done) { 67 | 68 | // 69 | function OverDueSpec() { 70 | } 71 | 72 | util.inherits(OverDueSpec, SyncSpec); 73 | 74 | OverDueSpec.prototype.isSatisfiedBy = function(invoice) { 75 | return true; 76 | }; 77 | 78 | // 79 | function NoticeSentSpec() { 80 | } 81 | 82 | util.inherits(NoticeSentSpec, SyncSpec); 83 | 84 | NoticeSentSpec.prototype.isSatisfiedBy = function(invoice) { 85 | return true; 86 | }; 87 | 88 | // 89 | function InCollectionSpec() { 90 | } 91 | 92 | util.inherits(InCollectionSpec, SyncSpec); 93 | 94 | InCollectionSpec.prototype.isSatisfiedBy = function(invoice) { 95 | return false; 96 | }; 97 | 98 | var overDue = new OverDueSpec(); 99 | var noticeSent = new NoticeSentSpec(); 100 | var inCol = new InCollectionSpec(); 101 | 102 | var composite = overDue.and(noticeSent).or(inCol.not()); 103 | var flag = composite.isSatisfiedBy({}); 104 | flag.should.be.true; 105 | 106 | var str = composite.explain(); 107 | str.should.be.not.empty; 108 | 109 | done(); 110 | }); 111 | 112 | it('can be used to print promise-based specifications', function(done) { 113 | 114 | // 115 | function OverDueSpec() { 116 | } 117 | 118 | util.inherits(OverDueSpec, PromiseSpec); 119 | 120 | OverDueSpec.prototype.isSatisfiedBy = function(invoice) { 121 | return Promise.resolve(true); 122 | }; 123 | 124 | // 125 | function NoticeSentSpec() { 126 | } 127 | 128 | util.inherits(NoticeSentSpec, PromiseSpec); 129 | 130 | NoticeSentSpec.prototype.isSatisfiedBy = function(invoice) { 131 | return Promise.resolve(false); 132 | }; 133 | 134 | // 135 | function InCollectionSpec() { 136 | } 137 | 138 | util.inherits(InCollectionSpec, PromiseSpec); 139 | 140 | InCollectionSpec.prototype.isSatisfiedBy = function(invoice) { 141 | return Promise.resolve(false); 142 | }; 143 | 144 | var overDue = new OverDueSpec(); 145 | var noticeSent = new NoticeSentSpec(); 146 | var inCol = new InCollectionSpec(); 147 | 148 | var composite = overDue.and(noticeSent).or(inCol.not()); 149 | composite.isSatisfiedBy({}).then(function(flag) { 150 | flag.should.be.true; 151 | 152 | var str = composite.explain(); 153 | str.should.be.not.empty; 154 | 155 | done(); 156 | }, function(reason) { 157 | throw new Error(reason); 158 | }); 159 | }); 160 | 161 | }); 162 | -------------------------------------------------------------------------------- /test/from-func-callback.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var util = require('util'); 5 | var bspec = require('./../lib/bspec'); 6 | 7 | describe('Specification from functions with a callback interface', function() { 8 | 9 | function alwaysTrue(obj, cb) { cb(null, true); } 10 | function alwaysFalse(obj, cb) { cb(null, false); } 11 | function alwaysError(obj, cb) { cb(new Error('some error')); } 12 | 13 | var Spec = bspec.CallbackSpec; 14 | 15 | it('can be created with `new`', function(done) { 16 | var spec = new Spec(alwaysTrue); 17 | spec.isSatisfiedBy({}, function(err, flag) { 18 | should.not.exist(err); 19 | flag.should.be.true; 20 | done(); 21 | }); 22 | }); 23 | 24 | it('can be created from `true`', function(done) { 25 | var spec = Spec(alwaysTrue); 26 | spec.isSatisfiedBy({}, function(err, flag) { 27 | should.not.exist(err); 28 | flag.should.be.true; 29 | done(); 30 | }); 31 | }); 32 | 33 | it('can be created from `false`', function(done) { 34 | var spec = Spec(alwaysFalse); 35 | spec.isSatisfiedBy({}, function(err, flag) { 36 | should.not.exist(err); 37 | flag.should.be.false; 38 | done(); 39 | }); 40 | }); 41 | 42 | it('can be created from `error`', function(done) { 43 | var spec = Spec(alwaysError); 44 | spec.isSatisfiedBy({}, function(err) { 45 | should.exist(err); 46 | done(); 47 | }); 48 | }); 49 | 50 | it('can be composed', function(done) { 51 | var spec = Spec(alwaysTrue).and(alwaysTrue).or(alwaysFalse).not(); 52 | spec.isSatisfiedBy({}, function(err, flag) { 53 | should.not.exist(err); 54 | flag.should.be.false; 55 | done(); 56 | }); 57 | }); 58 | 59 | }); 60 | -------------------------------------------------------------------------------- /test/from-func-promise.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('es6-promise').polyfill(); 4 | 5 | var should = require('should'); 6 | var util = require('util'); 7 | var bspec = require('./../lib/bspec'); 8 | 9 | describe('Specification from functions that return a promise', function() { 10 | 11 | function alwaysTrue() { return new Promise(function(resolve, reject) { resolve(true); }); } 12 | function alwaysFalse() { return new Promise(function(resolve, reject) { resolve(false); }); } 13 | function alwaysError() { return new Promise(function(resolve, reject) { throw new Error('some error'); }); } 14 | 15 | var Spec = bspec.PromiseSpec; 16 | 17 | it('can be created with `new`', function(done) { 18 | var spec = new Spec(alwaysTrue); 19 | spec.isSatisfiedBy({}).then(function(flag) { 20 | flag.should.be.true; 21 | done(); 22 | }, function(reason) { 23 | throw new Error(reason); 24 | }); 25 | }); 26 | 27 | it('can be created from `true`', function(done) { 28 | var spec = Spec(alwaysTrue); 29 | spec.isSatisfiedBy({}).then(function(flag) { 30 | flag.should.be.true; 31 | done(); 32 | }, function(reason) { 33 | throw new Error(reason); 34 | }); 35 | }); 36 | 37 | it('can be created from `false`', function(done) { 38 | var spec = Spec(alwaysFalse); 39 | spec.isSatisfiedBy({}).then(function(flag) { 40 | flag.should.be.false; 41 | done(); 42 | }, function(reason) { 43 | throw new Error(reason); 44 | }); 45 | }); 46 | 47 | it('can be created from `error`', function(done) { 48 | var spec = Spec(alwaysError); 49 | spec.isSatisfiedBy({}).then(function(flag) { 50 | throw new Error('should not be here'); 51 | }, function(reason) { 52 | should.exist(reason); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('can be composed', function(done) { 58 | var spec = Spec(alwaysTrue).and(alwaysTrue).or(alwaysFalse).not(); 59 | var flag = spec.isSatisfiedBy({}).then(function(flag) { 60 | flag.should.be.false; 61 | done(); 62 | }, function(reason) { 63 | throw new Error(reason); 64 | }); 65 | }); 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /test/from-func-sync.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var util = require('util'); 5 | var bspec = require('./../lib/bspec'); 6 | 7 | describe('Specification from sync functions', function() { 8 | 9 | function alwaysTrue() { return true; } 10 | function alwaysFalse() { return false; } 11 | function alwaysError() { throw new Error('some error'); } 12 | 13 | var Spec = bspec.SyncSpec; 14 | 15 | it('can be created with `new`', function(done) { 16 | var spec = new Spec(alwaysTrue); 17 | var flag = spec.isSatisfiedBy({}); 18 | flag.should.be.true; 19 | done(); 20 | }); 21 | 22 | it('can be created from `true`', function(done) { 23 | var spec = Spec(alwaysTrue); 24 | var flag = spec.isSatisfiedBy({}); 25 | flag.should.be.true; 26 | done(); 27 | }); 28 | 29 | it('can be created from `false`', function(done) { 30 | var spec = Spec(alwaysFalse); 31 | var flag = spec.isSatisfiedBy({}); 32 | flag.should.be.false; 33 | done(); 34 | }); 35 | 36 | it('can be created from `error`', function(done) { 37 | var spec = Spec(alwaysError); 38 | (function() { 39 | spec.isSatisfiedBy({}); 40 | }).should.throw(); 41 | done(); 42 | }); 43 | 44 | it('can be composed', function(done) { 45 | var spec = Spec(alwaysTrue).and(alwaysTrue).or(alwaysFalse).not(); 46 | var flag = spec.isSatisfiedBy({}); 47 | flag.should.be.false; 48 | done(); 49 | }); 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /test/from-spec-callback.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var util = require('util'); 5 | var bspec = require('./../lib/bspec'); 6 | 7 | describe('Specification from specifications with callback interface', function() { 8 | 9 | // 10 | function BooleanSpec(value) { 11 | this.value = value; 12 | } 13 | 14 | util.inherits(BooleanSpec, bspec.CallbackSpec); 15 | 16 | BooleanSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate, cb) { 17 | return cb(null, this.value); 18 | }; 19 | 20 | // 21 | function ErrorSpec() { 22 | } 23 | 24 | ErrorSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate, cb) { 25 | cb(new Error('some error')); 26 | }; 27 | 28 | util.inherits(ErrorSpec, bspec.CallbackSpec); 29 | 30 | var alwaysTrue = new BooleanSpec(true); 31 | var alwaysFalse = new BooleanSpec(false); 32 | var alwaysError = new ErrorSpec(); 33 | 34 | var Spec = bspec.CallbackSpec; 35 | 36 | it('can be created with `new`', function(done) { 37 | var spec = new Spec(alwaysTrue); 38 | spec.isSatisfiedBy({}, function(err, flag) { 39 | should.not.exist(err); 40 | flag.should.be.true; 41 | done(); 42 | }); 43 | }); 44 | 45 | it('can be created from `true`', function(done) { 46 | var spec = Spec(alwaysTrue); 47 | spec.isSatisfiedBy({}, function(err, flag) { 48 | should.not.exist(err); 49 | flag.should.be.true; 50 | done(); 51 | }); 52 | }); 53 | 54 | it('can be created from `false`', function(done) { 55 | var spec = Spec(alwaysFalse); 56 | spec.isSatisfiedBy({}, function(err, flag) { 57 | should.not.exist(err); 58 | flag.should.be.false; 59 | done(); 60 | }); 61 | }); 62 | 63 | it('can be created from `error`', function(done) { 64 | var spec = Spec(alwaysError); 65 | spec.isSatisfiedBy({}, function(err) { 66 | should.exist(err); 67 | done(); 68 | }); 69 | }); 70 | 71 | it('can be composed', function(done) { 72 | var spec = Spec(alwaysTrue).and(alwaysTrue).or(alwaysFalse).not(); 73 | spec.isSatisfiedBy({}, function(err, flag) { 74 | should.not.exist(err); 75 | flag.should.be.false; 76 | done(); 77 | }); 78 | }); 79 | 80 | }); 81 | -------------------------------------------------------------------------------- /test/from-spec-like-callback.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var util = require('util'); 5 | var bspec = require('./../lib/bspec'); 6 | 7 | describe('Specification from specification-like objects with callback interface', function() { 8 | 9 | // 10 | function BooleanSpec(value) { 11 | this.value = value; 12 | } 13 | 14 | BooleanSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate, cb) { 15 | return cb(null, this.value); 16 | }; 17 | 18 | // 19 | function ErrorSpec() { 20 | } 21 | 22 | ErrorSpec.prototype.isSatisfiedBy = function isSatisfiedBy(candidate, cb) { 23 | cb(new Error('some error')); 24 | }; 25 | 26 | var alwaysTrue = new BooleanSpec(true); 27 | var alwaysFalse = new BooleanSpec(false); 28 | var alwaysError = new ErrorSpec(); 29 | 30 | var Spec = bspec.CallbackSpec; 31 | 32 | it('can be created with `new`', function(done) { 33 | var spec = new Spec(alwaysTrue); 34 | spec.isSatisfiedBy({}, function(err, flag) { 35 | should.not.exist(err); 36 | flag.should.be.true; 37 | done(); 38 | }); 39 | }); 40 | 41 | it('can be created from `true`', function(done) { 42 | var spec = Spec(alwaysTrue); 43 | spec.isSatisfiedBy({}, function(err, flag) { 44 | should.not.exist(err); 45 | flag.should.be.true; 46 | done(); 47 | }); 48 | }); 49 | 50 | it('can be created from `false`', function(done) { 51 | var spec = Spec(alwaysFalse); 52 | spec.isSatisfiedBy({}, function(err, flag) { 53 | should.not.exist(err); 54 | flag.should.be.false; 55 | done(); 56 | }); 57 | }); 58 | 59 | it('can be created from `error`', function(done) { 60 | var spec = Spec(alwaysError); 61 | spec.isSatisfiedBy({}, function(err) { 62 | should.exist(err); 63 | done(); 64 | }); 65 | }); 66 | 67 | it('can be composed', function(done) { 68 | var spec = Spec(alwaysTrue).and(alwaysTrue).or(alwaysFalse).not(); 69 | spec.isSatisfiedBy({}, function(err, flag) { 70 | should.not.exist(err); 71 | flag.should.be.false; 72 | done(); 73 | }); 74 | }); 75 | 76 | }); 77 | -------------------------------------------------------------------------------- /test/from-spec-like-promise.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('es6-promise').polyfill(); 4 | 5 | var should = require('should'); 6 | var util = require('util'); 7 | var bspec = require('./../lib/bspec'); 8 | 9 | describe('Specification from specification-like objects that return a promise', function() { 10 | 11 | // 12 | function BooleanSpec(value) { 13 | this.value = value; 14 | } 15 | 16 | BooleanSpec.prototype.isSatisfiedBy = function() { 17 | var self = this; 18 | return new Promise(function(resolve, reject) { resolve(self.value); }); 19 | }; 20 | 21 | // 22 | function ErrorSpec() { 23 | } 24 | 25 | ErrorSpec.prototype.isSatisfiedBy = function() { 26 | return new Promise(function(resolve, reject) { throw new Error('some error'); }); 27 | }; 28 | 29 | var alwaysTrue = new BooleanSpec(true); 30 | var alwaysFalse = new BooleanSpec(false); 31 | var alwaysError = new ErrorSpec(); 32 | 33 | var Spec = bspec.PromiseSpec; 34 | 35 | it('can be created with `new`', function(done) { 36 | var spec = new Spec(alwaysTrue); 37 | spec.isSatisfiedBy({}).then(function(flag) { 38 | flag.should.be.true; 39 | done(); 40 | }, function(reason) { 41 | throw new Error(reason); 42 | }); 43 | }); 44 | 45 | it('can be created from `true`', function(done) { 46 | var spec = Spec(alwaysTrue); 47 | spec.isSatisfiedBy({}).then(function(flag) { 48 | flag.should.be.true; 49 | done(); 50 | }, function(reason) { 51 | throw new Error(reason); 52 | }); 53 | }); 54 | 55 | it('can be created from `false`', function(done) { 56 | var spec = Spec(alwaysFalse); 57 | spec.isSatisfiedBy({}).then(function(flag) { 58 | flag.should.be.false; 59 | done(); 60 | }, function(reason) { 61 | throw new Error(reason); 62 | }); 63 | }); 64 | 65 | it('can be created from `error`', function(done) { 66 | var spec = Spec(alwaysError); 67 | spec.isSatisfiedBy({}).then(function(flag) { 68 | throw new Error('should not be here'); 69 | }, function(reason) { 70 | should.exist(reason); 71 | done(); 72 | }); 73 | }); 74 | 75 | it('can be composed', function(done) { 76 | var spec = Spec(alwaysTrue).and(alwaysTrue).or(alwaysFalse).not(); 77 | var flag = spec.isSatisfiedBy({}).then(function(flag) { 78 | flag.should.be.false; 79 | done(); 80 | }, function(reason) { 81 | throw new Error(reason); 82 | }); 83 | }); 84 | 85 | }); 86 | -------------------------------------------------------------------------------- /test/from-spec-like-sync.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var util = require('util'); 5 | var bspec = require('./../lib/bspec'); 6 | 7 | describe('Specification from sync specification-like objects', function() { 8 | 9 | // 10 | function BooleanSpec(value) { 11 | this.value = value; 12 | } 13 | 14 | BooleanSpec.prototype.isSatisfiedBy = function() { 15 | return this.value; 16 | }; 17 | 18 | // 19 | function ErrorSpec() { 20 | } 21 | 22 | ErrorSpec.prototype.isSatisfiedBy = function() { 23 | throw new Error('some error'); 24 | }; 25 | 26 | var alwaysTrue = new BooleanSpec(true); 27 | var alwaysFalse = new BooleanSpec(false); 28 | var alwaysError = new ErrorSpec(); 29 | 30 | var Spec = bspec.SyncSpec; 31 | 32 | it('can be created with `new`', function(done) { 33 | var spec = new Spec(alwaysTrue); 34 | var flag = spec.isSatisfiedBy({}); 35 | flag.should.be.true; 36 | done(); 37 | }); 38 | 39 | it('can be created from `true`', function(done) { 40 | var spec = Spec(alwaysTrue); 41 | var flag = spec.isSatisfiedBy({}); 42 | flag.should.be.true; 43 | done(); 44 | }); 45 | 46 | it('can be created from `false`', function(done) { 47 | var spec = Spec(alwaysFalse); 48 | var flag = spec.isSatisfiedBy({}); 49 | flag.should.be.false; 50 | done(); 51 | }); 52 | 53 | it('can be created from `error`', function(done) { 54 | var spec = Spec(alwaysError); 55 | (function() { 56 | spec.isSatisfiedBy({}); 57 | }).should.throw(); 58 | done(); 59 | }); 60 | 61 | it('can be composed', function(done) { 62 | var spec = Spec(alwaysTrue).and(alwaysTrue).or(alwaysFalse).not(); 63 | var flag = spec.isSatisfiedBy({}); 64 | flag.should.be.false; 65 | done(); 66 | }); 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /test/from-spec-promise.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('es6-promise').polyfill(); 4 | 5 | var should = require('should'); 6 | var util = require('util'); 7 | var bspec = require('./../lib/bspec'); 8 | 9 | describe('Specification from specifications that return a promise', function() { 10 | 11 | // 12 | function BooleanSpec(value) { 13 | this.value = value; 14 | } 15 | 16 | util.inherits(BooleanSpec, bspec.SyncSpec); 17 | 18 | BooleanSpec.prototype.isSatisfiedBy = function() { 19 | var self = this; 20 | return new Promise(function(resolve, reject) { resolve(self.value); }); 21 | }; 22 | 23 | // 24 | function ErrorSpec() { 25 | } 26 | 27 | util.inherits(ErrorSpec, bspec.SyncSpec); 28 | 29 | ErrorSpec.prototype.isSatisfiedBy = function() { 30 | return new Promise(function(resolve, reject) { throw new Error('some error'); }); 31 | }; 32 | 33 | var alwaysTrue = new BooleanSpec(true); 34 | var alwaysFalse = new BooleanSpec(false); 35 | var alwaysError = new ErrorSpec(); 36 | 37 | var Spec = bspec.PromiseSpec; 38 | 39 | it('can be created with `new`', function(done) { 40 | var spec = new Spec(alwaysTrue); 41 | spec.isSatisfiedBy({}).then(function(flag) { 42 | flag.should.be.true; 43 | done(); 44 | }, function(reason) { 45 | throw new Error(reason); 46 | }); 47 | }); 48 | 49 | it('can be created from `true`', function(done) { 50 | var spec = Spec(alwaysTrue); 51 | spec.isSatisfiedBy({}).then(function(flag) { 52 | flag.should.be.true; 53 | done(); 54 | }, function(reason) { 55 | throw new Error(reason); 56 | }); 57 | }); 58 | 59 | it('can be created from `false`', function(done) { 60 | var spec = Spec(alwaysFalse); 61 | spec.isSatisfiedBy({}).then(function(flag) { 62 | flag.should.be.false; 63 | done(); 64 | }, function(reason) { 65 | throw new Error(reason); 66 | }); 67 | }); 68 | 69 | it('can be created from `error`', function(done) { 70 | var spec = Spec(alwaysError); 71 | spec.isSatisfiedBy({}).then(function(flag) { 72 | throw new Error('should not be here'); 73 | }, function(reason) { 74 | should.exist(reason); 75 | done(); 76 | }); 77 | }); 78 | 79 | it('can be composed', function(done) { 80 | var spec = Spec(alwaysTrue).and(alwaysTrue).or(alwaysFalse).not(); 81 | var flag = spec.isSatisfiedBy({}).then(function(flag) { 82 | flag.should.be.false; 83 | done(); 84 | }, function(reason) { 85 | throw new Error(reason); 86 | }); 87 | }); 88 | 89 | }); 90 | -------------------------------------------------------------------------------- /test/from-spec-sync.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var util = require('util'); 5 | var bspec = require('./../lib/bspec'); 6 | 7 | describe('Specification from sync specifications', function() { 8 | 9 | // 10 | function BooleanSpec(value) { 11 | this.value = value; 12 | } 13 | 14 | util.inherits(BooleanSpec, bspec.SyncSpec); 15 | 16 | BooleanSpec.prototype.isSatisfiedBy = function() { 17 | return this.value; 18 | }; 19 | 20 | // 21 | function ErrorSpec() { 22 | } 23 | 24 | util.inherits(ErrorSpec, bspec.SyncSpec); 25 | 26 | ErrorSpec.prototype.isSatisfiedBy = function() { 27 | throw new Error('some error'); 28 | }; 29 | 30 | var alwaysTrue = new BooleanSpec(true); 31 | var alwaysFalse = new BooleanSpec(false); 32 | var alwaysError = new ErrorSpec(); 33 | 34 | var Spec = bspec.SyncSpec; 35 | 36 | it('can be created with `new`', function(done) { 37 | var spec = new Spec(alwaysTrue); 38 | var flag = spec.isSatisfiedBy({}); 39 | flag.should.be.true; 40 | done(); 41 | }); 42 | 43 | it('can be created from `true`', function(done) { 44 | var spec = Spec(alwaysTrue); 45 | var flag = spec.isSatisfiedBy({}); 46 | flag.should.be.true; 47 | done(); 48 | }); 49 | 50 | it('can be created from `false`', function(done) { 51 | var spec = Spec(alwaysFalse); 52 | var flag = spec.isSatisfiedBy({}); 53 | flag.should.be.false; 54 | done(); 55 | }); 56 | 57 | it('can be created from `error`', function(done) { 58 | var spec = Spec(alwaysError); 59 | (function() { 60 | spec.isSatisfiedBy({}); 61 | }).should.throw(); 62 | done(); 63 | }); 64 | 65 | it('can be composed', function(done) { 66 | var spec = Spec(alwaysTrue).and(alwaysTrue).or(alwaysFalse).not(); 67 | var flag = spec.isSatisfiedBy({}); 68 | flag.should.be.false; 69 | done(); 70 | }); 71 | 72 | }); 73 | -------------------------------------------------------------------------------- /test/incomplete.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('es6-promise').polyfill(); 4 | 5 | var should = require('should'); 6 | var util = require('util'); 7 | var bspec = require('./../lib/bspec'); 8 | 9 | var CallbackSpec = bspec.CallbackSpec; 10 | var SyncSpec = bspec.SyncSpec; 11 | var PromiseSpec = bspec.PromiseSpec; 12 | 13 | 14 | describe('Incomplete', function() { 15 | 16 | describe('Callback-based specification', function() { 17 | 18 | it('cannot be constructed', function(done) { 19 | (function() { 20 | new CallbackSpec(123); 21 | }).should.throw(); 22 | done(); 23 | }); 24 | 25 | it('cannot be used', function(done) { 26 | 27 | function OverDueSpec() { 28 | } 29 | util.inherits(OverDueSpec, CallbackSpec); 30 | 31 | // NOTE: forgot to implement isSatisfiedBy 32 | 33 | var overDue = new OverDueSpec(); 34 | overDue.isSatisfiedBy({}, function(err) { 35 | should.exist(err); 36 | done(); 37 | }); 38 | 39 | }); 40 | 41 | }); 42 | 43 | describe('Sync specification', function() { 44 | 45 | it('cannot be constructed', function(done) { 46 | (function() { 47 | new SyncSpec(123); 48 | }).should.throw(); 49 | done(); 50 | }); 51 | 52 | it('cannot be used', function(done) { 53 | 54 | function OverDueSpec() { 55 | } 56 | util.inherits(OverDueSpec, SyncSpec); 57 | 58 | // NOTE: forgot to implement isSatisfiedBy 59 | 60 | var overDue = new OverDueSpec(); 61 | (function() { 62 | overDue.isSatisfiedBy({}); 63 | }).should.throw(); 64 | 65 | done(); 66 | }); 67 | 68 | }); 69 | 70 | describe('Promise-based specification', function() { 71 | 72 | it('cannot be constructed', function(done) { 73 | (function() { 74 | new PromiseSpec(123); 75 | }).should.throw(); 76 | done(); 77 | }); 78 | 79 | it('cannot be used', function(done) { 80 | 81 | function OverDueSpec() { 82 | } 83 | util.inherits(OverDueSpec, PromiseSpec); 84 | 85 | // NOTE: forgot to implement isSatisfiedBy 86 | 87 | var overDue = new OverDueSpec(); 88 | overDue.isSatisfiedBy({}).then(function(flag) { 89 | throw new Error('should not be here'); 90 | }, function(reason) { 91 | should.exist(reason); 92 | done(); 93 | }); 94 | 95 | }); 96 | 97 | }); 98 | 99 | }); -------------------------------------------------------------------------------- /test/no-func-name.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var util = require('./../lib/detail/util'); 5 | 6 | 7 | describe('Function with no name property', function() { 8 | 9 | it('can be traced', function(done) { 10 | 11 | var f = { 12 | toString: function() { return 'function myFunc(){}'; } 13 | }; 14 | 15 | var s = util.functionName(f); 16 | s.should.be.a.String; 17 | s.should.be.not.empty; 18 | 19 | done(); 20 | }); 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /test/not.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('es6-promise').polyfill(); 4 | 5 | var should = require('should'); 6 | var util = require('util'); 7 | var bspec = require('./../lib/bspec'); 8 | 9 | var CallbackSpec = bspec.CallbackSpec; 10 | var SyncSpec = bspec.SyncSpec; 11 | var PromiseSpec = bspec.PromiseSpec; 12 | 13 | 14 | describe('NOT Specification', function() { 15 | 16 | describe('Callback', function() { 17 | 18 | // TRUE 19 | function TrueSpec() { 20 | } 21 | 22 | util.inherits(TrueSpec, CallbackSpec); 23 | 24 | TrueSpec.prototype.isSatisfiedBy = function(dummy, cb) { 25 | return cb(null, true); 26 | }; 27 | 28 | // FALSE 29 | function FalseSpec() { 30 | } 31 | 32 | util.inherits(FalseSpec, CallbackSpec); 33 | 34 | FalseSpec.prototype.isSatisfiedBy = function(dummy, cb) { 35 | return cb(null, false); 36 | }; 37 | 38 | // ERROR 39 | function ErrorSpec() { 40 | } 41 | 42 | util.inherits(ErrorSpec, CallbackSpec); 43 | 44 | ErrorSpec.prototype.isSatisfiedBy = function(dummy, cb) { 45 | return cb(new Error('cannot satisfy the spec')); 46 | }; 47 | 48 | var alwaysTrue = new TrueSpec(); 49 | var alwaysFalse = new FalseSpec(); 50 | var alwaysError = new ErrorSpec(); 51 | 52 | 53 | it('can be validated for !true', function(done) { 54 | alwaysTrue.not().isSatisfiedBy({}, function(err, flag) { 55 | should.not.exist(err); 56 | flag.should.be.false; 57 | done(); 58 | }); 59 | }); 60 | 61 | it('can be validated for !false', function(done) { 62 | alwaysFalse.not().isSatisfiedBy({}, function(err, flag) { 63 | should.not.exist(err); 64 | flag.should.be.true; 65 | done(); 66 | }); 67 | }); 68 | 69 | it('should return an error for error', function(done) { 70 | alwaysError.not().isSatisfiedBy({}, function(err) { 71 | should.exist(err); 72 | done(); 73 | }); 74 | }); 75 | 76 | }); 77 | 78 | describe('Sync', function() { 79 | 80 | // TRUE 81 | function TrueSpec() { 82 | } 83 | 84 | util.inherits(TrueSpec, SyncSpec); 85 | 86 | TrueSpec.prototype.isSatisfiedBy = function() { 87 | return true; 88 | }; 89 | 90 | // FALSE 91 | function FalseSpec() { 92 | } 93 | 94 | util.inherits(FalseSpec, SyncSpec); 95 | 96 | FalseSpec.prototype.isSatisfiedBy = function() { 97 | return false; 98 | }; 99 | 100 | var alwaysTrue = new TrueSpec(); 101 | var alwaysFalse = new FalseSpec(); 102 | 103 | 104 | it('can be validated for !true', function(done) { 105 | var flag = alwaysTrue.not().isSatisfiedBy({}); 106 | flag.should.be.false; 107 | done(); 108 | }); 109 | 110 | it('can be validated for !false', function(done) { 111 | var flag = alwaysFalse.not().isSatisfiedBy({}); 112 | flag.should.be.true; 113 | done(); 114 | }); 115 | 116 | }); 117 | 118 | 119 | describe('Promise', function() { 120 | 121 | // TRUE 122 | function TrueSpec() { 123 | } 124 | 125 | util.inherits(TrueSpec, PromiseSpec); 126 | 127 | TrueSpec.prototype.isSatisfiedBy = function() { 128 | return Promise.resolve(true); 129 | }; 130 | 131 | // FALSE 132 | function FalseSpec() { 133 | } 134 | 135 | util.inherits(FalseSpec, PromiseSpec); 136 | 137 | FalseSpec.prototype.isSatisfiedBy = function() { 138 | return Promise.resolve(false); 139 | }; 140 | 141 | var alwaysTrue = new TrueSpec(); 142 | var alwaysFalse = new FalseSpec(); 143 | 144 | 145 | it('can be validated for !true', function(done) { 146 | alwaysTrue.not().isSatisfiedBy({}).then(function(flag) { 147 | flag.should.be.false; 148 | done(); 149 | }, function(reason) { 150 | throw new Error(reason); 151 | }); 152 | }); 153 | 154 | it('can be validated for !false', function(done) { 155 | alwaysFalse.not().isSatisfiedBy({}).then(function(flag) { 156 | flag.should.be.true; 157 | done(); 158 | }, function(reason) { 159 | throw new Error(reason); 160 | }); 161 | }); 162 | 163 | }); 164 | 165 | }); 166 | -------------------------------------------------------------------------------- /test/or.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('es6-promise').polyfill(); 4 | 5 | var should = require('should'); 6 | var util = require('util'); 7 | var bspec = require('./../lib/bspec'); 8 | 9 | var CallbackSpec = bspec.CallbackSpec; 10 | var SyncSpec = bspec.SyncSpec; 11 | var PromiseSpec = bspec.PromiseSpec; 12 | 13 | 14 | describe('OR Specification', function() { 15 | 16 | describe('Callback', function() { 17 | 18 | // TRUE 19 | function TrueSpec() { 20 | } 21 | 22 | util.inherits(TrueSpec, CallbackSpec); 23 | 24 | TrueSpec.prototype.isSatisfiedBy = function(dummy, cb) { 25 | return cb(null, true); 26 | }; 27 | 28 | // FALSE 29 | function FalseSpec() { 30 | } 31 | 32 | util.inherits(FalseSpec, CallbackSpec); 33 | 34 | FalseSpec.prototype.isSatisfiedBy = function(dummy, cb) { 35 | return cb(null, false); 36 | }; 37 | 38 | // ERROR 39 | function ErrorSpec() { 40 | } 41 | 42 | util.inherits(ErrorSpec, CallbackSpec); 43 | 44 | ErrorSpec.prototype.isSatisfiedBy = function(dummy, cb) { 45 | return cb(new Error('cannot satisfy the spec')); 46 | }; 47 | 48 | var alwaysTrue = new TrueSpec(); 49 | var alwaysFalse = new FalseSpec(); 50 | var alwaysError = new ErrorSpec(); 51 | 52 | 53 | it('can be validated for true-true', function(done) { 54 | alwaysTrue.or(alwaysTrue).isSatisfiedBy({}, function(err, flag) { 55 | should.not.exist(err); 56 | flag.should.be.true; 57 | done(); 58 | }); 59 | }); 60 | 61 | it('can be validated for true-false', function(done) { 62 | alwaysTrue.or(alwaysFalse).isSatisfiedBy({}, function(err, flag) { 63 | should.not.exist(err); 64 | flag.should.be.true; 65 | done(); 66 | }); 67 | }); 68 | 69 | it('can be validated for false-true', function(done) { 70 | alwaysFalse.or(alwaysTrue).isSatisfiedBy({}, function(err, flag) { 71 | should.not.exist(err); 72 | flag.should.be.true; 73 | done(); 74 | }); 75 | }); 76 | 77 | it('can be validated for false-false', function(done) { 78 | alwaysFalse.or(alwaysFalse).isSatisfiedBy({}, function(err, flag) { 79 | should.not.exist(err); 80 | flag.should.be.false; 81 | done(); 82 | }); 83 | }); 84 | 85 | it('can be validated for true-true-true', function(done) { 86 | alwaysTrue.or(alwaysTrue).or(alwaysTrue).isSatisfiedBy({}, function(err, flag) { 87 | should.not.exist(err); 88 | flag.should.be.true; 89 | done(); 90 | }); 91 | }); 92 | 93 | it('can be validated for true-true-false', function(done) { 94 | alwaysTrue.or(alwaysTrue).or(alwaysFalse).isSatisfiedBy({}, function(err, flag) { 95 | should.not.exist(err); 96 | flag.should.be.true; 97 | done(); 98 | }); 99 | }); 100 | 101 | it('should return an error for error-true', function(done) { 102 | alwaysError.or(alwaysTrue).isSatisfiedBy({}, function(err) { 103 | should.exist(err); 104 | done(); 105 | }); 106 | }); 107 | 108 | it('should return an error for error-false', function(done) { 109 | alwaysError.or(alwaysFalse).isSatisfiedBy({}, function(err) { 110 | should.exist(err); 111 | done(); 112 | }); 113 | }); 114 | 115 | it('should return an error for false-error', function(done) { 116 | alwaysFalse.or(alwaysError).isSatisfiedBy({}, function(err) { 117 | should.exist(err); 118 | done(); 119 | }); 120 | }); 121 | 122 | it('should return true for true-error', function(done) { 123 | alwaysTrue.or(alwaysError).isSatisfiedBy({}, function(err, flag) { 124 | should.not.exist(err); 125 | flag.should.be.true; 126 | done(); 127 | }); 128 | }); 129 | 130 | }); 131 | 132 | describe('Sync', function() { 133 | 134 | // TRUE 135 | function TrueSpec() { 136 | } 137 | 138 | util.inherits(TrueSpec, SyncSpec); 139 | 140 | TrueSpec.prototype.isSatisfiedBy = function() { 141 | return true; 142 | }; 143 | 144 | // FALSE 145 | function FalseSpec() { 146 | } 147 | 148 | util.inherits(FalseSpec, SyncSpec); 149 | 150 | FalseSpec.prototype.isSatisfiedBy = function() { 151 | return false; 152 | }; 153 | 154 | var alwaysTrue = new TrueSpec(); 155 | var alwaysFalse = new FalseSpec(); 156 | 157 | it('can be validated for true-true', function(done) { 158 | var flag = alwaysTrue.or(alwaysTrue).isSatisfiedBy({}); 159 | flag.should.be.true; 160 | done(); 161 | }); 162 | 163 | it('can be validated for true-false', function(done) { 164 | var flag = alwaysTrue.or(alwaysFalse).isSatisfiedBy({}); 165 | flag.should.be.true; 166 | done(); 167 | }); 168 | 169 | it('can be validated for false-true', function(done) { 170 | var flag = alwaysFalse.or(alwaysTrue).isSatisfiedBy({}); 171 | flag.should.be.true; 172 | done(); 173 | }); 174 | 175 | it('can be validated for false-false', function(done) { 176 | var flag = alwaysFalse.or(alwaysFalse).isSatisfiedBy({}); 177 | flag.should.be.false; 178 | done(); 179 | }); 180 | 181 | it('can be validated for true-true-true', function(done) { 182 | var flag = alwaysTrue.or(alwaysTrue).or(alwaysTrue).isSatisfiedBy({}); 183 | flag.should.be.true; 184 | done(); 185 | }); 186 | 187 | it('can be validated for true-true-false', function(done) { 188 | var flag = alwaysTrue.or(alwaysTrue).or(alwaysFalse).isSatisfiedBy({}); 189 | flag.should.be.true; 190 | done(); 191 | }); 192 | 193 | }); 194 | 195 | describe('Promise', function() { 196 | 197 | // TRUE 198 | function TrueSpec() { 199 | } 200 | 201 | util.inherits(TrueSpec, PromiseSpec); 202 | 203 | TrueSpec.prototype.isSatisfiedBy = function() { 204 | return Promise.resolve(true); 205 | }; 206 | 207 | // FALSE 208 | function FalseSpec() { 209 | } 210 | 211 | util.inherits(FalseSpec, PromiseSpec); 212 | 213 | FalseSpec.prototype.isSatisfiedBy = function() { 214 | return Promise.resolve(false); 215 | }; 216 | 217 | var alwaysTrue = new TrueSpec(); 218 | var alwaysFalse = new FalseSpec(); 219 | 220 | it('can be validated for true-true', function(done) { 221 | alwaysTrue.or(alwaysTrue).isSatisfiedBy({}).then(function(flag) { 222 | flag.should.be.true; 223 | done(); 224 | }, function(reason) { 225 | throw new Error(reason); 226 | }); 227 | }); 228 | 229 | it('can be validated for true-false', function(done) { 230 | alwaysTrue.or(alwaysFalse).isSatisfiedBy({}).then(function(flag) { 231 | flag.should.be.true; 232 | done(); 233 | }, function(reason) { 234 | throw new Error(reason); 235 | }); 236 | }); 237 | 238 | it('can be validated for false-true', function(done) { 239 | alwaysFalse.or(alwaysTrue).isSatisfiedBy({}).then(function(flag) { 240 | flag.should.be.true; 241 | done(); 242 | }, function(reason) { 243 | throw new Error(reason); 244 | }); 245 | }); 246 | 247 | it('can be validated for false-false', function(done) { 248 | alwaysFalse.or(alwaysFalse).isSatisfiedBy({}).then(function(flag) { 249 | flag.should.be.false; 250 | done(); 251 | }, function(reason) { 252 | throw new Error(reason); 253 | }); 254 | }); 255 | 256 | it('can be validated for true-true-true', function(done) { 257 | alwaysTrue.or(alwaysTrue).or(alwaysTrue).isSatisfiedBy({}).then(function(flag) { 258 | flag.should.be.true; 259 | done(); 260 | }, function(reason) { 261 | throw new Error(reason); 262 | }); 263 | }); 264 | 265 | it('can be validated for true-true-false', function(done) { 266 | alwaysTrue.or(alwaysTrue).or(alwaysFalse).isSatisfiedBy({}).then(function(flag) { 267 | flag.should.be.true; 268 | done(); 269 | }, function(reason) { 270 | throw new Error(reason); 271 | }); 272 | }); 273 | 274 | }); 275 | 276 | }); 277 | --------------------------------------------------------------------------------