vitest-auto-spy 1.0.1 → 1.1.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/README.md CHANGED
@@ -286,6 +286,10 @@ npm run test:coverage # 100% thresholds enforced
286
286
  npm run build
287
287
  ```
288
288
 
289
+ Releases are automated: merging a PR into `master` bumps the version from the
290
+ Conventional Commit types and publishes to npm — see
291
+ [CONTRIBUTING.md → Releases](./CONTRIBUTING.md#releases).
292
+
289
293
  If this package saved you time, a ⭐ on [GitHub](https://github.com/ASDAlexey/vitest-auto-spy)
290
294
  helps others find it.
291
295
 
package/dist/index.cjs CHANGED
@@ -1,71 +1,116 @@
1
1
  'use strict';
2
2
 
3
+ var vitest = require('vitest');
4
+ var javascriptStringify = require('javascript-stringify');
3
5
  var rxjs = require('rxjs');
4
6
  var operators = require('rxjs/operators');
5
7
  var testing = require('@angular/core/testing');
6
- var javascriptStringify = require('javascript-stringify');
7
- var vitest = require('vitest');
8
8
 
9
- // src/auto-spy.ts
9
+ // src/lib/accessor-spy.ts
10
+ function defineWithEmptyAccessors(obj, prop) {
11
+ Object.defineProperty(obj, prop, {
12
+ get() {
13
+ return void 0;
14
+ },
15
+ set(_value) {
16
+ },
17
+ configurable: true
18
+ });
19
+ }
20
+ function spyOnAccessor(autoSpy, accessorName, accessorType) {
21
+ const key = accessorName;
22
+ return accessorType === "setter" ? vitest.vi.spyOn(autoSpy, key, "set") : vitest.vi.spyOn(autoSpy, key, "get");
23
+ }
24
+ function createAccessorsSpies(autoSpy, gettersToSpyOn, settersToSpyOn) {
25
+ const accessorSpies = { getters: {}, setters: {} };
26
+ autoSpy["accessorSpies"] = accessorSpies;
27
+ gettersToSpyOn.forEach((getterName) => {
28
+ defineWithEmptyAccessors(autoSpy, getterName);
29
+ accessorSpies.getters[getterName] = spyOnAccessor(autoSpy, getterName, "getter");
30
+ });
31
+ settersToSpyOn.forEach((setterName) => {
32
+ if (!Object.prototype.hasOwnProperty.call(autoSpy, setterName)) {
33
+ defineWithEmptyAccessors(autoSpy, setterName);
34
+ }
35
+ accessorSpies.setters[setterName] = spyOnAccessor(autoSpy, setterName, "setter");
36
+ });
37
+ }
10
38
  var ArgsMap = class {
11
- constructor() {
12
- this.map = {};
13
- }
14
- // `stringify` always serializes an argument array to a string, so the cast is safe.
39
+ #map = {};
15
40
  set(key, value) {
16
- this.map[javascriptStringify.stringify(key)] = value;
41
+ this.#map[this.#serialize(key)] = value;
17
42
  }
18
43
  get(key) {
19
- return this.map[javascriptStringify.stringify(key)];
44
+ return this.#map[this.#serialize(key)];
45
+ }
46
+ // Keys are always argument arrays, which `javascript-stringify` always renders
47
+ // to a string. `String(...)` keeps the result total against its `string |
48
+ // undefined` signature without an unreachable fallback branch.
49
+ #serialize(key) {
50
+ return String(javascriptStringify.stringify(key));
20
51
  }
21
52
  };
