-
Notifications
You must be signed in to change notification settings - Fork 2
/
leche.js
374 lines (308 loc) · 11.6 KB
/
leche.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
/*!
Copyright 2014 Box, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(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.leche = 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<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/**
* @fileoverview A JavaScript testing utility designed to work with Mocha and Sinon
* @author nzakas
*/
'use strict';
/*global describe*/
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
/*
* Because this isn't a test file, we need to include mocha explicitly so that
* the globals (describe(), it(), etc.) are available when running in Node.js.
* For the browser, we pass in an option to omit Mocha from the build, as we want
* to use the global Mocha functions and not bundle Mocha with it (which could
* introduce incompatibility issues). So this line is effectively just for Node.js
* and is ignored in the browserified version.
*/
require('mocha');
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
/**
* Determines if a given property is an accessor property of an object. This is
* important because accessor properties have functions that will still execute
* after Leche inherits from that object, effectively keeping functionality alive
* on the fake object.
* @param {Object} object The object to check.
* @param {string} key The property name to check.
* @returns {boolean} True if it's an accessor property, false if not.
* @private
*/
function isAccessorProperty(object, key) {
var result = false;
// make sure this works in older browsers without error
if (Object.getOwnPropertyDescriptor && object.hasOwnProperty(key)) {
var descriptor = Object.getOwnPropertyDescriptor(object, key);
result = !('value' in descriptor);
}
return result;
}
/**
* Determines if a given property is a data property in ES5. This is
* important because we can overwrite data properties with getters in ES5,
* but not in ES3.
* @param {Object} object The object to check.
* @param {string} key The property name to check.
* @returns {boolean} True if it's an ES5 data property, false if not.
* @private
*/
function isES5DataProperty(object, key) {
var result = false;
// make sure this works in older browsers without error
if (Object.getOwnPropertyDescriptor && object.hasOwnProperty(key)) {
var descriptor = Object.getOwnPropertyDescriptor(object, key);
result = ('value' in descriptor) && (typeof descriptor.value !== 'function');
}
return result;
}
/**
* Determines if a given property is a data property in ES3.
* @param {Object} object The object to check.
* @param {string} key The property name to check.
* @returns {boolean} True if it's an ES5 data property, false if not.
* @private
*/
function isES3DataProperty(object, key) {
return typeof object[key] !== 'function';
}
/**
* An abstraction of Object.create() to ensure that this library can also be used
* in browsers.
* @param {Object} proto The object that should be the prototype of a new object.
* @returns {Object} A new object whose prototype is proto.
* @see http://javascript.crockford.com/prototypal.html
* @private
*/
function createObject(proto) {
function F() {}
F.prototype = proto;
return new F();
}
/**
* Returns the first maxLen characters of a the JSON string representation of
* the given object.
*
* @param {Object} object The object to get a string representation for.
* @param {number} maxLen The number of characters to truncate to.
* @returns {string} The first maxLen characters of the JSON string.
*/
function truncatedJSONStringify(object, maxLen) {
return JSON.stringify(object).slice(0, maxLen);
}
/**
* Gets a string representation of an object. If there is a usable toString()
* implementation, then it will return that. Otherwise, it will attempt to get
* a more specific string representation by getting the JSON string version of
* the object. For arrays, it will attempt to stringify its items recursively.
*
* @param {*} object The object to get a string representation for.
* @param {number} maxDepth The maximum depth to recurse for arrays.
* @returns {string} The string representation of the object.
* @private
*/
function stringifyObject(object, maxDepth) {
if (typeof object === 'undefined') {
return 'undefined';
} else if (object === null) {
return 'null';
}
var stringRepresentation = object.toString();
if (object instanceof Array && maxDepth > 0) {
return object.map(function(item) {
return stringifyObject(item, maxDepth - 1);
}).toString();
} else if (stringRepresentation === '[object Object]') {
return truncatedJSONStringify(object, 30);
} else {
return stringRepresentation;
}
}
/**
* Converts an array into an object whose keys are a string representation of the
* each array item and whose values are each array item. This is to normalize the
* information into an object so that other operations can assume objects are
* always used. For an array like this:
*
* [ "foo", "bar" ]
*
* It creates an object like this:
*
* { "foo": "foo", "bar": "bar" }
*
* If there are duplicate values in the array, only the last value is represented
* in the resulting object.
*
* @param {Array} array The array to convert.
* @returns {Object} An object representing the array.
* @private
*/
function createNamedDataset(array) {
var result = {};
for (var i = 0, len = array.length; i < len; i++) {
result[stringifyObject(array[i], 1)] = array[i];
}
return result;
}
/**
* Used by eos.create() as the default implementation for each method.
* @returns {void}
* @private
*/
function noop() {
// intentionally blank
}
//------------------------------------------------------------------------------
// Public
//------------------------------------------------------------------------------
/**
* @module leche
*/
module.exports = {
/**
* Creates a new object with the specified methods. All methods do nothing,
* so the resulting object is suitable for use in a variety of situations.
* @param {string[]} methods The method names to create methods for.
* @returns {Object} A new object with the specified methods defined.
*/
create: function(methods) {
var object = {};
for (var i = 0, len = methods.length; i < len; i++) {
// it's safe to use the same method for all since it doesn't do anything
object[methods[i]] = noop;
}
return object;
},
/**
* Creates a fake based on the given object. The fake has the template as
* its prototype and all methods are stubbed out to throw an error when
* called. The intent is to create an object that can be used with
* sinon.mock().
* @param {Object} template The object to base the fake off of.
* @returns {Object} A fake with the same methods as template.
*/
fake: function(template) {
var fake = createObject(template);
for (var key in fake) {
/*eslint-disable no-loop-func*/
// intentionally don't use hasOwnProperty() to get all prototype methods
if (isAccessorProperty(template, key)) { // must check against template, not fake
/*
* It's impossible to create an object that doesn't have a property
* that is an accessor on its own prototype. The best we can do
* is create a value property of the same name that has no initial
* value. It's not perfect, but it does prevent errors that occur
* when the accessor methods assume the object is real.
*/
Object.defineProperty(fake, key, {
value: undefined,
writable: true,
enumerable: true,
configurable: true
});
} else if (isES5DataProperty(template, key)) {
(function(propertyKey) {
var propertyIsSet = false,
propertyValue;
Object.defineProperty(fake, key, {
get: function() {
if (propertyIsSet) {
return propertyValue;
}
throw new Error('Unexpected use of property "' + propertyKey + '".');
},
set: function(value) {
propertyIsSet = true;
propertyValue = value;
// if not for https://github.com/box/leche/issues/14, we could do this
// Object.defineProperty(this, key, {
// value: value,
// writable: true
// });
},
enumerable: true,
configurable: true
});
}(key));
} else if (isES3DataProperty(template, key)) {
// can't do anything special for ES3, so just assign undefined
fake[key] = undefined;
} else if (typeof fake[key] === 'function') {
fake[key] = (function(methodKey) {
return function() {
throw new Error('Unexpected call to method "' + methodKey + '".');
};
}(key));
}
/*eslint-enable no-loop-func*/
}
return fake;
},
/**
* A data provider for use with Mocha. Use this around a call to it() to run
* the test over a series of data.
* @param {Object|Array} dataset The data to test.
* @param {Function} testFunction The function to call for each piece of data.
* @returns {void}
* @throws {Error} If dataset is missing or an empty array.
*/
withData: function(dataset, testFunction) {
// check for missing or null argument
if (typeof dataset !== 'object' || dataset === null) {
throw new Error('First argument must be an object or non-empty array.');
}
/*
* The dataset needs to be normalized so it looks like:
* {
* "name1": [ "data1", "data2" ],
* "name2": [ "data3", "data4" ],
* }
*/
var namedDataset = dataset;
if (dataset instanceof Array) {
// arrays must have at least one item
if (dataset.length) {
namedDataset = createNamedDataset(dataset);
} else {
throw new Error('First argument must be an object or non-empty array.');
}
}
/*
* For each name, create a new describe() block containing the name.
* This causes the dataset info to be output into the console, making
* it easier to determine which dataset caused a problem when there's an
* error.
*/
for (var name in namedDataset) {
if (namedDataset.hasOwnProperty(name)) {
/*eslint-disable no-loop-func*/
describe('with ' + name, (function(dataName) {
return function() {
var args = namedDataset[dataName];
if (!(args instanceof Array)) {
args = [args];
}
testFunction.apply(this, args);
};
}(name)));
/*eslint-enable no-loop-func*/
}
}
}
};
},{"mocha":2}],2:[function(require,module,exports){
},{}]},{},[1])(1)
});