@slimlib/smart-mock 0.1.6 → 1.0.1
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/README.md +19 -14
- package/package.json +7 -10
- package/src/index.js +532 -0
- package/types/index.d.ts +44 -0
- package/types/index.d.ts.map +15 -0
- package/dist/index.cjs +0 -323
- package/dist/index.d.ts +0 -16
- package/dist/index.mjs +0 -321
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Yet another proxy mock (YAPM?). Still in a very early state (EXPECT BUGS!).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A mock that records operations for code generation later. The idea is somewhat similar to `prepack`, but instead of interpreting code by other JS code, we run it in a JS VM and later use the mock to repeat the same operations. Ideally combined with a terser-like optimizer. Please check the example in `pkgbld` to see how it is used to eject config.
|
|
6
6
|
|
|
7
7
|
[Changelog](./CHANGELOG.md)
|
|
8
8
|
|
|
@@ -10,11 +10,11 @@ Mock that records operations for code generation later. Idea is somewhat similar
|
|
|
10
10
|
|
|
11
11
|
### default `() => { createMock, generateGlobals, generate }`
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The default export function is a factory that creates 3 other functions with shared state.
|
|
14
14
|
|
|
15
15
|
### `createMock<T extends object>(object: T, name: string): T`
|
|
16
16
|
|
|
17
|
-
Function to create mock wrapper around object and
|
|
17
|
+
Function to create a mock wrapper around an object and define a global name for later usage. `object` can be a real original object or a pure mock object with the same behavior for the specific situation. All operations on this object will be recorded by the mock.
|
|
18
18
|
|
|
19
19
|
### `generate(object: unknown): string`
|
|
20
20
|
|
|
@@ -22,19 +22,24 @@ Function to generate code for some export point (exit point). It will try to aut
|
|
|
22
22
|
|
|
23
23
|
### `generateGlobals(): string`
|
|
24
24
|
|
|
25
|
-
Function to generate global code that cannot be inlined to exit point.
|
|
25
|
+
Function to generate global code that cannot be inlined to an exit point.
|
|
26
26
|
|
|
27
27
|
### Example
|
|
28
28
|
|
|
29
29
|
```javascript
|
|
30
|
-
import createMockProvider from
|
|
30
|
+
import createMockProvider from "@slimlib/smart-mock";
|
|
31
31
|
const { createMock, generate, generateGlobals } = createMockProvider();
|
|
32
|
-
const mock = createMock(
|
|
33
|
-
|
|
32
|
+
const mock = createMock(
|
|
33
|
+
{
|
|
34
|
+
fly() {
|
|
35
|
+
return { status: "flying" };
|
|
36
|
+
},
|
|
34
37
|
land() {},
|
|
35
|
-
name:
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
+
name: "",
|
|
39
|
+
},
|
|
40
|
+
"fly"
|
|
41
|
+
);
|
|
42
|
+
mock.name = "Moth";
|
|
38
43
|
const status = mock.fly();
|
|
39
44
|
mock.land();
|
|
40
45
|
```
|
|
@@ -42,12 +47,12 @@ mock.land();
|
|
|
42
47
|
At this point `generate(mock)` will result in `mock`, `generate(mock.name)` in `'Moth'` and `generate(status)` in `fly.fly()`. And if you afterwards call `generateGlobals()` you get something like:
|
|
43
48
|
|
|
44
49
|
```javascript
|
|
45
|
-
fly.name = "Moth"
|
|
46
|
-
const tmp_0 = fly.land
|
|
47
|
-
tmp_0()
|
|
50
|
+
fly.name = "Moth";
|
|
51
|
+
const tmp_0 = fly.land;
|
|
52
|
+
tmp_0();
|
|
48
53
|
```
|
|
49
54
|
|
|
50
|
-
Each `generate` call updates counters / flags in mock so `generateGlobals` only emits what was not generated at the time of call.
|
|
55
|
+
Each `generate` call updates counters / flags in the mock so `generateGlobals` only emits what was not generated at the time of the call.
|
|
51
56
|
|
|
52
57
|
# License
|
|
53
58
|
|
package/package.json
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
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": "./
|
|
13
|
-
"
|
|
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": "./
|
|
15
|
+
"types": "./types/index.d.ts",
|
|
20
16
|
"files": [
|
|
21
|
-
"
|
|
17
|
+
"src",
|
|
18
|
+
"types"
|
|
22
19
|
],
|
|
23
20
|
"engines": {
|
|
24
|
-
"node": ">=
|
|
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
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -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 };
|