Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Extract Chai-Changes from chai-backbone-rails

  • Loading branch information...
commit ff351074bc2c9887d9a61c153707534bd669e217 1 parent 0bcdf9e
@matthijsgroen authored
View
22 LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012 Matthijs Groen
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
72 README.md
@@ -1,4 +1,74 @@
chai-changes
============
-chai-changes is an extension to the [chai](http://chaijs.com/) assertion library that provides a set of change-specific assertions.
+chai-changes is an extension to the [chai](http://chaijs.com/) assertion library that
+provides a set of change-specific assertions.
+
+Usage
+-----
+
+Include `chai-changes.js` in your test file, after `chai.js` (version 1.0.0-rc1 or later):
+
+ <script src="chai-changes.js"></script>
+
+Use the assertions with chai's `expect` or `should` assertions.
+
+Assertions
+----------
+
+All assertions use a `when` mechanism.
+
+
+Using 'expect':
+
+ expect(-> codeThatYieldsAChangedResult).to....when ->
+ executeTheCodeThatCausesTheChange()
+
+The code within the `expect` section will be executed first, then the
+code in the `when` section will be executed and then the code in the
+`expect` section will be executed again and the differences will be
+asserted.
+
+Same test using 'should':
+
+ (-> codeThatYieldsAChangedResult).should....when ->
+ executeTheCodeThatCausesTheChange()
+
+### `change`
+
+Assert if the 'expect/should' changes its outcome when 'when' is
+executed
+
+ result = 0
+ (-> result).should.change.when -> result += 1
+ expect(-> result).to.change.when -> result -= 1
+ expect(-> result).not.to.change.when -> result = result * 1
+
+### `change.by(delta)`
+
+Assert if the change of the 'expect/should' has the provided delta
+
+ result = 0
+ (-> result).should.change.by(3).when -> result += 3
+ expect(-> result).not.to.change.by(-3).when -> result += 1
+ expect(-> result).to.change.by(-2).when -> result -= 2
+
+### `change.from(startValue)`
+
+Assert if the change starts from a certain value. The value is
+compared using a deep equal.
+
+ result = ['a', 'b']
+ (-> result).should.change.from(['a', 'b']).when -> result.push('c')
+ (-> result).should.change.from(['a', 'b']).to(['a', 'b', 'c']).when -> result.push('c')
+
+### `change.to(endValue)`
+
+Assert if the change ends in a certain value. The value is
+compared using a deep equal.
+
+ result = ['a', 'b']
+ (-> result).should.change.to(['a', 'b', 'c']).when -> result.push('c')
+ (-> result).should.change.from(['a', 'b']).to(['a', 'c']).when -> result = ['a', 'c']
+
+
View
173 chai-changes.js
@@ -0,0 +1,173 @@
+(function(chaiChanges) {
+ if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
+ return module.exports = chaiChanges;
+ } else if (typeof define === "function" && define.amd) {
+ return define(function() {
+ return chaiChanges;
+ });
+ } else {
+ return chai.use(chaiChanges);
+ }
+})(function(chai, utils) {
+ var changeBy, changeByAssert, changeFrom, changeFromAssert, changeFromBeginAssert, changeTo, changeToAssert, changeToBeginAssert, flag, formatFunction, inspect, noChangeAssert;
+ inspect = utils.inspect;
+ flag = utils.flag;
+ /*
+ #
+ # Changes Matchers
+ #
+ */
+
+ chai.Assertion.addMethod('when', function(val) {
+ var action, definedActions, _i, _j, _len, _len1, _results;
+ definedActions = flag(this, 'whenActions') || [];
+ for (_i = 0, _len = definedActions.length; _i < _len; _i++) {
+ action = definedActions[_i];
+ if (typeof action.before === "function") {
+ action.before(this);
+ }
+ }
+ val();
+ _results = [];
+ for (_j = 0, _len1 = definedActions.length; _j < _len1; _j++) {
+ action = definedActions[_j];
+ _results.push(typeof action.after === "function" ? action.after(this) : void 0);
+ }
+ return _results;
+ });
+ noChangeAssert = function(context) {
+ var endValue, negate, object, relevant, result, startValue;
+ relevant = flag(context, 'no-change');
+ if (!relevant) {
+ return;
+ }
+ negate = flag(context, 'negate');
+ flag(context, 'negate', this.negate);
+ object = flag(context, 'object');
+ startValue = flag(context, 'changeStart');
+ endValue = object();
+ result = !utils.eql(endValue, startValue);
+ context.assert(result, "expected `" + (formatFunction(object)) + "` to change, but it stayed " + (utils.inspect(startValue)), "expected `" + (formatFunction(object)) + "` not to change, but it changed from " + (utils.inspect(startValue)) + " to " + (utils.inspect(endValue)));
+ return flag(context, 'negate', negate);
+ };
+ changeByAssert = function(context) {
+ var actualDelta, endValue, negate, object, startValue;
+ negate = flag(context, 'negate');
+ flag(context, 'negate', this.negate);
+ object = flag(context, 'object');
+ startValue = flag(context, 'changeStart');
+ endValue = object();
+ actualDelta = endValue - startValue;
+ context.assert(this.expectedDelta === actualDelta, "expected `" + (formatFunction(object)) + "` to change by " + this.expectedDelta + ", but it changed by " + actualDelta, "expected `" + (formatFunction(object)) + "` not to change by " + this.expectedDelta + ", but it did");
+ return flag(context, 'negate', negate);
+ };
+ changeToBeginAssert = function(context) {
+ var negate, object, result, startValue;
+ negate = flag(context, 'negate');
+ flag(context, 'negate', this.negate);
+ object = flag(context, 'object');
+ startValue = object();
+ result = !utils.eql(startValue, this.expectedEndValue);
+ if (negate) {
+ result = !result;
+ }
+ context.assert(result, "expected `" + (formatFunction(object)) + "` to change to " + (utils.inspect(this.expectedEndValue)) + ", but it was already " + (utils.inspect(startValue)), "not supported");
+ return flag(context, 'negate', negate);
+ };
+ changeToAssert = function(context) {
+ var endValue, negate, object, result;
+ negate = flag(context, 'negate');
+ flag(context, 'negate', this.negate);
+ object = flag(context, 'object');
+ endValue = object();
+ result = utils.eql(endValue, this.expectedEndValue);
+ context.assert(result, "expected `" + (formatFunction(object)) + "` to change to " + (utils.inspect(this.expectedEndValue)) + ", but it changed to " + (utils.inspect(endValue)), "expected `" + (formatFunction(object)) + "` not to change to " + (utils.inspect(this.expectedEndValue)) + ", but it did");
+ return flag(context, 'negate', negate);
+ };
+ changeFromBeginAssert = function(context) {
+ var negate, object, result, startValue;
+ negate = flag(context, 'negate');
+ flag(context, 'negate', this.negate);
+ object = flag(context, 'object');
+ startValue = object();
+ result = utils.eql(startValue, this.expectedStartValue);
+ context.assert(result, "expected the change of `" + (formatFunction(object)) + "` to start from " + (utils.inspect(this.expectedStartValue)) + ", but it started from " + (utils.inspect(startValue)), "expected the change of `" + (formatFunction(object)) + "` not to start from " + (utils.inspect(this.expectedStartValue)) + ", but it did");
+ return flag(context, 'negate', negate);
+ };
+ changeFromAssert = function(context) {
+ var endValue, negate, object, result, startValue;
+ negate = flag(context, 'negate');
+ flag(context, 'negate', this.negate);
+ object = flag(context, 'object');
+ startValue = flag(context, 'changeStart');
+ endValue = object();
+ result = !utils.eql(startValue, endValue);
+ if (negate) {
+ result = !result;
+ }
+ context.assert(result, "expected `" + (formatFunction(object)) + "` to change from " + (utils.inspect(this.expectedStartValue)) + ", but it did not change", "not supported");
+ return flag(context, 'negate', negate);
+ };
+ chai.Assertion.addProperty('change', function() {
+ var definedActions;
+ flag(this, 'no-change', true);
+ definedActions = flag(this, 'whenActions') || [];
+ definedActions.push({
+ negate: flag(this, 'negate'),
+ before: function(context) {
+ var startValue;
+ startValue = flag(context, 'object')();
+ return flag(context, 'changeStart', startValue);
+ },
+ after: noChangeAssert
+ });
+ return flag(this, 'whenActions', definedActions);
+ });
+ formatFunction = function(func) {
+ return func.toString().replace(/^\s*function \(\) {\s*/, '').replace(/\s+}$/, '').replace(/\s*return\s*/, '');
+ };
+ changeBy = function(delta) {
+ var definedActions;
+ flag(this, 'no-change', false);
+ definedActions = flag(this, 'whenActions') || [];
+ definedActions.push({
+ negate: flag(this, 'negate'),
+ expectedDelta: delta,
+ after: changeByAssert
+ });
+ return flag(this, 'whenActions', definedActions);
+ };
+ chai.Assertion.addChainableMethod('by', changeBy, function() {
+ return this;
+ });
+ changeTo = function(endValue) {
+ var definedActions;
+ flag(this, 'no-change', false);
+ definedActions = flag(this, 'whenActions') || [];
+ definedActions.push({
+ negate: flag(this, 'negate'),
+ expectedEndValue: endValue,
+ before: changeToBeginAssert,
+ after: changeToAssert
+ });
+ return flag(this, 'whenActions', definedActions);
+ };
+ chai.Assertion.addChainableMethod('to', changeTo, function() {
+ return this;
+ });
+ changeFrom = function(startValue) {
+ var definedActions;
+ flag(this, 'no-change', false);
+ definedActions = flag(this, 'whenActions') || [];
+ definedActions.push({
+ negate: flag(this, 'negate'),
+ expectedStartValue: startValue,
+ before: changeFromBeginAssert,
+ after: changeFromAssert
+ });
+ return flag(this, 'whenActions', definedActions);
+ };
+ return chai.Assertion.addChainableMethod('from', changeFrom, function() {
+ return this;
+ });
+});
View
179 chai-changes.js.coffee
@@ -0,0 +1,179 @@
+
+((chaiChanges) ->
+ # Module systems magic dance.
+ if (typeof require == "function" && typeof exports == "object" && typeof module == "object")
+ # NodeJS
+ module.exports = chaiChanges
+ else if (typeof define == "function" && define.amd)
+ # AMD
+ define -> chaiChanges
+ else
+ # Other environment (usually <script> tag): plug in to global chai instance directly.
+ chai.use chaiChanges
+)((chai, utils) ->
+ inspect = utils.inspect
+ flag = utils.flag
+
+ ###
+ #
+ # Changes Matchers
+ #
+ ###
+
+ chai.Assertion.addMethod 'when', (val) ->
+ definedActions = flag(this, 'whenActions') || []
+
+ action.before?(this) for action in definedActions
+ val() # execute the 'when'
+ action.after?(this) for action in definedActions
+
+ noChangeAssert = (context) ->
+ relevant = flag(context, 'no-change')
+ return unless relevant
+
+ negate = flag(context, 'negate')
+ flag(context, 'negate', @negate)
+ object = flag(context, 'object')
+
+ startValue = flag(context, 'changeStart')
+ endValue = object()
+
+ result = !utils.eql(endValue, startValue)
+ context.assert result,
+ "expected `#{formatFunction object}` to change, but it stayed #{utils.inspect startValue}",
+ "expected `#{formatFunction object}` not to change, but it changed from #{utils.inspect startValue} to #{utils.inspect endValue}",
+ flag(context, 'negate', negate)
+
+ changeByAssert = (context) ->
+ negate = flag(context, 'negate')
+ flag(context, 'negate', @negate)
+ object = flag(context, 'object')
+
+ startValue = flag(context, 'changeStart')
+ endValue = object()
+ actualDelta = endValue - startValue
+
+ context.assert (@expectedDelta is actualDelta),
+ "expected `#{formatFunction object}` to change by #{@expectedDelta}, but it changed by #{actualDelta}",
+ "expected `#{formatFunction object}` not to change by #{@expectedDelta}, but it did"
+ flag(context, 'negate', negate)
+
+ changeToBeginAssert = (context) ->
+ negate = flag(context, 'negate')
+ flag(context, 'negate', @negate)
+ object = flag(context, 'object')
+
+ startValue = object()
+
+ result = !utils.eql(startValue, @expectedEndValue)
+ result = !result if negate
+ context.assert result,
+ "expected `#{formatFunction object}` to change to #{utils.inspect @expectedEndValue}, but it was already #{utils.inspect startValue}",
+ "not supported"
+ flag(context, 'negate', negate)
+
+ changeToAssert = (context) ->
+ negate = flag(context, 'negate')
+ flag(context, 'negate', @negate)
+ object = flag(context, 'object')
+
+ endValue = object()
+
+ result = utils.eql(endValue, @expectedEndValue)
+ context.assert result,
+ "expected `#{formatFunction object}` to change to #{utils.inspect @expectedEndValue}, but it changed to #{utils.inspect endValue}",
+ "expected `#{formatFunction object}` not to change to #{utils.inspect @expectedEndValue}, but it did"
+ flag(context, 'negate', negate)
+
+ changeFromBeginAssert = (context) ->
+ negate = flag(context, 'negate')
+ flag(context, 'negate', @negate)
+ object = flag(context, 'object')
+
+ startValue = object()
+
+ result = utils.eql(startValue, @expectedStartValue)
+ context.assert result,
+ "expected the change of `#{formatFunction object}` to start from #{utils.inspect @expectedStartValue}, but it started from #{utils.inspect startValue}",
+ "expected the change of `#{formatFunction object}` not to start from #{utils.inspect @expectedStartValue}, but it did",
+ flag(context, 'negate', negate)
+
+ changeFromAssert = (context) ->
+ negate = flag(context, 'negate')
+ flag(context, 'negate', @negate)
+ object = flag(context, 'object')
+
+ startValue = flag(context, 'changeStart')
+ endValue = object()
+
+ result = !utils.eql(startValue, endValue)
+ result = !result if negate
+ context.assert result,
+ "expected `#{formatFunction object}` to change from #{utils.inspect @expectedStartValue}, but it did not change"
+ "not supported"
+ flag(context, 'negate', negate)
+
+ # Verifies if the subject return value changes by given delta 'when' events happen
+ #
+ # Examples:
+ # (-> resultValue).should.change.by(1).when -> resultValue += 1
+ #
+ chai.Assertion.addProperty 'change', ->
+ flag(this, 'no-change', true)
+
+ definedActions = flag(this, 'whenActions') || []
+ # Add a around filter to the when actions
+ definedActions.push
+ negate: flag(this, 'negate')
+
+ # set up the callback to trigger
+ before: (context) ->
+ startValue = flag(context, 'object')()
+ flag(context, 'changeStart', startValue)
+ after: noChangeAssert
+
+ flag(this, 'whenActions', definedActions)
+
+ formatFunction = (func) ->
+ func.toString().replace(/^\s*function \(\) {\s*/, '').replace(/\s+}$/, '').replace(/\s*return\s*/, '')
+
+ changeBy = (delta) ->
+ flag(this, 'no-change', false)
+ definedActions = flag(this, 'whenActions') || []
+ # Add a around filter to the when actions
+ definedActions.push
+ negate: flag(this, 'negate')
+ expectedDelta: delta
+ after: changeByAssert
+ flag(this, 'whenActions', definedActions)
+
+ chai.Assertion.addChainableMethod 'by', changeBy, -> this
+
+ changeTo = (endValue) ->
+ flag(this, 'no-change', false)
+ definedActions = flag(this, 'whenActions') || []
+ # Add a around filter to the when actions
+ definedActions.push
+ negate: flag(this, 'negate')
+ expectedEndValue: endValue
+ before: changeToBeginAssert
+ after: changeToAssert
+ flag(this, 'whenActions', definedActions)
+
+ chai.Assertion.addChainableMethod 'to', changeTo, -> this
+
+ changeFrom = (startValue) ->
+ flag(this, 'no-change', false)
+ definedActions = flag(this, 'whenActions') || []
+ # Add a around filter to the when actions
+ definedActions.push
+ negate: flag(this, 'negate')
+ expectedStartValue: startValue
+ before: changeFromBeginAssert
+ after: changeFromAssert
+ flag(this, 'whenActions', definedActions)
+
+ chai.Assertion.addChainableMethod 'from', changeFrom, -> this
+
+)
+
View
13 package.json
@@ -0,0 +1,13 @@
+{
+ name: 'Chai Changes',
+ desc: 'Extend Chai with assertions expected changes of results.',
+ url: 'chai-changes',
+ link: 'https://github.com/matthijsgroen/chai-changes',
+ tags: [ 'changes', 'when' ],
+ pkg: 'https://raw.github.com/matthijsgroen/chai-changes/master/package.json',
+ markdown: 'https://raw.github.com/matthijsgroen/chai-changes/master/README.md',
+ node: false,
+ browser: {
+ 'chai-changes.js': 'https://raw.github.com/matthijsgroen/chai-changes/master/chai-changes.js'
+ }
+}
View
79 test/chai-changes_spec.js.coffee
@@ -0,0 +1,79 @@
+#= require ./../chai-changes
+
+describe 'Chai-Changes', ->
+
+ describe 'change', ->
+
+ describe 'at all', ->
+
+ it 'detects changes', ->
+ result = 1
+ expect(->
+ expect(-> result).to.change.when -> result = 1
+ ).to.throw 'expected `result;` to change, but it stayed 1'
+ expect(-> result).to.change.when -> result += 1
+
+ it 'can be negated to not.change', ->
+ result = 1
+ expect(->
+ expect(-> result).not.to.change.when -> result += 2
+ ).to.throw 'expected `result;` not to change, but it changed from 1 to 3'
+ expect(-> result).to.not.change.when -> 1 + 3
+
+ describe 'by delta', ->
+
+ it 'asserts the delta of a change', ->
+ result = 1
+ expect(-> result).to.change.by(3).when -> result += 3
+ expect(-> result).not.to.change.by(2).when -> result += 3
+
+ it 'reports the contents of the subject method', ->
+ result = 1
+ expect(->
+ (-> 1 + 3; result).should.change.by(3).when -> result += 2
+ ).to.throw 'expected `1 + 3;result;` to change by 3, but it changed by 2'
+
+ describe 'to', ->
+
+ it 'asserts end values', ->
+ result = ['a']
+ expect(-> result).to.change.to(['b']).when -> result = ['b']
+ expect(-> result).not.to.change.to(['c']).when -> result = ['b']
+
+ it 'reports the mismatched end value', ->
+ result = ['a']
+ expect(->
+ expect(-> result).to.change.to(['b']).when -> result = ['c']
+ ).to.throw 'expected `result;` to change to [ \'b\' ], but it changed to [ \'c\' ]'
+
+ it 'raises an error if there was no change', ->
+ result = 'b'
+ expect(->
+ expect(-> result).to.change.to('b').when -> result = 'b'
+ ).to.throw 'expected `result;` to change to \'b\', but it was already \'b\''
+
+ describe 'from', ->
+
+ it 'asserts start values', ->
+ result = ['a']
+ expect(-> result).to.change.from(['a']).when -> result = ['b']
+ expect(-> result).to.change.not.from(['a']).when -> result = ['c']
+
+ it 'reports the mismatched start value', ->
+ result = ['a']
+ expect(->
+ expect(-> result).to.change.from(['b']).when -> result = ['c']
+ ).to.throw 'expected the change of `result;` to start from [ \'b\' ], but it started from [ \'a\' ]'
+
+ it 'raises an error if there was no change', ->
+ result = 'b'
+ expect(->
+ expect(-> result).to.change.from('b').when -> result = 'b'
+ ).to.throw 'expected `result;` to change from \'b\', but it did not change'
+
+ describe 'mix and match', ->
+
+ it 'can use from to and by in one sentence', ->
+ result = 3
+ expect(-> result).to.change.from(3).to(5).by(2).when -> result = 5
+
Please sign in to comment.
Something went wrong with that request. Please try again.