1// jscs:disable requireUseStrict 2 3var test = require('tape'); 4 5var functionBind = require('../implementation'); 6var getCurrentContext = function () { return this; }; 7 8test('functionBind is a function', function (t) { 9 t.equal(typeof functionBind, 'function'); 10 t.end(); 11}); 12 13test('non-functions', function (t) { 14 var nonFunctions = [true, false, [], {}, 42, 'foo', NaN, /a/g]; 15 t.plan(nonFunctions.length); 16 for (var i = 0; i < nonFunctions.length; ++i) { 17 try { functionBind.call(nonFunctions[i]); } catch (ex) { 18 t.ok(ex instanceof TypeError, 'throws when given ' + String(nonFunctions[i])); 19 } 20 } 21 t.end(); 22}); 23 24test('without a context', function (t) { 25 t.test('binds properly', function (st) { 26 var args, context; 27 var namespace = { 28 func: functionBind.call(function () { 29 args = Array.prototype.slice.call(arguments); 30 context = this; 31 }) 32 }; 33 namespace.func(1, 2, 3); 34 st.deepEqual(args, [1, 2, 3]); 35 st.equal(context, getCurrentContext.call()); 36 st.end(); 37 }); 38 39 t.test('binds properly, and still supplies bound arguments', function (st) { 40 var args, context; 41 var namespace = { 42 func: functionBind.call(function () { 43 args = Array.prototype.slice.call(arguments); 44 context = this; 45 }, undefined, 1, 2, 3) 46 }; 47 namespace.func(4, 5, 6); 48 st.deepEqual(args, [1, 2, 3, 4, 5, 6]); 49 st.equal(context, getCurrentContext.call()); 50 st.end(); 51 }); 52 53 t.test('returns properly', function (st) { 54 var args; 55 var namespace = { 56 func: functionBind.call(function () { 57 args = Array.prototype.slice.call(arguments); 58 return this; 59 }, null) 60 }; 61 var context = namespace.func(1, 2, 3); 62 st.equal(context, getCurrentContext.call(), 'returned context is namespaced context'); 63 st.deepEqual(args, [1, 2, 3], 'passed arguments are correct'); 64 st.end(); 65 }); 66 67 t.test('returns properly with bound arguments', function (st) { 68 var args; 69 var namespace = { 70 func: functionBind.call(function () { 71 args = Array.prototype.slice.call(arguments); 72 return this; 73 }, null, 1, 2, 3) 74 }; 75 var context = namespace.func(4, 5, 6); 76 st.equal(context, getCurrentContext.call(), 'returned context is namespaced context'); 77 st.deepEqual(args, [1, 2, 3, 4, 5, 6], 'passed arguments are correct'); 78 st.end(); 79 }); 80 81 t.test('called as a constructor', function (st) { 82 var thunkify = function (value) { 83 return function () { return value; }; 84 }; 85 st.test('returns object value', function (sst) { 86 var expectedReturnValue = [1, 2, 3]; 87 var Constructor = functionBind.call(thunkify(expectedReturnValue), null); 88 var result = new Constructor(); 89 sst.equal(result, expectedReturnValue); 90 sst.end(); 91 }); 92 93 st.test('does not return primitive value', function (sst) { 94 var Constructor = functionBind.call(thunkify(42), null); 95 var result = new Constructor(); 96 sst.notEqual(result, 42); 97 sst.end(); 98 }); 99 100 st.test('object from bound constructor is instance of original and bound constructor', function (sst) { 101 var A = function (x) { 102 this.name = x || 'A'; 103 }; 104 var B = functionBind.call(A, null, 'B'); 105 106 var result = new B(); 107 sst.ok(result instanceof B, 'result is instance of bound constructor'); 108 sst.ok(result instanceof A, 'result is instance of original constructor'); 109 sst.end(); 110 }); 111 112 st.end(); 113 }); 114 115 t.end(); 116}); 117 118test('with a context', function (t) { 119 t.test('with no bound arguments', function (st) { 120 var args, context; 121 var boundContext = {}; 122 var namespace = { 123 func: functionBind.call(function () { 124 args = Array.prototype.slice.call(arguments); 125 context = this; 126 }, boundContext) 127 }; 128 namespace.func(1, 2, 3); 129 st.equal(context, boundContext, 'binds a context properly'); 130 st.deepEqual(args, [1, 2, 3], 'supplies passed arguments'); 131 st.end(); 132 }); 133 134 t.test('with bound arguments', function (st) { 135 var args, context; 136 var boundContext = {}; 137 var namespace = { 138 func: functionBind.call(function () { 139 args = Array.prototype.slice.call(arguments); 140 context = this; 141 }, boundContext, 1, 2, 3) 142 }; 143 namespace.func(4, 5, 6); 144 st.equal(context, boundContext, 'binds a context properly'); 145 st.deepEqual(args, [1, 2, 3, 4, 5, 6], 'supplies bound and passed arguments'); 146 st.end(); 147 }); 148 149 t.test('returns properly', function (st) { 150 var boundContext = {}; 151 var args; 152 var namespace = { 153 func: functionBind.call(function () { 154 args = Array.prototype.slice.call(arguments); 155 return this; 156 }, boundContext) 157 }; 158 var context = namespace.func(1, 2, 3); 159 st.equal(context, boundContext, 'returned context is bound context'); 160 st.notEqual(context, getCurrentContext.call(), 'returned context is not lexical context'); 161 st.deepEqual(args, [1, 2, 3], 'passed arguments are correct'); 162 st.end(); 163 }); 164 165 t.test('returns properly with bound arguments', function (st) { 166 var boundContext = {}; 167 var args; 168 var namespace = { 169 func: functionBind.call(function () { 170 args = Array.prototype.slice.call(arguments); 171 return this; 172 }, boundContext, 1, 2, 3) 173 }; 174 var context = namespace.func(4, 5, 6); 175 st.equal(context, boundContext, 'returned context is bound context'); 176 st.notEqual(context, getCurrentContext.call(), 'returned context is not lexical context'); 177 st.deepEqual(args, [1, 2, 3, 4, 5, 6], 'passed arguments are correct'); 178 st.end(); 179 }); 180 181 t.test('passes the correct arguments when called as a constructor', function (st) { 182 var expected = { name: 'Correct' }; 183 var namespace = { 184 Func: functionBind.call(function (arg) { 185 return arg; 186 }, { name: 'Incorrect' }) 187 }; 188 var returned = new namespace.Func(expected); 189 st.equal(returned, expected, 'returns the right arg when called as a constructor'); 190 st.end(); 191 }); 192 193 t.test('has the new instance\'s context when called as a constructor', function (st) { 194 var actualContext; 195 var expectedContext = { foo: 'bar' }; 196 var namespace = { 197 Func: functionBind.call(function () { 198 actualContext = this; 199 }, expectedContext) 200 }; 201 var result = new namespace.Func(); 202 st.equal(result instanceof namespace.Func, true); 203 st.notEqual(actualContext, expectedContext); 204 st.end(); 205 }); 206 207 t.end(); 208}); 209 210test('bound function length', function (t) { 211 t.test('sets a correct length without thisArg', function (st) { 212 var subject = functionBind.call(function (a, b, c) { return a + b + c; }); 213 st.equal(subject.length, 3); 214 st.equal(subject(1, 2, 3), 6); 215 st.end(); 216 }); 217 218 t.test('sets a correct length with thisArg', function (st) { 219 var subject = functionBind.call(function (a, b, c) { return a + b + c; }, {}); 220 st.equal(subject.length, 3); 221 st.equal(subject(1, 2, 3), 6); 222 st.end(); 223 }); 224 225 t.test('sets a correct length without thisArg and first argument', function (st) { 226 var subject = functionBind.call(function (a, b, c) { return a + b + c; }, undefined, 1); 227 st.equal(subject.length, 2); 228 st.equal(subject(2, 3), 6); 229 st.end(); 230 }); 231 232 t.test('sets a correct length with thisArg and first argument', function (st) { 233 var subject = functionBind.call(function (a, b, c) { return a + b + c; }, {}, 1); 234 st.equal(subject.length, 2); 235 st.equal(subject(2, 3), 6); 236 st.end(); 237 }); 238 239 t.test('sets a correct length without thisArg and too many arguments', function (st) { 240 var subject = functionBind.call(function (a, b, c) { return a + b + c; }, undefined, 1, 2, 3, 4); 241 st.equal(subject.length, 0); 242 st.equal(subject(), 6); 243 st.end(); 244 }); 245 246 t.test('sets a correct length with thisArg and too many arguments', function (st) { 247 var subject = functionBind.call(function (a, b, c) { return a + b + c; }, {}, 1, 2, 3, 4); 248 st.equal(subject.length, 0); 249 st.equal(subject(), 6); 250 st.end(); 251 }); 252}); 253