53
+ var MUST_BE_CALLED_WITH_PREAMBLE = (functionName) => `The function '${functionName}' was configured with 'mustBeCalledWith' and expects to be called with specific arguments. `;
54
+ var NO_ARGUMENTS_MESSAGE = `But the function was called without any arguments.`;
55
+ var actualArgumentsMessage = (actualArgs) => {
56
+ const formatted = String(javascriptStringify.stringify(actualArgs));
57
+ return `But the actual arguments were: ${formatted.substring(1, formatted.length - 1)}`;
58
+ };
22
59
  var errorHandler = {
23
60
  throwArgumentsError(actualArgs, functionName) {
24
- let errorMessage = `The function '${functionName}' was configured with 'mustBeCalledWith' and expects to be called with specific arguments. `;
25
- if (actualArgs.length === 0) {
26
- errorMessage += `But the function was called without any arguments.`;
27
- } else {
28
- let formattedArgs = javascriptStringify.stringify(actualArgs);
29
- formattedArgs = formattedArgs.substring(1, formattedArgs.length - 1);
30
- errorMessage += `But the actual arguments were: ${formattedArgs}`;
31
- }
32
- throw new Error(errorMessage);
61
+ const detail = actualArgs.length === 0 ? NO_ARGUMENTS_MESSAGE : actualArgumentsMessage(actualArgs);
62
+ throw new Error(MUST_BE_CALLED_WITH_PREAMBLE(functionName) + detail);
33
63
  }
34
64
  };
65
+
66
+ // src/lib/constants.ts
67
+ var REPLAY_BUFFER_SIZE = 1;
68
+
69
+ // src/lib/spy-decoration.ts
70
+ function decorate(target, helpers) {
71
+ return Object.assign(target, helpers);
72
+ }
73
+
74
+ // src/lib/value-config-guards.ts
75
+ function isCompleteConfig(config) {
76
+ return "complete" in config;
77
+ }
78
+ function isNextValueConfig(config) {
79
+ return "value" in config;
80
+ }
81
+ function isErrorConfig(config) {
82
+ return "errorValue" in config;
83
+ }
84
+
85
+ // src/lib/observable-spy.ts
35
86
  function createReplaySubject() {
36
- return new rxjs.ReplaySubject(1);
87
+ return new rxjs.ReplaySubject(REPLAY_BUFFER_SIZE);
37
88
  }
