Skip to content

Commit

Permalink
test/chain-wrap: abstracted equivalence-testing pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
jonahkagan committed Nov 12, 2013
1 parent 85b745f commit 04977f5
Showing 1 changed file with 49 additions and 53 deletions.
102 changes: 49 additions & 53 deletions test/chain-wrap.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,57 @@ _s = require "#{__dirname}/../index"
sinon = require 'sinon'
_ = require 'underscore'

describe 'methods on wrapped/chained objects are the same as methods on _s', ->
for obj in [_s(), _s('a'), _s().chain(), _s('a').chain(), _s().chain().chain()]
assert.deepEqual(
_.chain().functions(obj).without('value').value()
_.chain().functions(_s).without('mixin').value()
)
# Takes an array and returns an array of adjacent pairs of elements in the
# array, wrapping around at the end.
adjacent = (arr) ->
_.zip arr, _.rest(arr).concat [_.first arr]

describe '_s(a)', ->
it 'binds a as the first argument to the next method invoked', ->
spy = sinon.spy ->
_s.mixin fn: spy
_s().fn(10)
_s().fn(10, 20)
_s('a').fn(10)
_s('a').fn(10, 20)
_s('a', 'b').fn(10)
_s('a', 'b').fn(10, 20)
assert.equal spy.callCount, 6
assert.deepEqual spy.args, [
[undefined, 10]
[undefined, 10, 20]
['a', 10]
['a', 10, 20]
['a', 10] # ignores > 1 argument
['a', 10, 20] # ignores > 1 argument
]
methods = (obj) ->
_.chain().functions(obj).without('value', 'mixin').value()

it '.fn(b) is equivalent to calling _s.fn(a,b)', ->
spy = sinon.spy ->
_s.mixin fn: spy
_s.fn 'a', 'b'
_s('a').fn('b')
assert.equal spy.callCount, 2
assert.deepEqual spy.args[0], ['a', 'b']
assert.deepEqual spy.args[0], spy.args[1]
idSpy = -> sinon.spy (x) -> x

it '.missing() throws', ->
assert.throws () ->
_s('a').missing()
testEquivalent = (exp1, exp2) ->
[spy1, spy2] = [idSpy(), idSpy()]
_s.mixin fn: spy1
v1 = exp1()

This comment has been minimized.

Copy link
@rgarcia

rgarcia Nov 12, 2013

Member

if the first call to testEquivalent has an exp1 that includes a call to fn, will it fail with no fn method found?

If so does it (gasp) make sense for this line to be eval on the expressions?

Also it'd be nice to not have leftover state after mixing in, i.e. require a fresh version of understream on every test

This comment has been minimized.

Copy link
@jonahkagan

jonahkagan Nov 12, 2013

Contributor

I don't think it will fail, since fn is mixed in before calling exp1. I'm not sure what you mean about using eval.

I agree that using a fresh Understream is a good idea. Is that possible though? I thought require cached its results.

This comment has been minimized.

Copy link
@rgarcia

rgarcia Nov 12, 2013

Member

ah, missed that exp{1,2} are callable things. I think there's a way to require and ignore cache... @azylman did it somewhere once

This comment has been minimized.

Copy link
@azylman

azylman Nov 12, 2013

Contributor

I don't think you can require while ignoring the cache. The way that I did it involved deleting the items out of the cache before requiring. The cache is a property of the require function, where the keys are the full file path (generated via path.resolve) and the values are whatever data require needs - I forget exactly what data they store there, but you only need to delete the keys to delete it out of the cache so it doesn't matter.

This comment has been minimized.

Copy link
@jonahkagan

jonahkagan Nov 13, 2013

Contributor
_s.mixin fn: spy2 # Relies on _s.mixin overwriting fn
v2 = exp2()
it 'return the same result', -> assert.deepEqual v1, v2
it 'have the same methods available on the result', -> assert.deepEqual methods(v1), methods(v2)
it 'call the method the same number of times', -> assert.equal spy1.callCount, spy2.callCount
it 'call the method with the same args', -> assert.deepEqual spy1.args, spy2.args

describe '_s(a).chain()', ->
it '.value() returns a', ->
assert.equal _s().chain().value(), undefined
assert.equal _s('a').chain().value(), 'a'
_.each
'no-op':
'plain' : -> 'a'
'unwrapped chained' : -> _s.chain('a').value()
'wrapped chained' : -> _s('a').chain().value()
'no-arg':
'unwrapped' : -> _s.fn()
'wrapped' : -> _s().fn()
'unwrapped chained' : -> _s.chain().fn().value()
'wrapped chained' : -> _s().chain().fn().value()
'one-arg':
'unwrapped' : -> _s.fn('a')
'wrapped' : -> _s('a').fn()
'unwrapped chained' : -> _s.chain('a').fn().value()
'wrapped chained' : -> _s('a').chain().fn().value()
'multi-arg':
'unwrapped' : -> _s.fn('a', {b:1}, 2)
'wrapped' : -> _s('a').fn({b:1}, 2)
'unwrapped chained' : -> _s.chain('a').fn({b:1}, 2).value()
'wrapped chained' : -> _s('a').chain().fn({b:1}, 2).value()
'multiple functions':
'unwrapped' : -> _s.fn _s.fn('a', 'b'), 'c'
'wrapped' : -> _s(_s('a').fn('b')).fn('c')
'unwrapped chained' : -> _s.chain('a').fn('b').fn('c').value()
'wrapped chained' : -> _s('a').chain().fn('b').fn('c').value()

it '.fn1(b).fn2(c).value() is equivalent to calling _s.fn2(_s.fn1(a, b), c)', ->
spy1 = sinon.spy -> 1
spy2 = sinon.spy -> 2
_s.mixin {fn1: spy1, fn2: spy2}
val = _s('a').chain().fn1('b').fn2('c').value()
assert.equal val, 2
assert.equal val, _s.fn2(_s.fn1('a', 'b'), 'c')
assert.equal spy1.callCount, 2
assert.equal spy2.callCount, 2
assert.deepEqual spy1.args[0], ['a', 'b']
assert.deepEqual spy1.args[1], ['a', 'b']
assert.deepEqual spy2.args[0], [1, 'c']
assert.deepEqual spy2.args[1], [1, 'c']
, (exps, desc) ->
describe desc, ->
# Since equivalence is transitive, to assert that a group of expressions
# are equivalent, we can assert that each one is equivalent to one other
# one.
_.each adjacent(_.pairs(exps)), ([[name1, exp1], [name2, exp2]]) ->
describe "#{name1}/#{name2}", -> testEquivalent exp1, exp2

5 comments on commit 04977f5

@jonahkagan
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this line near top: _s = _. The same tests passed and failed!

@rgarcia
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like we need some way to test equivalence of arguments arrays that considers [] equivalent to [undefined]

@rgarcia
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe trim trailing undefineds from the arguments arrays before deepequal'ing?

@jonahkagan
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless we think that the function should be called with no args instead of one undefined arg...

@rgarcia
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm interesting thought

Please sign in to comment.