forked from facebook/react
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmocks.js
More file actions
425 lines (367 loc) · 11.4 KB
/
mocks.js
File metadata and controls
425 lines (367 loc) · 11.4 KB
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
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/**
* Copyright 2013-2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule mocks
*/
function isA(typeName, value) {
return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
}
function getType(ref) {
if (isA('RegExp', ref)) {
return 'regexp';
}
if (isA('Array', ref)) {
return 'array';
}
if (isA('Function', ref)) {
return 'function';
}
if (isA('Object', ref)) {
return 'object';
}
// consider number and string fields to be constants that we want to
// pick up as they are
if (isA('Number', ref) || isA('String', ref)) {
return 'constant';
}
return null;
}
function makeComponent(metadata) {
switch (metadata.type) {
case 'object':
return {};
case 'array':
return [];
case 'regexp':
return new RegExp();
case 'constant':
return metadata.value;
case 'function':
var defaultReturnValue;
var specificReturnValues = [];
var mockImpl;
var isReturnValueLastSet = false;
var calls = [];
var instances = [];
var prototype =
(metadata.members && metadata.members.prototype &&
metadata.members.prototype.members) || {};
var f = function() {
global.dirtyMocks.push(f);
instances.push(this);
calls.push(Array.prototype.slice.call(arguments));
if (this instanceof arguments.callee) {
// This is probably being called as a constructor
for (var slot in prototype) {
// Copy prototype methods to the instance to make
// it easier to interact with mock instance call and
// return values
if (prototype[slot].type == 'function') {
var protoImpl = this[slot];
this[slot] = generateFromMetadata(prototype[slot]);
this[slot]._protoImpl = protoImpl;
}
}
// Run the mock constructor implementation
mockImpl && mockImpl.apply(this, arguments);
return;
}
var returnValue;
// If return value is last set, either specific or default, i.e.
// mockReturnValueOnce()/mockReturnValue() is called and no
// mockImplementation() is called after that.
// use the set return value.
if (isReturnValueLastSet) {
returnValue = specificReturnValues.shift();
if (returnValue === undefined) {
returnValue = defaultReturnValue;
}
}
// If mockImplementation() is last set, or specific return values
// are used up, use the mock implementation.
if (mockImpl && returnValue === undefined) {
return mockImpl.apply(this, arguments);
}
// Otherwise use prototype implementation
if (returnValue === undefined && arguments.callee._protoImpl) {
return arguments.callee._protoImpl.apply(this, arguments);
}
return returnValue;
};
f._isMockFunction = true;
f.mock = {
calls : calls,
instances : instances
};
f.mockClear = function() {
calls.length = 0;
instances.length = 0;
};
f.mockReturnValueOnce = function(value) {
// next function call will return this value or default return value
isReturnValueLastSet = true;
specificReturnValues.push(value);
return f;
};
f.mockReturnValue = function(value) {
// next function call will return specified return value or this one
isReturnValueLastSet = true;
defaultReturnValue = value;
return f;
};
f.mockImplementation = function(fn) {
// next function call will use mock implementation return value
isReturnValueLastSet = false;
mockImpl = fn;
return f;
};
f.mockReturnThis = function() {
return f.mockImplementation(function() {
return this;
});
};
f._getMockImplementation = function() {
return mockImpl;
};
if (metadata.mockImpl) {
f.mockImplementation(metadata.mockImpl);
}
return f;
}
throw new Error('Unrecognized type ' + metadata.type);
}
function generateFromMetadata(_metadata) {
var callbacks = [];
var refs = {};
function generateMock(metadata) {
var mock = makeComponent(metadata);
if (metadata.ref_id != null) {
refs[metadata.ref_id] = mock;
}
function getRefCallback(slot, ref) {
return function() {
mock[slot] = refs[ref];
};
}
for (var slot in metadata.members) {
var slotMetadata = metadata.members[slot];
if (slotMetadata.ref != null) {
callbacks.push(getRefCallback(slot, slotMetadata.ref));
} else {
mock[slot] = generateMock(slotMetadata);
}
}
return mock;
}
var mock = generateMock(_metadata);
callbacks.forEach(function(setter) {
setter();
});
return mock;
}
function _getMetadata(component, _refs) {
var refs = _refs || [];
// This is a potential performance drain, since the whole list is scanned
// for every component
var ref = refs.indexOf(component);
if (ref > -1) {
return {ref: ref};
}
var type = getType(component);
if (!type) {
return null;
}
var metadata = {type : type};
if (type == 'constant') {
metadata.value = component;
return metadata;
} else if (type == 'function') {
if (component._isMockFunction) {
metadata.mockImpl = component._getMockImplementation();
}
}
metadata.ref_id = refs.length;
refs.push(component);
var members = null;
function addMember(slot, data) {
if (!data) {
return;
}
if (!members) {
members = {};
}
members[slot] = data;
}
// Leave arrays alone
if (type != 'array') {
for (var slot in component) {
if (slot.charAt(0) == '_' ||
(type == 'function' && component._isMockFunction &&
slot.match(/^mock/))) {
continue;
}
if (component.hasOwnProperty(slot) ||
(type == 'object' && component[slot] != Object.prototype[slot])) {
addMember(slot, _getMetadata(component[slot], refs));
}
}
// If component is native code function, prototype might be undefined
if (type == 'function' && component.prototype) {
var prototype = _getMetadata(component.prototype, refs);
if (prototype && prototype.members) {
addMember('prototype', prototype);
}
}
}
if (members) {
metadata.members = members;
}
return metadata;
}
function removeUnusedRefs(metadata) {
function visit(md, f) {
f(md);
if (md.members) {
for (var slot in md.members) {
visit(md.members[slot], f);
}
}
}
var usedRefs = {};
visit(metadata, function(md) {
if (md.ref != null) {
usedRefs[md.ref] = true;
}
});
visit(metadata, function(md) {
if (!usedRefs[md.ref_id]) {
delete md.ref_id;
}
});
}
var global = Function("return this")();
global.dirtyMocks = global.dirtyMocks || [];
module.exports = {
/**
* Invokes the .mockClear method of all function mocks that have been
* called since the last time clear was called.
*/
clear: function() {
var old = global.dirtyMocks;
global.dirtyMocks = [];
old.forEach(function(mock) {
mock.mockClear();
});
},
/**
* Generates a mock based on the given metadata. Mocks treat functions
* specially, and all mock functions have additional members, described in the
* documentation for getMockFunction in this module.
*
* One important note: function prototoypes are handled specially by this
* mocking framework. For functions with prototypes, when called as a
* constructor, the mock will install mocked function members on the instance.
* This allows different instances of the same constructor to have different
* values for its mocks member and its return values.
*
* @param metadata Metadata for the mock in the schema returned by the
* getMetadata method of this module.
*
*/
generateFromMetadata: generateFromMetadata,
/**
* Inspects the argument and returns its schema in the following recursive
* format:
* {
* type: ...
* members : {}
* }
*
* Where type is one of 'array', 'object', 'function', or 'ref', and members
* is an optional dictionary where the keys are member names and the values
* are metadata objects. Function prototypes are defined simply by defining
* metadata for the member.prototype of the function. The type of a function
* prototype should always be "object". For instance, a simple class might be
* defined like this:
*
* {
* type: 'function',
* members: {
* staticMethod: {type: 'function'},
* prototype: {
* type: 'object',
* members: {
* instanceMethod: {type: 'function'}
* }
* }
* }
* }
*
* Metadata may also contain references to other objects defined within the
* same metadata object. The metadata for the referent must be marked with
* 'ref_id' key and an arbitrary value. The referer must be marked with a
* 'ref' key that has the same value as object with ref_id that it refers to.
* For instance, this metadata blob:
* {
* type: 'object',
* ref_id: 1,
* members: {
* self: {ref: 1}
* }
* }
*
* defines an object with a slot named 'self' that refers back to the object.
*
* @param component The component for which to retrieve metadata.
*/
getMetadata: function(component) {
var metadata = _getMetadata(component);
// to make it easier to work with mock metadata, only preserve references
// that are actually used
removeUnusedRefs(metadata);
return metadata;
},
/**
* Generates a stand-alone function with members that help drive unit tests or
* confirm expectations. Specifically, functions returned by this method have
* the following members:
*
* .mock:
* An object with two members, "calls", and "instances", which are both
* lists. The items in the "calls" list are the arguments with which the
* function was called. The "instances" list stores the value of 'this' for
* each call to the function. This is useful for retrieving instances from a
* constructor.
*
* .mockReturnValueOnce(value)
* Pushes the given value onto a FIFO queue of return values for the
* function.
*
* .mockReturnValue(value)
* Sets the default return value for the function.
*
* .mockImplementation(function)
* Sets a mock implementation for the function.
*
* .mockReturnThis()
* Syntactic sugar for .mockImplementation(function() {return this;})
*
* In case both mockImplementation() and
* mockReturnValueOnce()/mockReturnValue() are called. The priority of
* which to use is based on what is the last call:
* - if the last call is mockReturnValueOnce() or mockReturnValue(),
* use the specific return specific return value or default return value.
* If specific return values are used up or no default return value is set,
* fall back to try mockImplementation();
* - if the last call is mockImplementation(), run the given implementation
* and return the result.
*/
getMockFunction: function() {
return makeComponent({type: 'function'});
}
};