38
89
  function mergeSubjectWithDefaultValues(subject, valuesConfigs) {
39
- const onCompleteSubject = new rxjs.ReplaySubject(1);
90
+ const onCompleteSubject = new rxjs.ReplaySubject(REPLAY_BUFFER_SIZE);
40
91
  const results$ = rxjs.from(valuesConfigs).pipe(
41
- operators.concatMap((valueConfig) => {
42
- if ("complete" in valueConfig && valueConfig.complete && valueConfig.delay) {
43
- return rxjs.of(valueConfig).pipe(operators.delay(valueConfig.delay));
44
- }
45
- return rxjs.of(valueConfig);
46
- }),
47
- operators.takeWhile((valueConfig) => {
48
- if (!("complete" in valueConfig)) {
92
+ // Honor a delay on a completion entry before it stops the stream.
93
+ operators.concatMap(
94
+ (config) => isCompleteConfig(config) && config.complete && config.delay ? rxjs.of(config).pipe(operators.delay(config.delay)) : rxjs.of(config)
95
+ ),
96
+ // Stop (and signal completion) as soon as a `{ complete: true }` entry arrives.
97
+ operators.takeWhile((config) => {
98
+ if (!isCompleteConfig(config)) {
49
99
  return true;
50
100
  }
51
- if (valueConfig.complete) {
101
+ if (config.complete) {
52
102
  onCompleteSubject.next();
53
103
  return false;
54
104
  }
55
105
  return true;
56
106
  }),
57
- operators.concatMap((valueConfig) => {
58
- if ("value" in valueConfig && valueConfig.value) {
59
- if (valueConfig.delay) {
60
- return rxjs.of(valueConfig.value).pipe(operators.delay(valueConfig.delay));
61
- }
62
- return rxjs.of(valueConfig.value);
107
+ // Map each remaining entry to its emission: a value, an error, or nothing.
108
+ operators.concatMap((config) => {
109
+ if (isNextValueConfig(config) && config.value) {
110
+ return config.delay ? rxjs.of(config.value).pipe(operators.delay(config.delay)) : rxjs.of(config.value);
63
111
  }
64
- if ("errorValue" in valueConfig && valueConfig.errorValue) {
65
- if (valueConfig.delay) {
66
- return rxjs.timer(valueConfig.delay).pipe(operators.switchMap(() => rxjs.throwError(() => valueConfig.errorValue)));
67
- }
68
- return rxjs.throwError(() => valueConfig.errorValue);
112
+ if (isErrorConfig(config) && config.errorValue) {
113
+ return config.delay ? rxjs.timer(config.delay).pipe(operators.switchMap(() => rxjs.throwError(() => config.errorValue))) : rxjs.throwError(() => config.errorValue);
69
114
  }
70
115
  return rxjs.EMPTY;
71
116
  })
@@ -73,57 +118,65 @@ function mergeSubjectWithDefaultValues(subject, valuesConfigs) {
73
118
  return rxjs.merge(results$, subject.pipe(operators.takeUntil(onCompleteSubject)));
74
119
  }
75
120
  function addObservableHelpers(objectToDecorate, providedSubject, onSubjectConfigured) {
76
- objectToDecorate.nextWith = (value) => {
77
- providedSubject.next(value);
78
- onSubjectConfigured(providedSubject);
79
- };
80
- objectToDecorate.nextOneTimeWith = (value) => {
81
- providedSubject.next(value);
82
- providedSubject.complete();
83
- onSubjectConfigured(providedSubject);
84
- };
85
- objectToDecorate.nextWithValues = (valuesConfigs) => {
86
- if (valuesConfigs.length === 0) {
87
- return;
121
+ decorate(objectToDecorate, {
122
+ nextWith: (value) => {
123
+ providedSubject.next(value);
124
+ onSubjectConfigured(providedSubject);
125
+ },
126
+ nextOneTimeWith: (value) => {
127
+ providedSubject.next(value);
128
+ providedSubject.complete();
129
+ onSubjectConfigured(providedSubject);
130
+ },
131
+ nextWithValues: (valuesConfigs) => {
132
+ if (valuesConfigs.length === 0) {
133
+ return;
134
+ }
135
+ onSubjectConfigured(mergeSubjectWithDefaultValues(providedSubject, valuesConfigs));
136
+ },
137
+ throwWith: (value) => {
138
+ providedSubject.error(value);
139
+ onSubjectConfigured(providedSubject);
140
+ },
141
+ complete: () => {
142
+ providedSubject.complete();
143
+ onSubjectConfigured(providedSubject);
144
+ },
145
+ returnSubject: () => {
146
+ onSubjectConfigured(providedSubject);
147
+ return providedSubject;
88
148
  }
89
- onSubjectConfigured(mergeSubjectWithDefaultValues(providedSubject, valuesConfigs));
90
- };
91
- objectToDecorate.throwWith = (value) => {
92
- providedSubject.error(value);
93
- onSubjectConfigured(providedSubject);
94
- };
95
- objectToDecorate.complete = () => {
96
- providedSubject.complete();
97
- onSubjectConfigured(providedSubject);
98
- };
99
- objectToDecorate.returnSubject = () => {
100
- onSubjectConfigured(providedSubject);
101
- return providedSubject;
102
- };
149
+ });
150
+ }
151
+ function buildPerCallObservable(replaySubject, config) {
152
+ let observable = replaySubject.asObservable();
153
+ if (config.delay) {
154
+ observable = observable.pipe(operators.delay(config.delay));
155
+ }
156
+ if (!config.doNotComplete) {
157
+ observable = observable.pipe(operators.take(1));
158
+ }
159
+ return observable;
103
160
  }
104
161
  function addNextWithPerCall(objectToDecorate, returnValueContainer, onConfigured = () => void 0) {
105
- objectToDecorate.nextWithPerCall = (valueConfigsPerCall) => {
106
- const returnedSubjects = [];
107
- if (valueConfigsPerCall.length === 0) {
162
+ decorate(objectToDecorate, {
163
+ nextWithPerCall: (valueConfigsPerCall) => {
164
+ const returnedSubjects = [];
165
+ if (valueConfigsPerCall.length === 0) {
166
+ return returnedSubjects;
167
+ }
168
+ const valuesPerCalls = [];
169
+ valueConfigsPerCall.forEach((config) => {
170
+ const replaySubject = createReplaySubject();
171
+ replaySubject.next(config.value);
172
+ returnedSubjects.push(replaySubject);
173
+ valuesPerCalls.push({ wrappedValue: buildPerCallObservable(replaySubject, config) });
174
+ });
175
+ returnValueContainer.valuesPerCalls = valuesPerCalls;
176
+ onConfigured(returnValueContainer);
108
177
  return returnedSubjects;
109
178
  }
110
- returnValueContainer.valuesPerCalls = [];
111
- valueConfigsPerCall.forEach((valueConfiguration) => {
112
- const replaySubject = new rxjs.ReplaySubject(1);
113
- replaySubject.next(valueConfiguration.value);
114
- returnedSubjects.push(replaySubject);
115
- let returnedObservable = replaySubject.asObservable();
116
- if (valueConfiguration.delay) {
117
- returnedObservable = returnedObservable.pipe(operators.delay(valueConfiguration.delay));
118
- }
119
- if (!valueConfiguration.doNotComplete) {
120
- returnedObservable = returnedObservable.pipe(operators.take(1));
121
- }
122
- returnValueContainer.valuesPerCalls.push({ wrappedValue: returnedObservable });
123
- });
124
- onConfigured(returnValueContainer);
125
- return returnedSubjects;
126
- };
179
+ });
127
180
  }
128
181
  function addObservableHelpersToFunctionSpy(spyFunction, valueContainer) {
129
182
  const subject = createReplaySubject();
@@ -146,108 +199,107 @@ function addObservableHelpersToCalledWithObject(calledWithObject, calledWithArgs
146
199
  function createObservableWithValues(valuesConfigs, config) {
147
200
  const subject = createReplaySubject();
148
201
  const values$ = mergeSubjectWithDefaultValues(subject, valuesConfigs);
149
- if (config && config.returnSubject) {
150
- return { values$, subject };
151
- }
152
- return values$;
202
+ return config?.returnSubject ? { values$, subject } : values$;
153
203
  }
154
204
  function createObservablePropSpy() {
155
- let subject = createReplaySubject();
156
- const observableSpy = rxjs.defer(() => subject);
157
- addObservableHelpers(observableSpy, subject, (configuredSubject) => {
158
- subject = configuredSubject;
205
+ const providedSubject = createReplaySubject();
206
+ let published$ = providedSubject;
207
+ const observableSpy = rxjs.defer(() => published$);
208
+ addObservableHelpers(observableSpy, providedSubject, (configuredSubject) => {
209
+ published$ = configuredSubject;
159
210
  });
160
211
  return observableSpy;
161
212
  }
162
- function addPromiseHelpersToFunctionSpy(spyFunction, valueContainer) {
163
- spyFunction.resolveWith = (value) => {
164
- valueContainer.value = Promise.resolve(value);
165
- };
166
- spyFunction.rejectWith = (value) => {
167
- valueContainer.value = value;
168
- valueContainer._isRejectedPromise = true;
169
- };
170
- spyFunction.resolveWithPerCall = (valueConfigsPerCall) => {
171
- if (valueConfigsPerCall.length === 0) {
172
- return;
213
+
214
+ // src/lib/promise-spy.ts
215
+ function toResolvedPerCallValues(valueConfigsPerCall) {
216
+ return valueConfigsPerCall.map((config) => ({
217
+ wrappedValue: config.delay === void 0 ? Promise.resolve(config.value) : new Promise((resolve) => setTimeout(() => resolve(config.value), config.delay))
218
+ }));
219
+ }
220
+ function addPromiseHelpers(target, store) {
221
+ decorate(target, {
222
+ resolveWith: (value) => {
223
+ store({ value: Promise.resolve(value) });
224
+ },
225
+ rejectWith: (value) => {
226
+ store({ value, _isRejectedPromise: true });
227
+ },
228
+ resolveWithPerCall: (valueConfigsPerCall) => {
229
+ if (valueConfigsPerCall.length === 0) {
230
+ return;
231
+ }
232
+ store({ value: void 0, valuesPerCalls: toResolvedPerCallValues(valueConfigsPerCall) });
173
233
  }
174
- valueContainer.valuesPerCalls = [];
175
- valueConfigsPerCall.forEach((valueConfiguration) => {
176
- valueContainer.valuesPerCalls.push({
177
- wrappedValue: Promise.resolve(valueConfiguration.value),
178
- delay: valueConfiguration.delay
179
- });
180
- });
181
- };
234
+ });
235
+ }
236
+ function addPromiseHelpersToFunctionSpy(spyFunction, valueContainer) {
237
+ addPromiseHelpers(spyFunction, (container) => {
238
+ valueContainer.value = container.value;
239
+ valueContainer._isRejectedPromise = container._isRejectedPromise ?? false;
240
+ valueContainer.valuesPerCalls = container.valuesPerCalls ?? [];
241
+ });
182
242
  }
183
243
  function addPromiseHelpersToCalledWithObject(calledWithObject, calledWithArgs) {
184
- calledWithObject.resolveWith = (value) => {
185
- calledWithObject.argsToValuesMap.set(calledWithArgs, { value: Promise.resolve(value) });
186
- };
187
- calledWithObject.rejectWith = (value) => {
188
- calledWithObject.argsToValuesMap.set(calledWithArgs, { value, _isRejectedPromise: true });
189
- };
190
- calledWithObject.resolveWithPerCall = (valueConfigsPerCall) => {
191
- if (valueConfigsPerCall.length === 0) {
192
- return;
193
- }
194
- const valueContainer = { value: void 0, valuesPerCalls: [] };
195
- valueConfigsPerCall.forEach((valueConfiguration) => {
196
- valueContainer.valuesPerCalls.push({
197
- wrappedValue: Promise.resolve(valueConfiguration.value),
198
- delay: valueConfiguration.delay
199
- });
200
- });
201
- calledWithObject.argsToValuesMap.set(calledWithArgs, valueContainer);
202
- };
244
+ addPromiseHelpers(calledWithObject, (container) => {
245
+ calledWithObject.argsToValuesMap.set(calledWithArgs, container);
246
+ });
203
247
  }
204
- function getNextCallValue(valueContainer) {
205
- const wrapped = valueContainer.valuesPerCalls.shift();
206
- let returnedValue = wrapped?.wrappedValue;
207
- if (wrapped && wrapped.delay) {
208
- returnedValue = returnedValue.then(
209
- (value) => new Promise((resolve) => setTimeout(() => resolve(value), wrapped.delay))
210
- );
248
+
249
+ // src/lib/function-spy.ts
250
+ function isReturnValueContainer(value) {
251
+ return typeof value === "object" && value !== null && "value" in value;
252
+ }
253
+ function unwrapContainer(container) {
254
+ if (container._isRejectedPromise) {
255
+ return Promise.reject(container.value);
256
+ }
257
+ const wrapped = container.valuesPerCalls?.shift();
258
+ if (wrapped) {
259
+ return wrapped.wrappedValue;
260
+ }
261
+ return container.value;
262
+ }
263
+ function lookupConfigured(calledWithObject, actualArgs) {
264
+ const configured = calledWithObject.argsToValuesMap.get(actualArgs);
265
+ if (isReturnValueContainer(configured)) {
266
+ return { found: true, value: unwrapContainer(configured) };
211
267
  }
212
- return returnedValue;
268
+ return { found: false, value: void 0 };
213
269
  }
214
270
  function returnTheCorrectFakeValue(calledWithObject, mustBeCalledWithObject, valueContainer, actualArgs, functionName) {
215
271
  if (calledWithObject.wasConfigured) {
216
- const configured = calledWithObject.argsToValuesMap.get(actualArgs);
217
- if (configured) {
218
- return unwrapContainer(configured);
272
+ const match = lookupConfigured(calledWithObject, actualArgs);
273
+ if (match.found) {
274
+ return match.value;
219
275
  }
220
276
  }
221
277
  if (mustBeCalledWithObject.wasConfigured) {
222
- const configured = mustBeCalledWithObject.argsToValuesMap.get(actualArgs);
223
- if (configured) {
224
- return unwrapContainer(configured);
278
+ const match = lookupConfigured(mustBeCalledWithObject, actualArgs);
279
+ if (match.found) {
280
+ return match.value;
225
281
  }
226
282
  errorHandler.throwArgumentsError(actualArgs, functionName);
227
283
  }
228
284
  return unwrapContainer(valueContainer);
229
285
  }
230
- function unwrapContainer(container) {
231
- if (container._isRejectedPromise) {
232
- return Promise.reject(container.value);
233
- }
234
- if (container.valuesPerCalls?.length) {
235
- return getNextCallValue(container);
236
- }
237
- return container.value;
238
- }
239
286
  function addMethodsToCalledWith(calledWith, calledWithArgs) {
240
287
  calledWith.wasConfigured = true;
241
- calledWith.mockReturnValue = (value) => {
242
- calledWith.argsToValuesMap.set(calledWithArgs, { value });
243
- };
288
+ decorate(calledWith, {
289
+ mockReturnValue: (value) => {
290
+ calledWith.argsToValuesMap.set(calledWithArgs, { value });
291
+ }
292
+ });
244
293
  addPromiseHelpersToCalledWithObject(calledWith, calledWithArgs);
245
294
  addObservableHelpersToCalledWithObject(calledWith, calledWithArgs);
246
295
  return calledWith;
247
296
  }
297
+ function createCalledWithObject() {
298
+ return { wasConfigured: false, argsToValuesMap: new ArgsMap() };
299
+ }
248
300
  function createFunctionSpy(name) {
249
- const calledWithObject = { wasConfigured: false, argsToValuesMap: new ArgsMap() };
250
- const mustBeCalledWithObject = { wasConfigured: false, argsToValuesMap: new ArgsMap() };
301
+ const calledWithObject = createCalledWithObject();
302
+ const mustBeCalledWithObject = createCalledWithObject();
251
303
  const valueContainer = { value: void 0 };
252
304
  const functionSpy = vitest.vi.fn(
253
305
  (...actualArgs) => returnTheCorrectFakeValue(calledWithObject, mustBeCalledWithObject, valueContainer, actualArgs, name)
@@ -255,78 +307,56 @@ function createFunctionSpy(name) {
255
307
  functionSpy.mockName(name);
256
308
  addPromiseHelpersToFunctionSpy(functionSpy, valueContainer);
257
309
  addObservableHelpersToFunctionSpy(functionSpy, valueContainer);
258
- functionSpy.calledWith = (...calledWithArgs) => addMethodsToCalledWith(calledWithObject, calledWithArgs);
259
- functionSpy.mustBeCalledWith = (...calledWithArgs) => addMethodsToCalledWith(mustBeCalledWithObject, calledWithArgs);
260
- return functionSpy;
261
- }
262
- function defineWithEmptyAccessors(obj, prop) {
263
- Object.defineProperty(obj, prop, {
264
- get() {
265
- return void 0;
266
- },
267
- set(_value) {
268
- },
269
- configurable: true
310
+ const spy = decorate(functionSpy, {
311
+ calledWith: (...calledWithArgs) => addMethodsToCalledWith(calledWithObject, calledWithArgs),
312
+ mustBeCalledWith: (...calledWithArgs) => addMethodsToCalledWith(mustBeCalledWithObject, calledWithArgs)
270
313
  });
314
+ return exposeAsSpy(spy);
271
315
  }
272
- function accessorSpyFactory(autoSpy, accessorName, accessorType) {
273
- if (accessorType === "setter") {
274
- return vitest.vi.spyOn(autoSpy, accessorName, "set");
275
- }
276
- return vitest.vi.spyOn(autoSpy, accessorName, "get");
277
- }
278
- function createAccessorsSpies(autoSpy, gettersToSpyOn, settersToSpyOn) {
279
- autoSpy.accessorSpies = { getters: {}, setters: {} };
280
- gettersToSpyOn.forEach((getterName) => {
281
- defineWithEmptyAccessors(autoSpy, getterName);
282
- autoSpy.accessorSpies.getters[getterName] = accessorSpyFactory(autoSpy, getterName, "getter");
283
- });
284
- settersToSpyOn.forEach((setterName) => {
285
- if (!Object.prototype.hasOwnProperty.call(autoSpy, setterName)) {
286
- defineWithEmptyAccessors(autoSpy, setterName);
287
- }
288
- autoSpy.accessorSpies.setters[setterName] = accessorSpyFactory(autoSpy, setterName, "setter");
289
- });
316
+ function exposeAsSpy(spy) {
317
+ return spy;
290
318
  }
319
+
320
+ // src/lib/create-spy-from-class.ts
321
+ var EMPTY_CONFIGURATION = {
322
+ methodsToSpyOn: [],
323
+ observablePropsToSpyOn: [],
324
+ settersToSpyOn: [],
325
+ gettersToSpyOn: []
326
+ };
291
327
  function extractMethodsFromObject(obj) {
292
328
  const descriptors = Object.getOwnPropertyDescriptors(obj);
293
- return Object.keys(descriptors).reduce((names, name) => {
294
- if (name !== "constructor" && !descriptors[name].get) {
295
- names.push(name);
296
- }
297
- return names;
298
- }, []);
329
+ return Object.keys(descriptors).filter((name) => name !== "constructor" && !descriptors[name]?.get);
299
330
  }
300
- function getAllMethodNames(obj) {
301
- let methods = [];
302
- while (obj) {
303
- const parentObj = Object.getPrototypeOf(obj);
331
+ function getAllMethodNames(prototype) {
332
+ const methods = /* @__PURE__ */ new Set();
333
+ let current = prototype;
334
+ while (current) {
335
+ const parentObj = Object.getPrototypeOf(current);
304
336
  if (parentObj) {
305
- methods = methods.concat(extractMethodsFromObject(obj));
337
+ extractMethodsFromObject(current).forEach((name) => methods.add(name));
306
338
  }
307
- obj = parentObj;
339
+ current = parentObj;
308
340
  }
309
- return methods;
341
+ return [...methods];
310
342
  }
311
- function createSpyFromClass(ObjectClass, methodsToSpyOnOrConfig) {
312
- const methodNames = getAllMethodNames(ObjectClass.prototype);
313
- let methodsToSpyOn = [];
314
- let observablePropsToSpyOn = [];
315
- let settersToSpyOn = [];
316
- let gettersToSpyOn = [];
317
- if (methodsToSpyOnOrConfig) {
318
- if (Array.isArray(methodsToSpyOnOrConfig)) {
319
- methodsToSpyOn = methodsToSpyOnOrConfig;
320
- } else {
321
- methodsToSpyOn = methodsToSpyOnOrConfig.methodsToSpyOn || [];
322
- observablePropsToSpyOn = methodsToSpyOnOrConfig.observablePropsToSpyOn || [];
323
- settersToSpyOn = methodsToSpyOnOrConfig.settersToSpyOn || [];
324
- gettersToSpyOn = methodsToSpyOnOrConfig.gettersToSpyOn || [];
325
- }
343
+ function resolveConfiguration(methodsToSpyOnOrConfig) {
344
+ if (!methodsToSpyOnOrConfig) {
345
+ return { ...EMPTY_CONFIGURATION };
326
346
  }
327
- if (methodsToSpyOn.length > 0) {
328
- methodNames.push(...methodsToSpyOn);
347
+ if (Array.isArray(methodsToSpyOnOrConfig)) {
348
+ return { ...EMPTY_CONFIGURATION, methodsToSpyOn: methodsToSpyOnOrConfig };
329
349
  }
350
+ return {
351
+ methodsToSpyOn: methodsToSpyOnOrConfig.methodsToSpyOn ?? [],
352
+ observablePropsToSpyOn: methodsToSpyOnOrConfig.observablePropsToSpyOn ?? [],
353
+ settersToSpyOn: methodsToSpyOnOrConfig.settersToSpyOn ?? [],
354
+ gettersToSpyOn: methodsToSpyOnOrConfig.gettersToSpyOn ?? []
355
+ };
356
+ }
357
+ function createSpyFromClass(ObjectClass, methodsToSpyOnOrConfig) {
358
+ const { methodsToSpyOn, observablePropsToSpyOn, settersToSpyOn, gettersToSpyOn } = resolveConfiguration(methodsToSpyOnOrConfig);
359
+ const methodNames = methodsToSpyOn.length > 0 ? methodsToSpyOn : getAllMethodNames(ObjectClass.prototype);
330
360
  const autoSpy = {};
331
361
  observablePropsToSpyOn.forEach((observablePropName) => {
332
362
  autoSpy[observablePropName] = createObservablePropSpy();
@@ -344,7 +374,8 @@ function provideAutoSpy(ObjectClass, methodsToSpyOnOrConfig) {
344
374
  };
345
375
  }
346
376
  function injectSpy(token) {
347
- return testing.TestBed.inject(token);
377
+ const injected = testing.TestBed.inject(token);
378
+ return injected;
348
379
  }
349
380
  function mockReadonlyProp(object, property, value) {
350
381
  Object.defineProperty(object, property, { get: () => value, configurable: true });