@slimlib/smart-mock 0.1.0 → 0.1.2
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 +46 -1
- package/dist/index.cjs +33 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +33 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
# Smart Mock
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Yet another proxy mock (YAPM?). Still in a very early state (EXPECT BUGS!).
|
|
4
|
+
|
|
5
|
+
Mock that records operations for code generation later. Idea is somewhat similar to `prepack` but instead of interpreting code by other JS code we run it in JS VM and later use mock to repeat same operations. Ideally combined with terser like optimizer. Please check example in `pkgbld` how it is used to eject config.
|
|
6
|
+
|
|
7
|
+
## API
|
|
8
|
+
|
|
9
|
+
### default `() => { createMock, generateGlobals, generate }`
|
|
10
|
+
|
|
11
|
+
Default export function is a factory that creates 3 other functions with shared state.
|
|
12
|
+
|
|
13
|
+
### `createMock<T extends object>(object: T, name: string): T`
|
|
14
|
+
|
|
15
|
+
Function to create mock wrapper around object and defining global name for later usage. `object` can be real original object or a pure mock object with same behavior for the specific situation. All operations on this object will be recorded by mock.
|
|
16
|
+
|
|
17
|
+
### `generate(object: unknown): string`
|
|
18
|
+
|
|
19
|
+
Function to generate code for some export point (exit point). It will try to automatically inline operations that can be inlined.
|
|
20
|
+
|
|
21
|
+
### `generateGlobals(): string`
|
|
22
|
+
|
|
23
|
+
Function to generate global code that cannot be inlined to exit point.
|
|
24
|
+
|
|
25
|
+
### Example
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
import createMockProvider from '@slimlib/smart-mock';
|
|
29
|
+
const { createMock, generate, generateGlobals } = createMockProvider();
|
|
30
|
+
const mock = createMock({
|
|
31
|
+
fly() { return { status: 'flying' }; },
|
|
32
|
+
land() {},
|
|
33
|
+
name: ''
|
|
34
|
+
}, 'fly');
|
|
35
|
+
mock.name = 'Moth';
|
|
36
|
+
const status = mock.fly();
|
|
37
|
+
mock.land();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
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:
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
fly.name = "Moth"
|
|
44
|
+
const tmp_0 = fly.land
|
|
45
|
+
tmp_0()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Each `generate` call updates counters / flags in mock so `generateGlobals` only emits what was not generated at the time of call.
|
|
4
49
|
|
|
5
50
|
# License
|
|
6
51
|
|
package/dist/index.cjs
CHANGED
|
@@ -17,7 +17,7 @@ function createRecordingMockFactory() {
|
|
|
17
17
|
name,
|
|
18
18
|
source: 0 /* MockDataSource.root */,
|
|
19
19
|
useCount: 0,
|
|
20
|
-
generated:
|
|
20
|
+
generated: false
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
function generateGlobals() {
|
|
@@ -25,34 +25,39 @@ function createRecordingMockFactory() {
|
|
|
25
25
|
for (const mockData of mockDatas) {
|
|
26
26
|
if (mockData.generated)
|
|
27
27
|
continue;
|
|
28
|
-
if (!mockData.instanceName) {
|
|
28
|
+
if (!mockData.instanceName && mockData.source !== 0 /* MockDataSource.root */) {
|
|
29
29
|
mockData.instanceName = getNextInstanceName();
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
const identifier = (mockData?.instanceName ?? mockData?.name);
|
|
32
|
+
if (mockData.source !== 0 /* MockDataSource.root */) {
|
|
33
|
+
strings.push('const ' + identifier + ' = ' + getAccessor(mockData, mockData.parent));
|
|
34
|
+
}
|
|
32
35
|
for (const effect of (mockData.sideEffects || [])) {
|
|
33
36
|
switch (effect.source) {
|
|
34
37
|
case 3 /* MockDataSource.set */:
|
|
35
|
-
strings.push(
|
|
38
|
+
strings.push(identifier + '.' + effect.name + ' = ' + stringify(effect.options, replacer));
|
|
36
39
|
break;
|
|
37
40
|
// case MockDataSource.defineProperty:
|
|
38
|
-
// strings.push('Object.defineProperty(' +
|
|
41
|
+
// strings.push('Object.defineProperty(' + identifier + ', "' + (effect.name as string) + '", ' + stringify(effect.options, replacer as ReplacerFunction) + ')');
|
|
39
42
|
// break;
|
|
40
43
|
case 5 /* MockDataSource.deleteProperty */:
|
|
41
|
-
strings.push('delete ' +
|
|
44
|
+
strings.push('delete ' + identifier + '["' + effect.name + '"]');
|
|
42
45
|
break;
|
|
43
46
|
case 6 /* MockDataSource.setPrototypeOf */:
|
|
44
|
-
strings.push('Object.setPrototypeOf(' +
|
|
47
|
+
strings.push('Object.setPrototypeOf(' + identifier + ', ' + stringify(effect.options, replacer) + ')');
|
|
45
48
|
break;
|
|
46
49
|
case 7 /* MockDataSource.preventExtensions */:
|
|
47
|
-
strings.push('Object.preventExtensions(' +
|
|
50
|
+
strings.push('Object.preventExtensions(' + identifier + ')');
|
|
51
|
+
break;
|
|
52
|
+
case 2 /* MockDataSource.call */:
|
|
53
|
+
strings.push(identifier + getParameters(effect.options, replacer));
|
|
48
54
|
break;
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
return strings.join('\n');
|
|
53
59
|
function getAccessor(mockData, parent) {
|
|
54
|
-
|
|
55
|
-
const parentName = ((_a = parent.instanceName) !== null && _a !== void 0 ? _a : parent.name);
|
|
60
|
+
const parentName = (parent?.instanceName ?? parent?.name);
|
|
56
61
|
switch (mockData.source) {
|
|
57
62
|
case 2 /* MockDataSource.call */:
|
|
58
63
|
return parentName + getParameters(mockData.options, replacer);
|
|
@@ -253,7 +258,23 @@ function createRecordingMockFactory() {
|
|
|
253
258
|
const realThis = unwrapValue(thisArg);
|
|
254
259
|
const realArguments = unwrapValue(argumentsList);
|
|
255
260
|
++mockData.useCount;
|
|
256
|
-
|
|
261
|
+
const result = Reflect.apply(target, realThis, realArguments);
|
|
262
|
+
if (result === null || (typeof result !== 'object' && typeof result !== 'function')) {
|
|
263
|
+
if (!mockData.sideEffects) {
|
|
264
|
+
mockData.sideEffects = [];
|
|
265
|
+
}
|
|
266
|
+
mockData.sideEffects.push({
|
|
267
|
+
useCount: 0,
|
|
268
|
+
name: '',
|
|
269
|
+
parent: mockData,
|
|
270
|
+
source: 2 /* MockDataSource.call */,
|
|
271
|
+
options: realArguments,
|
|
272
|
+
generated: false
|
|
273
|
+
});
|
|
274
|
+
++mockData.useCount;
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
return createInternalMock(result, {
|
|
257
278
|
useCount: 0,
|
|
258
279
|
name: '',
|
|
259
280
|
parent: mockData,
|
|
@@ -271,6 +292,7 @@ function createRecordingMockFactory() {
|
|
|
271
292
|
function getParameters(options, replacer) {
|
|
272
293
|
return `(${options.length ? options.map(value => stringify(value, replacer)).join(',') : ''})`;
|
|
273
294
|
}
|
|
295
|
+
// stringify like functionality, recursively walks through objects and converts them to strings but leaved some basic values intact
|
|
274
296
|
function stringify(value, replacer) {
|
|
275
297
|
const original = value;
|
|
276
298
|
value = replacer(value);
|
package/dist/index.d.ts
CHANGED
|
@@ -12,5 +12,5 @@ export declare const enum MockDataSource {
|
|
|
12
12
|
export default function createRecordingMockFactory(): {
|
|
13
13
|
createMock: <T extends object>(object: T, name: string) => T;
|
|
14
14
|
generateGlobals: () => string;
|
|
15
|
-
generate:
|
|
15
|
+
generate: (object: unknown) => string | boolean | RegExp | null | undefined;
|
|
16
16
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -15,7 +15,7 @@ function createRecordingMockFactory() {
|
|
|
15
15
|
name,
|
|
16
16
|
source: 0 /* MockDataSource.root */,
|
|
17
17
|
useCount: 0,
|
|
18
|
-
generated:
|
|
18
|
+
generated: false
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
21
|
function generateGlobals() {
|
|
@@ -23,34 +23,39 @@ function createRecordingMockFactory() {
|
|
|
23
23
|
for (const mockData of mockDatas) {
|
|
24
24
|
if (mockData.generated)
|
|
25
25
|
continue;
|
|
26
|
-
if (!mockData.instanceName) {
|
|
26
|
+
if (!mockData.instanceName && mockData.source !== 0 /* MockDataSource.root */) {
|
|
27
27
|
mockData.instanceName = getNextInstanceName();
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
const identifier = (mockData?.instanceName ?? mockData?.name);
|
|
30
|
+
if (mockData.source !== 0 /* MockDataSource.root */) {
|
|
31
|
+
strings.push('const ' + identifier + ' = ' + getAccessor(mockData, mockData.parent));
|
|
32
|
+
}
|
|
30
33
|
for (const effect of (mockData.sideEffects || [])) {
|
|
31
34
|
switch (effect.source) {
|
|
32
35
|
case 3 /* MockDataSource.set */:
|
|
33
|
-
strings.push(
|
|
36
|
+
strings.push(identifier + '.' + effect.name + ' = ' + stringify(effect.options, replacer));
|
|
34
37
|
break;
|
|
35
38
|
// case MockDataSource.defineProperty:
|
|
36
|
-
// strings.push('Object.defineProperty(' +
|
|
39
|
+
// strings.push('Object.defineProperty(' + identifier + ', "' + (effect.name as string) + '", ' + stringify(effect.options, replacer as ReplacerFunction) + ')');
|
|
37
40
|
// break;
|
|
38
41
|
case 5 /* MockDataSource.deleteProperty */:
|
|
39
|
-
strings.push('delete ' +
|
|
42
|
+
strings.push('delete ' + identifier + '["' + effect.name + '"]');
|
|
40
43
|
break;
|
|
41
44
|
case 6 /* MockDataSource.setPrototypeOf */:
|
|
42
|
-
strings.push('Object.setPrototypeOf(' +
|
|
45
|
+
strings.push('Object.setPrototypeOf(' + identifier + ', ' + stringify(effect.options, replacer) + ')');
|
|
43
46
|
break;
|
|
44
47
|
case 7 /* MockDataSource.preventExtensions */:
|
|
45
|
-
strings.push('Object.preventExtensions(' +
|
|
48
|
+
strings.push('Object.preventExtensions(' + identifier + ')');
|
|
49
|
+
break;
|
|
50
|
+
case 2 /* MockDataSource.call */:
|
|
51
|
+
strings.push(identifier + getParameters(effect.options, replacer));
|
|
46
52
|
break;
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
56
|
return strings.join('\n');
|
|
51
57
|
function getAccessor(mockData, parent) {
|
|
52
|
-
|
|
53
|
-
const parentName = ((_a = parent.instanceName) !== null && _a !== void 0 ? _a : parent.name);
|
|
58
|
+
const parentName = (parent?.instanceName ?? parent?.name);
|
|
54
59
|
switch (mockData.source) {
|
|
55
60
|
case 2 /* MockDataSource.call */:
|
|
56
61
|
return parentName + getParameters(mockData.options, replacer);
|
|
@@ -251,7 +256,23 @@ function createRecordingMockFactory() {
|
|
|
251
256
|
const realThis = unwrapValue(thisArg);
|
|
252
257
|
const realArguments = unwrapValue(argumentsList);
|
|
253
258
|
++mockData.useCount;
|
|
254
|
-
|
|
259
|
+
const result = Reflect.apply(target, realThis, realArguments);
|
|
260
|
+
if (result === null || (typeof result !== 'object' && typeof result !== 'function')) {
|
|
261
|
+
if (!mockData.sideEffects) {
|
|
262
|
+
mockData.sideEffects = [];
|
|
263
|
+
}
|
|
264
|
+
mockData.sideEffects.push({
|
|
265
|
+
useCount: 0,
|
|
266
|
+
name: '',
|
|
267
|
+
parent: mockData,
|
|
268
|
+
source: 2 /* MockDataSource.call */,
|
|
269
|
+
options: realArguments,
|
|
270
|
+
generated: false
|
|
271
|
+
});
|
|
272
|
+
++mockData.useCount;
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
return createInternalMock(result, {
|
|
255
276
|
useCount: 0,
|
|
256
277
|
name: '',
|
|
257
278
|
parent: mockData,
|
|
@@ -269,6 +290,7 @@ function createRecordingMockFactory() {
|
|
|
269
290
|
function getParameters(options, replacer) {
|
|
270
291
|
return `(${options.length ? options.map(value => stringify(value, replacer)).join(',') : ''})`;
|
|
271
292
|
}
|
|
293
|
+
// stringify like functionality, recursively walks through objects and converts them to strings but leaved some basic values intact
|
|
272
294
|
function stringify(value, replacer) {
|
|
273
295
|
const original = value;
|
|
274
296
|
value = replacer(value);
|
package/package.json
CHANGED