@slimlib/smart-mock 0.1.6 → 1.0.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/package.json CHANGED
@@ -1,27 +1,24 @@
1
1
  {
2
2
  "type": "module",
3
- "version": "0.1.6",
3
+ "version": "1.0.0",
4
4
  "name": "@slimlib/smart-mock",
5
5
  "description": "One more proxy based mock",
6
6
  "license": "MIT",
7
7
  "author": "Konstantin Shutkin",
8
- "main": "./dist/index.cjs",
9
- "module": "./dist/index.mjs",
10
8
  "exports": {
11
9
  ".": {
12
- "types": "./dist/index.d.ts",
13
- "import": "./dist/index.mjs",
14
- "require": "./dist/index.cjs",
15
- "default": "./dist/index.mjs"
10
+ "types": "./types/index.d.ts",
11
+ "default": "./src/index.js"
16
12
  },
17
13
  "./package.json": "./package.json"
18
14
  },
19
- "types": "./dist/index.d.ts",
15
+ "types": "./types/index.d.ts",
20
16
  "files": [
21
- "dist"
17
+ "src",
18
+ "types"
22
19
  ],
23
20
  "engines": {
24
- "node": ">=15"
21
+ "node": ">=20"
25
22
  },
26
23
  "repository": {
27
24
  "type": "git",
package/src/index.js ADDED
@@ -0,0 +1,532 @@
1
+ /**
2
+ * @enum {number}
3
+ */
4
+ export const MockDataSource = /** @type {const} */ ({
5
+ root: 0,
6
+ get: 1,
7
+ call: 2,
8
+ set: 3,
9
+ defineProperty: 4,
10
+ deleteProperty: 5,
11
+ setPrototypeOf: 6,
12
+ preventExtensions: 7,
13
+ construct: 8,
14
+ });
15
+
16
+ /**
17
+ * @typedef {object} MockData
18
+ * @property {number} useCount
19
+ * @property {string | symbol} name
20
+ * @property {MockData} [parent]
21
+ * @property {number} source
22
+ * @property {unknown | unknown[]} [options]
23
+ * @property {{[key: string | symbol]: MockData}} [mocks]
24
+ * @property {MockData[]} [sideEffects]
25
+ * @property {string} [instanceName]
26
+ * @property {boolean} generated
27
+ * @property {Function} [target]
28
+ */
29
+
30
+ /**
31
+ * @typedef {(v: unknown) => unknown} ReplacerFunction
32
+ */
33
+
34
+ /**
35
+ * @template T
36
+ * @typedef {new (...args: unknown[]) => T} Constructor
37
+ */
38
+
39
+ const mock = Symbol();
40
+ const unwrap = Symbol();
41
+
42
+ /**
43
+ * @template T
44
+ * @typedef {object} Unwrappable
45
+ * @property {T} [unwrap]
46
+ * @property {MockData} [mock]
47
+ */
48
+
49
+ /**
50
+ * @template T
51
+ * @param {T} value
52
+ * @returns {T}
53
+ */
54
+ const unwrapValue = value => (value != null && /** @type {any} */ (value)[unwrap]) || value;
55
+
56
+ /**
57
+ * @template T
58
+ * @param {T} value
59
+ * @returns {MockData | undefined}
60
+ */
61
+ const getMockData = value => (value != null && /** @type {any} */ (value)[mock]) || undefined;
62
+
63
+ /**
64
+ * @returns {{createMock: <T extends object>(object: T, name: string) => T, generateGlobals: () => string, generate: (object: unknown) => string | null | undefined | RegExp | boolean}}
65
+ */
66
+ export default function createRecordingMockFactory() {
67
+ /** @type {MockData[]} */
68
+ const mockDatas = [];
69
+
70
+ let counter = 0;
71
+
72
+ return {
73
+ createMock,
74
+ generateGlobals,
75
+ generate,
76
+ };
77
+
78
+ /**
79
+ * @template {object} T
80
+ * @param {T} object
81
+ * @param {string} name
82
+ * @returns {T}
83
+ */
84
+ function createMock(object, name) {
85
+ return createInternalMock(object, {
86
+ name,
87
+ source: MockDataSource.root,
88
+ useCount: 0,
89
+ generated: false,
90
+ });
91
+ }
92
+
93
+ /**
94
+ * @returns {string}
95
+ */
96
+ function generateGlobals() {
97
+ /** @type {string[]} */
98
+ const strings = [];
99
+ for (const mockData of mockDatas) {
100
+ if (mockData.generated) continue;
101
+ if (!mockData.instanceName && mockData.source !== MockDataSource.root) {
102
+ mockData.instanceName = getNextInstanceName();
103
+ }
104
+ const identifier = mockData?.instanceName ?? /** @type {string} */ (mockData?.name);
105
+ if (mockData.source !== MockDataSource.root) {
106
+ strings.push(`const ${identifier} = ${getAccessor(mockData, /** @type {MockData} */ (mockData.parent))}`);
107
+ }
108
+ for (const effect of mockData.sideEffects || []) {
109
+ switch (/** @type {number} */ (effect.source)) {
110
+ case MockDataSource.set:
111
+ strings.push(
112
+ identifier +
113
+ '.' +
114
+ /** @type {string} */ (effect.name) +
115
+ ' = ' +
116
+ stringify(effect.options, /** @type {ReplacerFunction} */ (replacer))
117
+ );
118
+ break;
119
+ // case MockDataSource.defineProperty:
120
+ // strings.push('Object.defineProperty(' + identifier + ', "' + (effect.name as string) + '", ' + stringify(effect.options, replacer as ReplacerFunction) + ')');
121
+ // break;
122
+ case MockDataSource.deleteProperty:
123
+ strings.push(`delete ${identifier}["${String(effect.name)}"]`);
124
+ break;
125
+ case MockDataSource.setPrototypeOf:
126
+ strings.push(
127
+ 'Object.setPrototypeOf(' +
128
+ identifier +
129
+ ', ' +
130
+ stringify(effect.options, /** @type {ReplacerFunction} */ (replacer)) +
131
+ ')'
132
+ );
133
+ break;
134
+ case MockDataSource.preventExtensions:
135
+ strings.push(`Object.preventExtensions(${identifier})`);
136
+ break;
137
+ case MockDataSource.call:
138
+ strings.push(
139
+ identifier +
140
+ getParameters(/** @type {unknown[]} */ (effect.options), /** @type {ReplacerFunction} */ (replacer))
141
+ );
142
+ break;
143
+ }
144
+ }
145
+ }
146
+ return strings.join('\n');
147
+
148
+ /**
149
+ * @param {MockData} mockData
150
+ * @param {MockData} parent
151
+ * @returns {string}
152
+ */
153
+ function getAccessor(mockData, parent) {
154
+ const parentName = parent?.instanceName ?? /** @type {string} */ (parent?.name);
155
+ switch (/** @type {number} */ (mockData.source)) {
156
+ case MockDataSource.call:
157
+ return (
158
+ parentName + getParameters(/** @type {unknown[]} */ (mockData.options), /** @type {ReplacerFunction} */ (replacer))
159
+ );
160
+ case MockDataSource.get:
161
+ return `${parentName}.${String(mockData.name)}`;
162
+ case MockDataSource.construct: {
163
+ const newTarget = stringify(mockData.target, /** @type {ReplacerFunction} */ (replacer));
164
+ return parentName !== newTarget
165
+ ? 'Reflect.construct(' +
166
+ parentName +
167
+ ',' +
168
+ stringify(mockData.options, /** @type {ReplacerFunction} */ (replacer)) +
169
+ ',' +
170
+ newTarget +
171
+ ')'
172
+ : 'new ' +
173
+ parentName +
174
+ getParameters(/** @type {unknown[]} */ (mockData.options), /** @type {ReplacerFunction} */ (replacer));
175
+ }
176
+ }
177
+ return '';
178
+ }
179
+ }
180
+
181
+ /**
182
+ * @param {unknown} object
183
+ * @returns {string | null | undefined | RegExp | boolean}
184
+ */
185
+ function generate(object) {
186
+ stringify(object, /** @type {ReplacerFunction} */ (bumpReplacer));
187
+ return stringify(object, /** @type {ReplacerFunction} */ (replacer));
188
+ }
189
+
190
+ /**
191
+ * @param {object & { [key: symbol]: MockData }} value
192
+ * @returns {unknown}
193
+ */
194
+ function bumpReplacer(value) {
195
+ const mockData = getMockData(value);
196
+ if (mockData) {
197
+ ++mockData.useCount;
198
+ return getCode(mockData, /** @type {ReplacerFunction} */ (bumpReplacer), true);
199
+ }
200
+ return value;
201
+ }
202
+
203
+ /**
204
+ * @param {object & { [key: symbol]: MockData }} value
205
+ * @returns {unknown}
206
+ */
207
+ function replacer(value) {
208
+ const mockData = getMockData(value);
209
+ if (mockData) {
210
+ return getCode(mockData, /** @type {ReplacerFunction} */ (replacer), true);
211
+ }
212
+ return value;
213
+ }
214
+
215
+ /**
216
+ * @param {MockData} value
217
+ * @param {ReplacerFunction} replacer
218
+ * @param {boolean} bumpCount
219
+ * @returns {string}
220
+ */
221
+ function getCode(value, replacer, bumpCount) {
222
+ if (bumpCount && value.useCount > 1) {
223
+ if (value.source === MockDataSource.root) {
224
+ return /** @type {string} */ (value.name);
225
+ }
226
+ if (!value.instanceName) {
227
+ value.instanceName = getNextInstanceName();
228
+ }
229
+ return value.instanceName;
230
+ }
231
+ value.generated = true;
232
+ switch (/** @type {number} */ (value.source)) {
233
+ case MockDataSource.call:
234
+ return getPrevCode(value) + getParameters(/** @type {unknown[]} */ (value.options), replacer);
235
+ case MockDataSource.get:
236
+ return `${getPrevCode(value)}.${String(value.name)}`;
237
+ case MockDataSource.root:
238
+ return /** @type {string} */ (value.name);
239
+ case MockDataSource.construct: {
240
+ const prevCode = getPrevCode(value);
241
+ const newTarget = stringify(value.target, /** @type {ReplacerFunction} */ (replacer));
242
+ return prevCode !== newTarget
243
+ ? 'Reflect.construct(' +
244
+ prevCode +
245
+ ',' +
246
+ stringify(value.options, /** @type {ReplacerFunction} */ (replacer)) +
247
+ ',' +
248
+ newTarget +
249
+ ')'
250
+ : 'new ' +
251
+ prevCode +
252
+ getParameters(/** @type {unknown[]} */ (value.options), /** @type {ReplacerFunction} */ (replacer));
253
+ }
254
+ }
255
+ return '';
256
+
257
+ /**
258
+ * @param {MockData} mockData
259
+ * @returns {string}
260
+ */
261
+ function getPrevCode(mockData) {
262
+ return mockData.parent ? getCode(mockData.parent, replacer, bumpCount) : '';
263
+ }
264
+ }
265
+
266
+ /**
267
+ * @template {object} T
268
+ * @param {T} target
269
+ * @param {MockData} mockData
270
+ * @returns {T}
271
+ */
272
+ function createInternalMock(target, mockData) {
273
+ mockDatas.push(mockData);
274
+ /** @type {any} */ (target)[mock] = mockData;
275
+ return /** @type {T} */ (
276
+ new Proxy(target, {
277
+ /**
278
+ * @param {T} target
279
+ * @param {string | symbol} p
280
+ * @param {unknown} value
281
+ * @param {unknown} receiver
282
+ * @returns {boolean}
283
+ */
284
+ set(target, p, value, receiver) {
285
+ const realValue = unwrapValue(value);
286
+ if (!mockData.sideEffects) {
287
+ mockData.sideEffects = [];
288
+ }
289
+ mockData.sideEffects.push({
290
+ useCount: 0,
291
+ name: p,
292
+ options: realValue,
293
+ parent: mockData,
294
+ source: MockDataSource.set,
295
+ generated: false,
296
+ });
297
+ ++mockData.useCount;
298
+ Reflect.set(target, p, realValue, receiver);
299
+ return true;
300
+ },
301
+ /**
302
+ * @param {object} target
303
+ * @param {string | symbol} p
304
+ * @returns {unknown}
305
+ */
306
+ get(target, p) {
307
+ if (p === unwrap) return target;
308
+ if (p === mock) return mockData;
309
+ const value = Reflect.get(target, p);
310
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
311
+ return value;
312
+ }
313
+ if (!mockData.mocks) {
314
+ mockData.mocks = Object.create(null);
315
+ }
316
+ if (!(/** @type {{[key: string | symbol]: MockData}} */ (mockData.mocks)[p])) {
317
+ /** @type {{[key: string | symbol]: MockData}} */ (mockData.mocks)[p] = createInternalMock(value, {
318
+ useCount: 0,
319
+ name: p,
320
+ parent: mockData,
321
+ source: MockDataSource.get,
322
+ generated: false,
323
+ });
324
+ }
325
+ const result = /** @type {{[key: string | symbol]: MockData}} */ (mockData.mocks)[p];
326
+ ++mockData.useCount;
327
+ return result;
328
+ },
329
+ /**
330
+ * @param {Constructor<T>} target
331
+ * @param {unknown[]} argArray
332
+ * @param {Function} newTarget
333
+ * @returns {T}
334
+ */
335
+ construct(target, argArray, newTarget) {
336
+ const realTarget = unwrapValue(newTarget);
337
+ const realArguments = unwrapValue(argArray);
338
+ ++mockData.useCount;
339
+ const result = Reflect.construct(
340
+ target,
341
+ /** @type {unknown[]} */ (realArguments),
342
+ /** @type {Function} */ (realTarget)
343
+ );
344
+ return createInternalMock(result, {
345
+ useCount: 0,
346
+ name: '',
347
+ options: realArguments,
348
+ target: /** @type {Function} */ (realTarget),
349
+ parent: mockData,
350
+ source: MockDataSource.construct,
351
+ generated: false,
352
+ });
353
+ },
354
+ /**
355
+ * @param {T} target
356
+ * @param {string | symbol} property
357
+ * @param {PropertyDescriptor} attributes
358
+ * @returns {boolean}
359
+ */
360
+ defineProperty(target, property, attributes) {
361
+ const realValue = unwrapValue(attributes);
362
+ // if (!mockData.sideEffects) {
363
+ // mockData.sideEffects = [];
364
+ // }
365
+ // mockData.sideEffects.push({
366
+ // useCount: 0,
367
+ // name: property,
368
+ // options: realValue,
369
+ // parent: mockData,
370
+ // source: MockDataSource.defineProperty,
371
+ // generated: false
372
+ // });
373
+ // ++mockData.useCount;
374
+ return Reflect.defineProperty(target, property, /** @type {PropertyDescriptor} */ (realValue));
375
+ },
376
+ /**
377
+ * @param {object} target
378
+ * @param {string | symbol} p
379
+ * @returns {boolean}
380
+ */
381
+ deleteProperty(target, p) {
382
+ if (!mockData.sideEffects) {
383
+ mockData.sideEffects = [];
384
+ }
385
+ mockData.sideEffects.push({
386
+ useCount: 0,
387
+ name: p,
388
+ options: undefined,
389
+ parent: mockData,
390
+ source: MockDataSource.deleteProperty,
391
+ generated: false,
392
+ });
393
+ ++mockData.useCount;
394
+ const result = Reflect.deleteProperty(target, p);
395
+ return result;
396
+ },
397
+ /**
398
+ * @param {T} target
399
+ * @param {object | null} v
400
+ * @returns {boolean}
401
+ */
402
+ setPrototypeOf(target, v) {
403
+ const realValue = unwrapValue(v);
404
+ if (!mockData.sideEffects) {
405
+ mockData.sideEffects = [];
406
+ }
407
+ mockData.sideEffects.push({
408
+ useCount: 0,
409
+ name: '',
410
+ options: realValue,
411
+ parent: mockData,
412
+ source: MockDataSource.setPrototypeOf,
413
+ generated: false,
414
+ });
415
+ ++mockData.useCount;
416
+ return Reflect.setPrototypeOf(target, realValue);
417
+ },
418
+ /**
419
+ * @param {T} target
420
+ * @returns {boolean}
421
+ */
422
+ preventExtensions(target) {
423
+ if (!mockData.sideEffects) {
424
+ mockData.sideEffects = [];
425
+ }
426
+ mockData.sideEffects.push({
427
+ useCount: 0,
428
+ name: '',
429
+ options: undefined,
430
+ parent: mockData,
431
+ source: MockDataSource.preventExtensions,
432
+ generated: false,
433
+ });
434
+ ++mockData.useCount;
435
+ return Reflect.preventExtensions(target);
436
+ },
437
+ /**
438
+ * @param {Function} target
439
+ * @param {unknown} thisArg
440
+ * @param {unknown[]} argumentsList
441
+ * @returns {unknown}
442
+ */
443
+ apply(target, thisArg, argumentsList) {
444
+ const realThis = unwrapValue(thisArg);
445
+ const realArguments = unwrapValue(argumentsList);
446
+ ++mockData.useCount;
447
+ const result = Reflect.apply(target, realThis, /** @type {unknown[]} */ (realArguments));
448
+ if (result === null || (typeof result !== 'object' && typeof result !== 'function')) {
449
+ if (!mockData.sideEffects) {
450
+ mockData.sideEffects = [];
451
+ }
452
+ mockData.sideEffects.push({
453
+ useCount: 0,
454
+ name: '',
455
+ parent: mockData,
456
+ source: MockDataSource.call,
457
+ options: realArguments,
458
+ generated: false,
459
+ });
460
+ ++mockData.useCount;
461
+ return result;
462
+ }
463
+ return createInternalMock(result, {
464
+ useCount: 0,
465
+ name: '',
466
+ parent: mockData,
467
+ source: MockDataSource.call,
468
+ options: realArguments,
469
+ generated: false,
470
+ });
471
+ },
472
+ })
473
+ );
474
+ }
475
+
476
+ /**
477
+ * @returns {string}
478
+ */
479
+ function getNextInstanceName() {
480
+ return `tmp_${counter++}`;
481
+ }
482
+ }
483
+
484
+ /**
485
+ * @param {unknown[]} options
486
+ * @param {ReplacerFunction} replacer
487
+ * @returns {string}
488
+ */
489
+ function getParameters(options, replacer) {
490
+ return `(${options.length ? options.map(value => stringify(value, replacer)).join(',') : ''})`;
491
+ }
492
+
493
+ /**
494
+ * stringify like functionality, recursively walks through objects and converts them to strings but leaves some basic values intact
495
+ * @param {unknown} value
496
+ * @param {ReplacerFunction} replacer
497
+ * @returns {string | null | undefined | RegExp | boolean}
498
+ */
499
+ function stringify(value, replacer) {
500
+ const original = value;
501
+ value = replacer(value);
502
+ if (original !== value && typeof value === 'string') {
503
+ return value;
504
+ }
505
+ if (value === null) {
506
+ return null;
507
+ }
508
+ if (value === undefined) {
509
+ return undefined;
510
+ }
511
+ if (typeof value === 'number') {
512
+ return `${value}`;
513
+ }
514
+ if (Array.isArray(value)) {
515
+ return `[${value.map(v => stringify(v, replacer)).join(',')}]`;
516
+ }
517
+ if (typeof value === 'boolean') {
518
+ return value;
519
+ }
520
+ if (typeof value === 'function') {
521
+ return value.toString();
522
+ }
523
+ if (value instanceof RegExp) {
524
+ return value;
525
+ }
526
+ if (typeof value === 'object') {
527
+ return `{${Object.entries(value)
528
+ .map(([k, v]) => `${k}:${stringify(v, replacer)}`)
529
+ .join(',')}}`;
530
+ }
531
+ return `"${String(value)}"`;
532
+ }
@@ -0,0 +1,44 @@
1
+ declare module '@slimlib/smart-mock' {
2
+ export default function createRecordingMockFactory(): {
3
+ createMock: <T extends object>(object: T, name: string) => T;
4
+ generateGlobals: () => string;
5
+ generate: (object: unknown) => string | null | undefined | RegExp | boolean;
6
+ };
7
+ export type MockDataSource = number;
8
+ export namespace MockDataSource {
9
+ let root: 0;
10
+ let get: 1;
11
+ let call: 2;
12
+ let set: 3;
13
+ let defineProperty: 4;
14
+ let deleteProperty: 5;
15
+ let setPrototypeOf: 6;
16
+ let preventExtensions: 7;
17
+ let construct: 8;
18
+ }
19
+ export type MockData = {
20
+ useCount: number;
21
+ name: string | symbol;
22
+ parent?: MockData | undefined;
23
+ source: number;
24
+ options?: unknown | unknown[];
25
+ mocks?: {
26
+ [key: string]: MockData;
27
+ [key: symbol]: MockData;
28
+ } | undefined;
29
+ sideEffects?: MockData[] | undefined;
30
+ instanceName?: string | undefined;
31
+ generated: boolean;
32
+ target?: Function | undefined;
33
+ };
34
+ export type ReplacerFunction = (v: unknown) => unknown;
35
+ export type Constructor<T> = new (...args: unknown[]) => T;
36
+ export type Unwrappable<T> = {
37
+ unwrap?: T | undefined;
38
+ mock?: MockData | undefined;
39
+ };
40
+
41
+ export {};
42
+ }
43
+
44
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": 3,
3
+ "file": "index.d.ts",
4
+ "names": [
5
+ "createRecordingMockFactory",
6
+ "ReplacerFunction"
7
+ ],
8
+ "sources": [
9
+ "../src/index.js"
10
+ ],
11
+ "sourcesContent": [
12
+ null
13
+ ],
14
+ "mappings": ";yBAiEwBA,0BAA0BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAnCZC,gBAAgBA"
15
+ }
package/dist/index.cjs DELETED
@@ -1,323 +0,0 @@
1
- 'use strict';
2
-
3
- const mock = Symbol();
4
- const unwrap = Symbol();
5
- const unwrapValue = (value) => (value != null && value[unwrap]) || value;
6
- const getMockData = (value) => (value != null && value[mock]) || undefined;
7
- function createRecordingMockFactory() {
8
- const mockDatas = [];
9
- let counter = 0;
10
- return {
11
- createMock,
12
- generateGlobals,
13
- generate
14
- };
15
- function createMock(object, name) {
16
- return createInternalMock(object, {
17
- name,
18
- source: 0 /* MockDataSource.root */,
19
- useCount: 0,
20
- generated: false
21
- });
22
- }
23
- function generateGlobals() {
24
- const strings = [];
25
- for (const mockData of mockDatas) {
26
- if (mockData.generated)
27
- continue;
28
- if (!mockData.instanceName && mockData.source !== 0 /* MockDataSource.root */) {
29
- mockData.instanceName = getNextInstanceName();
30
- }
31
- const identifier = (mockData?.instanceName ?? mockData?.name);
32
- if (mockData.source !== 0 /* MockDataSource.root */) {
33
- strings.push('const ' + identifier + ' = ' + getAccessor(mockData, mockData.parent));
34
- }
35
- for (const effect of (mockData.sideEffects || [])) {
36
- switch (effect.source) {
37
- case 3 /* MockDataSource.set */:
38
- strings.push(identifier + '.' + effect.name + ' = ' + stringify(effect.options, replacer));
39
- break;
40
- // case MockDataSource.defineProperty:
41
- // strings.push('Object.defineProperty(' + identifier + ', "' + (effect.name as string) + '", ' + stringify(effect.options, replacer as ReplacerFunction) + ')');
42
- // break;
43
- case 5 /* MockDataSource.deleteProperty */:
44
- strings.push('delete ' + identifier + '["' + effect.name + '"]');
45
- break;
46
- case 6 /* MockDataSource.setPrototypeOf */:
47
- strings.push('Object.setPrototypeOf(' + identifier + ', ' + stringify(effect.options, replacer) + ')');
48
- break;
49
- case 7 /* MockDataSource.preventExtensions */:
50
- strings.push('Object.preventExtensions(' + identifier + ')');
51
- break;
52
- case 2 /* MockDataSource.call */:
53
- strings.push(identifier + getParameters(effect.options, replacer));
54
- break;
55
- }
56
- }
57
- }
58
- return strings.join('\n');
59
- function getAccessor(mockData, parent) {
60
- const parentName = (parent?.instanceName ?? parent?.name);
61
- switch (mockData.source) {
62
- case 2 /* MockDataSource.call */:
63
- return parentName + getParameters(mockData.options, replacer);
64
- case 1 /* MockDataSource.get */:
65
- return parentName + '.' + mockData.name;
66
- case 8 /* MockDataSource.construct */:
67
- {
68
- const newTarget = stringify(mockData.target, replacer);
69
- return parentName !== newTarget
70
- ? 'Reflect.construct(' + parentName + ',' + stringify(mockData.options, replacer) + ',' + newTarget + ')'
71
- : 'new ' + parentName + getParameters(mockData.options, replacer);
72
- }
73
- }
74
- }
75
- }
76
- function generate(object) {
77
- stringify(object, bumpReplacer);
78
- return stringify(object, replacer);
79
- }
80
- function bumpReplacer(value) {
81
- const mockData = getMockData(value);
82
- if (mockData) {
83
- ++mockData.useCount;
84
- return getCode(mockData, bumpReplacer, true);
85
- }
86
- return value;
87
- }
88
- function replacer(value) {
89
- const mockData = getMockData(value);
90
- if (mockData) {
91
- return getCode(mockData, replacer, true);
92
- }
93
- return value;
94
- }
95
- function getCode(value, replacer, bumpCount) {
96
- if (bumpCount && value.useCount > 1) {
97
- if (value.source === 0 /* MockDataSource.root */) {
98
- return value.name;
99
- }
100
- if (!value.instanceName) {
101
- value.instanceName = getNextInstanceName();
102
- }
103
- return value.instanceName;
104
- }
105
- value.generated = true;
106
- switch (value.source) {
107
- case 2 /* MockDataSource.call */:
108
- return getPrevCode(value) + getParameters(value.options, replacer);
109
- case 1 /* MockDataSource.get */:
110
- return getPrevCode(value) + '.' + value.name;
111
- case 0 /* MockDataSource.root */:
112
- return value.name;
113
- case 8 /* MockDataSource.construct */:
114
- {
115
- const prevCode = getPrevCode(value);
116
- const newTarget = stringify(value.target, replacer);
117
- return prevCode !== newTarget
118
- ? 'Reflect.construct(' + prevCode + ',' + stringify(value.options, replacer) + ',' + newTarget + ')'
119
- : 'new ' + prevCode + getParameters(value.options, replacer);
120
- }
121
- }
122
- function getPrevCode(mockData) {
123
- return mockData.parent ? getCode(mockData.parent, replacer, bumpCount) : '';
124
- }
125
- }
126
- function createInternalMock(target, mockData) {
127
- mockDatas.push(mockData);
128
- target[mock] = mockData;
129
- return new Proxy(target, {
130
- set(target, p, value, receiver) {
131
- const realValue = unwrapValue(value);
132
- if (!mockData.sideEffects) {
133
- mockData.sideEffects = [];
134
- }
135
- mockData.sideEffects.push({
136
- useCount: 0,
137
- name: p,
138
- options: realValue,
139
- parent: mockData,
140
- source: 3 /* MockDataSource.set */,
141
- generated: false
142
- });
143
- ++mockData.useCount;
144
- Reflect.set(target, p, realValue, receiver);
145
- return true;
146
- },
147
- get(target, p) {
148
- if (p === unwrap)
149
- return target;
150
- if (p === mock)
151
- return mockData;
152
- const value = Reflect.get(target, p);
153
- if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
154
- return value;
155
- }
156
- if (!mockData.mocks) {
157
- mockData.mocks = Object.create(null);
158
- }
159
- if (!mockData.mocks[p]) {
160
- mockData.mocks[p] = createInternalMock(value, {
161
- useCount: 0,
162
- name: p,
163
- parent: mockData,
164
- source: 1 /* MockDataSource.get */,
165
- generated: false
166
- });
167
- }
168
- const result = mockData.mocks[p];
169
- ++mockData.useCount;
170
- return result;
171
- },
172
- // eslint-disable-next-line @typescript-eslint/ban-types
173
- construct(target, argArray, newTarget) {
174
- const realTarget = unwrapValue(newTarget);
175
- const realArguments = unwrapValue(argArray);
176
- ++mockData.useCount;
177
- const result = Reflect.construct(target, realArguments, realTarget);
178
- return createInternalMock(result, {
179
- useCount: 0,
180
- name: '',
181
- options: realArguments,
182
- target: realTarget,
183
- parent: mockData,
184
- source: 8 /* MockDataSource.construct */,
185
- generated: false
186
- });
187
- },
188
- defineProperty(target, property, attributes) {
189
- const realValue = unwrapValue(attributes);
190
- // if (!mockData.sideEffects) {
191
- // mockData.sideEffects = [];
192
- // }
193
- // mockData.sideEffects.push({
194
- // useCount: 0,
195
- // name: property,
196
- // options: realValue,
197
- // parent: mockData,
198
- // source: MockDataSource.defineProperty,
199
- // generated: false
200
- // });
201
- // ++mockData.useCount;
202
- return Reflect.defineProperty(target, property, realValue);
203
- },
204
- deleteProperty(target, p) {
205
- if (!mockData.sideEffects) {
206
- mockData.sideEffects = [];
207
- }
208
- mockData.sideEffects.push({
209
- useCount: 0,
210
- name: p,
211
- options: undefined,
212
- parent: mockData,
213
- source: 5 /* MockDataSource.deleteProperty */,
214
- generated: false
215
- });
216
- ++mockData.useCount;
217
- const result = Reflect.deleteProperty(target, p);
218
- return result;
219
- },
220
- setPrototypeOf(target, v) {
221
- const realValue = unwrapValue(v);
222
- if (!mockData.sideEffects) {
223
- mockData.sideEffects = [];
224
- }
225
- mockData.sideEffects.push({
226
- useCount: 0,
227
- name: '',
228
- options: realValue,
229
- parent: mockData,
230
- source: 6 /* MockDataSource.setPrototypeOf */,
231
- generated: false
232
- });
233
- ++mockData.useCount;
234
- return Reflect.setPrototypeOf(target, realValue);
235
- },
236
- preventExtensions(target) {
237
- if (!mockData.sideEffects) {
238
- mockData.sideEffects = [];
239
- }
240
- mockData.sideEffects.push({
241
- useCount: 0,
242
- name: '',
243
- options: undefined,
244
- parent: mockData,
245
- source: 7 /* MockDataSource.preventExtensions */,
246
- generated: false
247
- });
248
- ++mockData.useCount;
249
- return Reflect.preventExtensions(target);
250
- },
251
- apply(target, thisArg, argumentsList) {
252
- const realThis = unwrapValue(thisArg);
253
- const realArguments = unwrapValue(argumentsList);
254
- ++mockData.useCount;
255
- const result = Reflect.apply(target, realThis, realArguments);
256
- if (result === null || (typeof result !== 'object' && typeof result !== 'function')) {
257
- if (!mockData.sideEffects) {
258
- mockData.sideEffects = [];
259
- }
260
- mockData.sideEffects.push({
261
- useCount: 0,
262
- name: '',
263
- parent: mockData,
264
- source: 2 /* MockDataSource.call */,
265
- options: realArguments,
266
- generated: false
267
- });
268
- ++mockData.useCount;
269
- return result;
270
- }
271
- return createInternalMock(result, {
272
- useCount: 0,
273
- name: '',
274
- parent: mockData,
275
- source: 2 /* MockDataSource.call */,
276
- options: realArguments,
277
- generated: false
278
- });
279
- }
280
- });
281
- }
282
- function getNextInstanceName() {
283
- return `tmp_${counter++}`;
284
- }
285
- }
286
- function getParameters(options, replacer) {
287
- return `(${options.length ? options.map(value => stringify(value, replacer)).join(',') : ''})`;
288
- }
289
- // stringify like functionality, recursively walks through objects and converts them to strings but leaved some basic values intact
290
- function stringify(value, replacer) {
291
- const original = value;
292
- value = replacer(value);
293
- if (original !== value && typeof value === 'string') {
294
- return value;
295
- }
296
- if (value === null) {
297
- return null;
298
- }
299
- if (value === undefined) {
300
- return undefined;
301
- }
302
- if (typeof value === 'number') {
303
- return `${value}`;
304
- }
305
- if (Array.isArray(value)) {
306
- return `[${value.map((v) => stringify(v, replacer)).join(',')}]`;
307
- }
308
- if (typeof value === 'boolean') {
309
- return value;
310
- }
311
- if (typeof value === 'function') {
312
- return value.toString();
313
- }
314
- if (value instanceof RegExp) {
315
- return value;
316
- }
317
- if (typeof value === 'object') {
318
- return `{${Object.entries(value).map(([k, v]) => k + ':' + stringify(v, replacer)).join(',')}}`;
319
- }
320
- return '"' + String(value) + '"';
321
- }
322
-
323
- module.exports = createRecordingMockFactory;
package/dist/index.d.ts DELETED
@@ -1,16 +0,0 @@
1
- export declare const enum MockDataSource {
2
- root = 0,
3
- get = 1,
4
- call = 2,
5
- set = 3,
6
- defineProperty = 4,
7
- deleteProperty = 5,
8
- setPrototypeOf = 6,
9
- preventExtensions = 7,
10
- construct = 8
11
- }
12
- export default function createRecordingMockFactory(): {
13
- createMock: <T extends object>(object: T, name: string) => T;
14
- generateGlobals: () => string;
15
- generate: (object: unknown) => string | boolean | RegExp | null | undefined;
16
- };
package/dist/index.mjs DELETED
@@ -1,321 +0,0 @@
1
- const mock = Symbol();
2
- const unwrap = Symbol();
3
- const unwrapValue = (value) => (value != null && value[unwrap]) || value;
4
- const getMockData = (value) => (value != null && value[mock]) || undefined;
5
- function createRecordingMockFactory() {
6
- const mockDatas = [];
7
- let counter = 0;
8
- return {
9
- createMock,
10
- generateGlobals,
11
- generate
12
- };
13
- function createMock(object, name) {
14
- return createInternalMock(object, {
15
- name,
16
- source: 0 /* MockDataSource.root */,
17
- useCount: 0,
18
- generated: false
19
- });
20
- }
21
- function generateGlobals() {
22
- const strings = [];
23
- for (const mockData of mockDatas) {
24
- if (mockData.generated)
25
- continue;
26
- if (!mockData.instanceName && mockData.source !== 0 /* MockDataSource.root */) {
27
- mockData.instanceName = getNextInstanceName();
28
- }
29
- const identifier = (mockData?.instanceName ?? mockData?.name);
30
- if (mockData.source !== 0 /* MockDataSource.root */) {
31
- strings.push('const ' + identifier + ' = ' + getAccessor(mockData, mockData.parent));
32
- }
33
- for (const effect of (mockData.sideEffects || [])) {
34
- switch (effect.source) {
35
- case 3 /* MockDataSource.set */:
36
- strings.push(identifier + '.' + effect.name + ' = ' + stringify(effect.options, replacer));
37
- break;
38
- // case MockDataSource.defineProperty:
39
- // strings.push('Object.defineProperty(' + identifier + ', "' + (effect.name as string) + '", ' + stringify(effect.options, replacer as ReplacerFunction) + ')');
40
- // break;
41
- case 5 /* MockDataSource.deleteProperty */:
42
- strings.push('delete ' + identifier + '["' + effect.name + '"]');
43
- break;
44
- case 6 /* MockDataSource.setPrototypeOf */:
45
- strings.push('Object.setPrototypeOf(' + identifier + ', ' + stringify(effect.options, replacer) + ')');
46
- break;
47
- case 7 /* MockDataSource.preventExtensions */:
48
- strings.push('Object.preventExtensions(' + identifier + ')');
49
- break;
50
- case 2 /* MockDataSource.call */:
51
- strings.push(identifier + getParameters(effect.options, replacer));
52
- break;
53
- }
54
- }
55
- }
56
- return strings.join('\n');
57
- function getAccessor(mockData, parent) {
58
- const parentName = (parent?.instanceName ?? parent?.name);
59
- switch (mockData.source) {
60
- case 2 /* MockDataSource.call */:
61
- return parentName + getParameters(mockData.options, replacer);
62
- case 1 /* MockDataSource.get */:
63
- return parentName + '.' + mockData.name;
64
- case 8 /* MockDataSource.construct */:
65
- {
66
- const newTarget = stringify(mockData.target, replacer);
67
- return parentName !== newTarget
68
- ? 'Reflect.construct(' + parentName + ',' + stringify(mockData.options, replacer) + ',' + newTarget + ')'
69
- : 'new ' + parentName + getParameters(mockData.options, replacer);
70
- }
71
- }
72
- }
73
- }
74
- function generate(object) {
75
- stringify(object, bumpReplacer);
76
- return stringify(object, replacer);
77
- }
78
- function bumpReplacer(value) {
79
- const mockData = getMockData(value);
80
- if (mockData) {
81
- ++mockData.useCount;
82
- return getCode(mockData, bumpReplacer, true);
83
- }
84
- return value;
85
- }
86
- function replacer(value) {
87
- const mockData = getMockData(value);
88
- if (mockData) {
89
- return getCode(mockData, replacer, true);
90
- }
91
- return value;
92
- }
93
- function getCode(value, replacer, bumpCount) {
94
- if (bumpCount && value.useCount > 1) {
95
- if (value.source === 0 /* MockDataSource.root */) {
96
- return value.name;
97
- }
98
- if (!value.instanceName) {
99
- value.instanceName = getNextInstanceName();
100
- }
101
- return value.instanceName;
102
- }
103
- value.generated = true;
104
- switch (value.source) {
105
- case 2 /* MockDataSource.call */:
106
- return getPrevCode(value) + getParameters(value.options, replacer);
107
- case 1 /* MockDataSource.get */:
108
- return getPrevCode(value) + '.' + value.name;
109
- case 0 /* MockDataSource.root */:
110
- return value.name;
111
- case 8 /* MockDataSource.construct */:
112
- {
113
- const prevCode = getPrevCode(value);
114
- const newTarget = stringify(value.target, replacer);
115
- return prevCode !== newTarget
116
- ? 'Reflect.construct(' + prevCode + ',' + stringify(value.options, replacer) + ',' + newTarget + ')'
117
- : 'new ' + prevCode + getParameters(value.options, replacer);
118
- }
119
- }
120
- function getPrevCode(mockData) {
121
- return mockData.parent ? getCode(mockData.parent, replacer, bumpCount) : '';
122
- }
123
- }
124
- function createInternalMock(target, mockData) {
125
- mockDatas.push(mockData);
126
- target[mock] = mockData;
127
- return new Proxy(target, {
128
- set(target, p, value, receiver) {
129
- const realValue = unwrapValue(value);
130
- if (!mockData.sideEffects) {
131
- mockData.sideEffects = [];
132
- }
133
- mockData.sideEffects.push({
134
- useCount: 0,
135
- name: p,
136
- options: realValue,
137
- parent: mockData,
138
- source: 3 /* MockDataSource.set */,
139
- generated: false
140
- });
141
- ++mockData.useCount;
142
- Reflect.set(target, p, realValue, receiver);
143
- return true;
144
- },
145
- get(target, p) {
146
- if (p === unwrap)
147
- return target;
148
- if (p === mock)
149
- return mockData;
150
- const value = Reflect.get(target, p);
151
- if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
152
- return value;
153
- }
154
- if (!mockData.mocks) {
155
- mockData.mocks = Object.create(null);
156
- }
157
- if (!mockData.mocks[p]) {
158
- mockData.mocks[p] = createInternalMock(value, {
159
- useCount: 0,
160
- name: p,
161
- parent: mockData,
162
- source: 1 /* MockDataSource.get */,
163
- generated: false
164
- });
165
- }
166
- const result = mockData.mocks[p];
167
- ++mockData.useCount;
168
- return result;
169
- },
170
- // eslint-disable-next-line @typescript-eslint/ban-types
171
- construct(target, argArray, newTarget) {
172
- const realTarget = unwrapValue(newTarget);
173
- const realArguments = unwrapValue(argArray);
174
- ++mockData.useCount;
175
- const result = Reflect.construct(target, realArguments, realTarget);
176
- return createInternalMock(result, {
177
- useCount: 0,
178
- name: '',
179
- options: realArguments,
180
- target: realTarget,
181
- parent: mockData,
182
- source: 8 /* MockDataSource.construct */,
183
- generated: false
184
- });
185
- },
186
- defineProperty(target, property, attributes) {
187
- const realValue = unwrapValue(attributes);
188
- // if (!mockData.sideEffects) {
189
- // mockData.sideEffects = [];
190
- // }
191
- // mockData.sideEffects.push({
192
- // useCount: 0,
193
- // name: property,
194
- // options: realValue,
195
- // parent: mockData,
196
- // source: MockDataSource.defineProperty,
197
- // generated: false
198
- // });
199
- // ++mockData.useCount;
200
- return Reflect.defineProperty(target, property, realValue);
201
- },
202
- deleteProperty(target, p) {
203
- if (!mockData.sideEffects) {
204
- mockData.sideEffects = [];
205
- }
206
- mockData.sideEffects.push({
207
- useCount: 0,
208
- name: p,
209
- options: undefined,
210
- parent: mockData,
211
- source: 5 /* MockDataSource.deleteProperty */,
212
- generated: false
213
- });
214
- ++mockData.useCount;
215
- const result = Reflect.deleteProperty(target, p);
216
- return result;
217
- },
218
- setPrototypeOf(target, v) {
219
- const realValue = unwrapValue(v);
220
- if (!mockData.sideEffects) {
221
- mockData.sideEffects = [];
222
- }
223
- mockData.sideEffects.push({
224
- useCount: 0,
225
- name: '',
226
- options: realValue,
227
- parent: mockData,
228
- source: 6 /* MockDataSource.setPrototypeOf */,
229
- generated: false
230
- });
231
- ++mockData.useCount;
232
- return Reflect.setPrototypeOf(target, realValue);
233
- },
234
- preventExtensions(target) {
235
- if (!mockData.sideEffects) {
236
- mockData.sideEffects = [];
237
- }
238
- mockData.sideEffects.push({
239
- useCount: 0,
240
- name: '',
241
- options: undefined,
242
- parent: mockData,
243
- source: 7 /* MockDataSource.preventExtensions */,
244
- generated: false
245
- });
246
- ++mockData.useCount;
247
- return Reflect.preventExtensions(target);
248
- },
249
- apply(target, thisArg, argumentsList) {
250
- const realThis = unwrapValue(thisArg);
251
- const realArguments = unwrapValue(argumentsList);
252
- ++mockData.useCount;
253
- const result = Reflect.apply(target, realThis, realArguments);
254
- if (result === null || (typeof result !== 'object' && typeof result !== 'function')) {
255
- if (!mockData.sideEffects) {
256
- mockData.sideEffects = [];
257
- }
258
- mockData.sideEffects.push({
259
- useCount: 0,
260
- name: '',
261
- parent: mockData,
262
- source: 2 /* MockDataSource.call */,
263
- options: realArguments,
264
- generated: false
265
- });
266
- ++mockData.useCount;
267
- return result;
268
- }
269
- return createInternalMock(result, {
270
- useCount: 0,
271
- name: '',
272
- parent: mockData,
273
- source: 2 /* MockDataSource.call */,
274
- options: realArguments,
275
- generated: false
276
- });
277
- }
278
- });
279
- }
280
- function getNextInstanceName() {
281
- return `tmp_${counter++}`;
282
- }
283
- }
284
- function getParameters(options, replacer) {
285
- return `(${options.length ? options.map(value => stringify(value, replacer)).join(',') : ''})`;
286
- }
287
- // stringify like functionality, recursively walks through objects and converts them to strings but leaved some basic values intact
288
- function stringify(value, replacer) {
289
- const original = value;
290
- value = replacer(value);
291
- if (original !== value && typeof value === 'string') {
292
- return value;
293
- }
294
- if (value === null) {
295
- return null;
296
- }
297
- if (value === undefined) {
298
- return undefined;
299
- }
300
- if (typeof value === 'number') {
301
- return `${value}`;
302
- }
303
- if (Array.isArray(value)) {
304
- return `[${value.map((v) => stringify(v, replacer)).join(',')}]`;
305
- }
306
- if (typeof value === 'boolean') {
307
- return value;
308
- }
309
- if (typeof value === 'function') {
310
- return value.toString();
311
- }
312
- if (value instanceof RegExp) {
313
- return value;
314
- }
315
- if (typeof value === 'object') {
316
- return `{${Object.entries(value).map(([k, v]) => k + ':' + stringify(v, replacer)).join(',')}}`;
317
- }
318
- return '"' + String(value) + '"';
319
- }
320
-
321
- export { createRecordingMockFactory as default };