lil-mocky 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -0
- package/CLAUDE.md +191 -0
- package/Dockerfile +2 -0
- package/README.md +705 -0
- package/TODO +57 -0
- package/init-docker.sh +4 -0
- package/lil-mocky.sublime-project +8 -0
- package/lil-mocky.sublime-workspace +2405 -0
- package/package.json +14 -0
- package/src/lil-mocky.js +329 -0
- package/test/lilMockyTest.js +867 -0
- package/test-docker.sh +3 -0
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lil-mocky",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"main": "src/lil-mocky.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "mocha test/lilMockyTest.js"
|
|
7
|
+
},
|
|
8
|
+
"author": "Doug Mcleod <doug@dougmcleod.ca>",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"chai": "^4.3.3",
|
|
12
|
+
"mocha": "^10.7.3"
|
|
13
|
+
}
|
|
14
|
+
}
|
package/src/lil-mocky.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
function mockBuilder(builder) {
|
|
2
|
+
return builder.build();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function functionBuilder(body) {
|
|
6
|
+
const options = {
|
|
7
|
+
async: false,
|
|
8
|
+
body: body,
|
|
9
|
+
args: [],
|
|
10
|
+
select: [],
|
|
11
|
+
original: undefined
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
__mockyFunction: true,
|
|
16
|
+
async: function() {
|
|
17
|
+
options.async = true;
|
|
18
|
+
return this;
|
|
19
|
+
},
|
|
20
|
+
args: function(...args) {
|
|
21
|
+
options.args = args;
|
|
22
|
+
return this;
|
|
23
|
+
},
|
|
24
|
+
argSelect: function(...indexes) {
|
|
25
|
+
options.select = indexes;
|
|
26
|
+
return this;
|
|
27
|
+
},
|
|
28
|
+
original: function(fn) {
|
|
29
|
+
options.original = fn;
|
|
30
|
+
return this;
|
|
31
|
+
},
|
|
32
|
+
build: function(parent, key) {
|
|
33
|
+
const state = {};
|
|
34
|
+
const mock = createFunction(state, options);
|
|
35
|
+
|
|
36
|
+
if (parent)
|
|
37
|
+
Object.defineProperty(parent, key, { value: mock });
|
|
38
|
+
|
|
39
|
+
return wireFunction(parent ? parent[key] : mock, state);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function wireFunction(mock, state) {
|
|
45
|
+
mock.ret = (value, call = 0) => {
|
|
46
|
+
state.rets[call] = value;
|
|
47
|
+
};
|
|
48
|
+
mock.onRet = (handler, call = 0) => {
|
|
49
|
+
state.retHandlers[call] = handler;
|
|
50
|
+
};
|
|
51
|
+
mock.calls = (call) => {
|
|
52
|
+
return call !== undefined ? state.calls[call] : state.calls;
|
|
53
|
+
};
|
|
54
|
+
mock.data = (key) => {
|
|
55
|
+
return key !== undefined ? state.data[key] : state.data;
|
|
56
|
+
};
|
|
57
|
+
mock.reset = () => {
|
|
58
|
+
state.rets = [];
|
|
59
|
+
state.retHandlers = [];
|
|
60
|
+
state.calls = [];
|
|
61
|
+
state.data = {};
|
|
62
|
+
};
|
|
63
|
+
mock.reset();
|
|
64
|
+
|
|
65
|
+
return mock;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createFunction(state, options) {
|
|
69
|
+
if (options.async) {
|
|
70
|
+
return async function() {
|
|
71
|
+
return getFunctionBody(this, state, options, Array.from(arguments));
|
|
72
|
+
};
|
|
73
|
+
} else {
|
|
74
|
+
return function() {
|
|
75
|
+
return getFunctionBody(this, state, options, Array.from(arguments));
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getFunctionBody(parent, state, options, rawArgs) {
|
|
81
|
+
const args = deepClone(getFunctionArgs(rawArgs, options));
|
|
82
|
+
state.calls.push(args);
|
|
83
|
+
const call = state.calls.length;
|
|
84
|
+
|
|
85
|
+
let ret;
|
|
86
|
+
|
|
87
|
+
if (state.rets[call]) {
|
|
88
|
+
ret = state.rets[call];
|
|
89
|
+
} else if (state.retHandlers[call]) {
|
|
90
|
+
ret = state.retHandlers[call](args);
|
|
91
|
+
} else if (state.rets[0]) {
|
|
92
|
+
ret = state.rets[0];
|
|
93
|
+
} else if (state.retHandlers[0]) {
|
|
94
|
+
ret = state.retHandlers[0](args);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (ret instanceof Error)
|
|
98
|
+
throw ret;
|
|
99
|
+
|
|
100
|
+
if (options.body) {
|
|
101
|
+
return options.body({
|
|
102
|
+
self: parent,
|
|
103
|
+
state: state,
|
|
104
|
+
call: call,
|
|
105
|
+
args: args,
|
|
106
|
+
rawArgs: rawArgs,
|
|
107
|
+
original: options.original,
|
|
108
|
+
ret: ret
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
return ret;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getFunctionArgs(callArgs, options) {
|
|
116
|
+
let args = null;
|
|
117
|
+
|
|
118
|
+
if (options.select.length == 1) {
|
|
119
|
+
args = callArgs[options.select[0]];
|
|
120
|
+
} else if (options.args.length) {
|
|
121
|
+
args = {};
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < options.args.length; i++) {
|
|
124
|
+
if (options.select.length && !options.select.includes(i))
|
|
125
|
+
continue;
|
|
126
|
+
|
|
127
|
+
let argName = '';
|
|
128
|
+
let defaultValue = undefined;
|
|
129
|
+
|
|
130
|
+
if (typeof options.args[i] == 'string') {
|
|
131
|
+
argName = options.args[i];
|
|
132
|
+
} else {
|
|
133
|
+
[argName, defaultValue] = Object.entries(options.args[i])[0];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
args[argName] = callArgs[i] !== undefined ? callArgs[i] : defaultValue;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
// NOTE: Previously returned null. Now returns array (may break backwards compatibility).
|
|
140
|
+
args = Array.from(callArgs);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return args;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function objectBuilder(props) {
|
|
147
|
+
const options = { props };
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
__mockyObject: true,
|
|
151
|
+
build: function(parent, key) {
|
|
152
|
+
const mock = createObjectWithProps(options.props);
|
|
153
|
+
|
|
154
|
+
if (parent)
|
|
155
|
+
Object.defineProperty(parent, key, { value: mock });
|
|
156
|
+
|
|
157
|
+
return wireObject(parent ? parent[key] : mock, props);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function wireObject(mock, initialProps) {
|
|
163
|
+
const initialMocks = new Set();
|
|
164
|
+
const initialValues = new Map();
|
|
165
|
+
|
|
166
|
+
for (const key of Reflect.ownKeys(initialProps)) {
|
|
167
|
+
const prop = initialProps[key];
|
|
168
|
+
if (typeof prop?.build === 'function') {
|
|
169
|
+
initialMocks.add(key);
|
|
170
|
+
} else {
|
|
171
|
+
initialValues.set(key, prop);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
mock.reset = () => {
|
|
176
|
+
for (const key of Reflect.ownKeys(mock)) {
|
|
177
|
+
if (key === 'reset') continue;
|
|
178
|
+
|
|
179
|
+
if (initialMocks.has(key)) {
|
|
180
|
+
if (typeof mock[key]?.reset === 'function')
|
|
181
|
+
mock[key].reset();
|
|
182
|
+
} else if (initialValues.has(key)) {
|
|
183
|
+
mock[key] = initialValues.get(key);
|
|
184
|
+
} else {
|
|
185
|
+
delete mock[key];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return mock;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function createObjectWithProps(props) {
|
|
194
|
+
const object = {};
|
|
195
|
+
|
|
196
|
+
for (const key of Reflect.ownKeys(props)) {
|
|
197
|
+
const prop = props[key];
|
|
198
|
+
|
|
199
|
+
if (typeof prop?.build == 'function') {
|
|
200
|
+
prop.build(object, key);
|
|
201
|
+
} else {
|
|
202
|
+
object[key] = prop;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return object;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function classBuilder(members) {
|
|
210
|
+
const options = { members };
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
__mockyClass: true,
|
|
214
|
+
build: function(parent, key) {
|
|
215
|
+
const state = {};
|
|
216
|
+
const Mock = createClass(state, options);
|
|
217
|
+
|
|
218
|
+
if (parent)
|
|
219
|
+
Object.defineProperty(parent, key, { value: Mock });
|
|
220
|
+
|
|
221
|
+
return wireClass(parent ? parent[key] : Mock, state, options);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function createClass(state, options) {
|
|
227
|
+
const Mock = function() {
|
|
228
|
+
const index = state.numInstances;
|
|
229
|
+
|
|
230
|
+
if (!state.descriptions[index])
|
|
231
|
+
state.descriptions[index] = createObjectWithProps(options.members);
|
|
232
|
+
|
|
233
|
+
Object.defineProperty(this, '__mockyInst', { value: index });
|
|
234
|
+
|
|
235
|
+
if (typeof state.descriptions[index].constructor === 'function')
|
|
236
|
+
state.descriptions[index].constructor.call(this, ...arguments);
|
|
237
|
+
|
|
238
|
+
state.numInstances++;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
for (const key of Reflect.ownKeys(options.members)) {
|
|
242
|
+
if (key === 'constructor')
|
|
243
|
+
continue;
|
|
244
|
+
|
|
245
|
+
Object.defineProperty(Mock.prototype, key, {
|
|
246
|
+
get: function() {
|
|
247
|
+
return state.descriptions[this.__mockyInst][key];
|
|
248
|
+
},
|
|
249
|
+
set: function(value) {
|
|
250
|
+
state.descriptions[this.__mockyInst][key] = value;
|
|
251
|
+
},
|
|
252
|
+
enumerable: true,
|
|
253
|
+
configurable: true
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return Mock;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function wireClass(Mock, state, options) {
|
|
261
|
+
Mock.inst = (index = 0) => {
|
|
262
|
+
if (!state.descriptions[index])
|
|
263
|
+
state.descriptions[index] = createObjectWithProps(options.members);
|
|
264
|
+
|
|
265
|
+
return state.descriptions[index];
|
|
266
|
+
};
|
|
267
|
+
Mock.numInsts = () => {
|
|
268
|
+
return state.numInstances;
|
|
269
|
+
};
|
|
270
|
+
Mock.reset = () => {
|
|
271
|
+
state.descriptions = [];
|
|
272
|
+
state.numInstances = 0;
|
|
273
|
+
state.data = {};
|
|
274
|
+
};
|
|
275
|
+
Mock.reset();
|
|
276
|
+
|
|
277
|
+
return Mock;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function propertyBuilder() {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function deepClone(target) {
|
|
285
|
+
if (typeof target === 'object') {
|
|
286
|
+
if (Array.isArray(target)) {
|
|
287
|
+
return target.map(deepClone);
|
|
288
|
+
} else if (target?.constructor === Object) {
|
|
289
|
+
const clone = {};
|
|
290
|
+
|
|
291
|
+
for (const key in target) {
|
|
292
|
+
clone[key] = deepClone(target[key]);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return clone;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return target;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function createSpy(object, key, replacement) {
|
|
303
|
+
if (!replacement) {
|
|
304
|
+
replacement = functionBuilder((ctx) => {
|
|
305
|
+
if (ctx.ret !== undefined)
|
|
306
|
+
return ctx.ret;
|
|
307
|
+
|
|
308
|
+
return ctx.original.apply(ctx.self, ctx.rawArgs);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const original = object[key];
|
|
313
|
+
const spyFn = replacement.original(original).build();
|
|
314
|
+
spyFn.restore = () => {
|
|
315
|
+
object[key] = original;
|
|
316
|
+
};
|
|
317
|
+
object[key] = spyFn;
|
|
318
|
+
|
|
319
|
+
return spyFn;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
module.exports = {
|
|
323
|
+
spy: createSpy,
|
|
324
|
+
create: mockBuilder,
|
|
325
|
+
function: functionBuilder,
|
|
326
|
+
object: objectBuilder,
|
|
327
|
+
class: classBuilder,
|
|
328
|
+
property: propertyBuilder,
|
|
329
|
+
};
|