@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 CHANGED
@@ -1,6 +1,51 @@
1
1
  # Smart Mock
2
2
 
3
- One more proxy based mock
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: true
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
- strings.push('const ' + mockData.instanceName + ' = ' + getAccessor(mockData, mockData.parent));
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(mockData.instanceName + '.' + effect.name + ' = ' + stringify(effect.options, replacer));
38
+ strings.push(identifier + '.' + effect.name + ' = ' + stringify(effect.options, replacer));
36
39
  break;
37
40
  // case MockDataSource.defineProperty:
38
- // strings.push('Object.defineProperty(' + mockData.instanceName + ', "' + (effect.name as string) + '", ' + stringify(effect.options, replacer as ReplacerFunction) + ')');
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 ' + mockData.instanceName + '["' + effect.name + '"]');
44
+ strings.push('delete ' + identifier + '["' + effect.name + '"]');
42
45
  break;
43
46
  case 6 /* MockDataSource.setPrototypeOf */:
44
- strings.push('Object.setPrototypeOf(' + mockData.instanceName + ', ' + stringify(effect.options, replacer) + ')');
47
+ strings.push('Object.setPrototypeOf(' + identifier + ', ' + stringify(effect.options, replacer) + ')');
45
48
  break;
46
49
  case 7 /* MockDataSource.preventExtensions */:
47
- strings.push('Object.preventExtensions(' + mockData.instanceName + ')');
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
- var _a;
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
- return createInternalMock(Reflect.apply(target, realThis, realArguments), {
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: <T_1 extends object>(object: T_1) => string | boolean | RegExp | null | undefined;
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: true
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
- strings.push('const ' + mockData.instanceName + ' = ' + getAccessor(mockData, mockData.parent));
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(mockData.instanceName + '.' + effect.name + ' = ' + stringify(effect.options, replacer));
36
+ strings.push(identifier + '.' + effect.name + ' = ' + stringify(effect.options, replacer));
34
37
  break;
35
38
  // case MockDataSource.defineProperty:
36
- // strings.push('Object.defineProperty(' + mockData.instanceName + ', "' + (effect.name as string) + '", ' + stringify(effect.options, replacer as ReplacerFunction) + ')');
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 ' + mockData.instanceName + '["' + effect.name + '"]');
42
+ strings.push('delete ' + identifier + '["' + effect.name + '"]');
40
43
  break;
41
44
  case 6 /* MockDataSource.setPrototypeOf */:
42
- strings.push('Object.setPrototypeOf(' + mockData.instanceName + ', ' + stringify(effect.options, replacer) + ')');
45
+ strings.push('Object.setPrototypeOf(' + identifier + ', ' + stringify(effect.options, replacer) + ')');
43
46
  break;
44
47
  case 7 /* MockDataSource.preventExtensions */:
45
- strings.push('Object.preventExtensions(' + mockData.instanceName + ')');
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
- var _a;
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
- return createInternalMock(Reflect.apply(target, realThis, realArguments), {
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
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.0",
2
+ "version": "0.1.2",
3
3
  "license": "MIT",
4
4
  "name": "@slimlib/smart-mock",
5
5
  "author": "Konstantin Shutkin",