prostub 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.d.ts +201 -0
  2. package/index.js +571 -0
  3. package/package.json +15 -0
package/index.d.ts ADDED
@@ -0,0 +1,201 @@
1
+ interface Type<T extends object> {
2
+ new (...args: never[]): T;
3
+ prototype: T;
4
+ }
5
+
6
+ declare const mock$1: unique symbol;
7
+ declare const stub$1: unique symbol;
8
+
9
+ interface InvocationRecord<TArgs extends Array<unknown>, TReturn> {
10
+ args: TArgs;
11
+ returnValue?: TReturn;
12
+ invocationTime: Date;
13
+ exception?: unknown;
14
+ }
15
+ interface AssignmentRecord<TValue> {
16
+ newValue: TValue;
17
+ assignmentTime: Date;
18
+ }
19
+ interface ReadRecord<TValue> {
20
+ value: TValue;
21
+ readTime: Date;
22
+ }
23
+ interface FunctionStub<TThis extends object, TKey extends keyof TThis, TArgs extends Array<unknown>, TReturn> {
24
+ [stub$1]: {
25
+ invoke: (thisArg: TThis, propertyName: TKey, ...args: TArgs) => TReturn;
26
+ type: "function";
27
+ metadata: FunctionStubMetadata<TArgs, TReturn>;
28
+ };
29
+ }
30
+ interface PropertyStubMetadata<TValue> {
31
+ reads: Array<ReadRecord<TValue>>;
32
+ assignments: Array<AssignmentRecord<TValue>>;
33
+ }
34
+ type FunctionStubMetadata<TArgs extends Array<unknown>, TReturn> = {
35
+ calls: Array<InvocationRecord<TArgs, TReturn>>;
36
+ };
37
+ interface PropertyStub<TThis extends object, TKey extends keyof TThis> {
38
+ [stub$1]: {
39
+ type: "property";
40
+ get(thisArg: TThis, propertyName: TKey): TThis[TKey];
41
+ set(thisArg: TThis, propertyName: TKey, newValue: TThis[TKey]): void;
42
+ metadata: {
43
+ reads: Array<ReadRecord<TThis[TKey]>>;
44
+ assignments: Array<AssignmentRecord<TThis[TKey]>>;
45
+ };
46
+ };
47
+ }
48
+ type Stubbable<T extends object> = {
49
+ -readonly [K in keyof T]: Stub<T, K>;
50
+ };
51
+ type Stub<TThis extends object, TKey extends keyof TThis> = TThis[TKey] extends (...args: infer TArgs) => infer TReturn ? FunctionStub<TThis, TKey, TArgs, TReturn> : PropertyStub<TThis, TKey>;
52
+
53
+ declare function mock<const TObject extends object>(clazz: Type<TObject>): TObject & {
54
+ [mock$1]: {
55
+ stubs: { -readonly [TKey in keyof TObject]?: Stub<TObject, TKey>; };
56
+ };
57
+ } & {
58
+ [mock$1]: {
59
+ stubs: TObject & {
60
+ [mock$1]: {
61
+ stubs: { -readonly [TKey_1 in keyof TObject]?: Stub<TObject, TKey_1>; };
62
+ };
63
+ } extends infer T extends object ? { -readonly [TKey in keyof T]?: Stub<T, TKey> | undefined; } : never;
64
+ };
65
+ };
66
+
67
+ declare function spy<const TObject extends object>(realObject: TObject): TObject & {
68
+ [mock$1]: {
69
+ stubs: {
70
+ -readonly [TKey in keyof TObject]?: Stub<TObject, TKey>;
71
+ };
72
+ };
73
+ };
74
+
75
+ declare function stub<const TObject extends object>(mockOrSpy: TObject & {
76
+ [mock$1]: {
77
+ stubs: {
78
+ -readonly [TKey in keyof TObject]?: Stub<TObject, TKey>;
79
+ };
80
+ };
81
+ }): Stubbable<TObject>;
82
+
83
+ declare function answer<const TObject extends object, const TKey extends keyof TObject, const TArgs extends Array<unknown>, const TReturn>(answerFunction: (this: TObject, ...args: TArgs) => TReturn): FunctionStub<TObject, TKey, TArgs, TReturn>;
84
+
85
+ declare function callThrough<const TObject extends object, const TKey extends keyof TObject, const TArgs extends Array<unknown>, const TReturn>(): FunctionStub<TObject, TKey, TArgs, TReturn>;
86
+
87
+ declare function delegateTo<const TObject extends object, const TKey extends keyof TObject, const TArgs extends Array<unknown>, const TReturn>(delegateFunction: (this: TObject, ...args: TArgs) => TReturn): FunctionStub<TObject, TKey, TArgs, TReturn>;
88
+
89
+ declare function returnFixed<const TObject extends object, const TKey extends keyof TObject, const TArgs extends Array<unknown>, const TReturn>(returnValue: TReturn): FunctionStub<TObject, TKey, TArgs, TReturn>;
90
+
91
+ declare function returnSerial<const TObject extends object, const TKey extends keyof TObject, const TArgs extends Array<unknown>, const TReturn>(...values: Array<TReturn>): FunctionStub<TObject, TKey, TArgs, TReturn>;
92
+
93
+ declare function noop<const TObject extends object, const TKey extends keyof TObject, const TArgs extends Array<unknown>>(): FunctionStub<TObject, TKey, TArgs, void>;
94
+
95
+ declare function throwOnCall<const TObject extends object, const TKey extends keyof TObject, const TArgs extends Array<unknown>>(errorFactory: () => Error): FunctionStub<TObject, TKey, TArgs, never>;
96
+
97
+ type ArgumentPredicate<TArgs extends Array<unknown>> = TArgs extends [infer TFirst, ...infer TRest] ? [
98
+ ((arg: TFirst) => boolean),
99
+ ...ArgumentPredicate<TRest>
100
+ ] : TArgs extends [infer TSingle] ? [
101
+ (arg: TSingle) => boolean
102
+ ] : TArgs extends [] ? [
103
+ ] : never;
104
+
105
+ declare class OngoingFunctionStub<const TThis extends object, const TKey extends keyof TThis, const TArgs extends Array<unknown>, const TReturn> implements FunctionStub<TThis, TKey, TArgs, TReturn> {
106
+ #private;
107
+ get [stub$1](): {
108
+ invoke: (thisArg: TThis, propertyName: TKey, ...args: TArgs) => TReturn;
109
+ type: "function";
110
+ metadata: FunctionStubMetadata<TArgs, TReturn>;
111
+ };
112
+ constructor(argumentPredicates: ArgumentPredicate<TArgs>, stub: FunctionStub<TThis, TKey, TArgs, TReturn>, parentStub?: OngoingFunctionStub<TThis, TKey, TArgs, TReturn>);
113
+ when(...argumentPredicates: ArgumentPredicate<TArgs>): OngoingFunctionStubBuilder<TThis, TKey, TArgs, TReturn>;
114
+ }
115
+ declare class OngoingFunctionStubBuilder<const TThis extends object, const TKey extends keyof TThis, const TArgs extends Array<unknown>, const TReturn> implements FunctionStub<TThis, TKey, TArgs, TReturn> {
116
+ #private;
117
+ get [stub$1](): {
118
+ invoke: (_thisArg: TThis, _propertyName: TKey, ..._args: TArgs) => never;
119
+ type: "function";
120
+ metadata: {
121
+ calls: never[];
122
+ };
123
+ };
124
+ constructor(argumentPredicates: ArgumentPredicate<TArgs>, parentStub?: OngoingFunctionStub<TThis, TKey, TArgs, TReturn>);
125
+ then(stub: FunctionStub<TThis, TKey, TArgs, TReturn>): OngoingFunctionStub<TThis, TKey, TArgs, TReturn>;
126
+ }
127
+ declare function when<const TObject extends object, const TKey extends keyof TObject, const TArgs extends Array<unknown>, const TReturn>(...argumentPredicates: ArgumentPredicate<TArgs>): OngoingFunctionStubBuilder<TObject, TKey, TArgs, TReturn>;
128
+
129
+ declare function defaultValue<const TObject extends object, const TKey extends keyof TObject>(value: TObject[TKey]): PropertyStub<TObject, TKey>;
130
+
131
+ declare function fixedValue<const TThis extends object, const TKey extends keyof TThis>(value: TThis[TKey]): PropertyStub<TThis, TKey>;
132
+
133
+ declare function trackValue<const TObject extends object, const TKey extends keyof TObject>(): PropertyStub<TObject, TKey>;
134
+
135
+ declare class AssignmentVerification<const TValue> {
136
+ #private;
137
+ constructor(assignmentRecord: AssignmentRecord<TValue>);
138
+ wasBefore(otherAssignment: AssignmentVerification<TValue>): void;
139
+ wasAfter(otherAssignment: AssignmentVerification<TValue>): void;
140
+ hasValue(validator: (result: TValue) => boolean): void;
141
+ }
142
+
143
+ declare class ReadVerification<const TValue> {
144
+ #private;
145
+ constructor(read: ReadRecord<TValue>);
146
+ wasBefore(otherRead: ReadVerification<TValue>): void;
147
+ wasAfter(otherRead: ReadVerification<TValue>): void;
148
+ hadValue(validator: (result: TValue) => boolean): void;
149
+ }
150
+
151
+ declare class PropertyVerification<const TValue> {
152
+ #private;
153
+ constructor(propertyStubMetadata: PropertyStubMetadata<TValue>);
154
+ wasReadNTimes(expectedGetCallCount?: number): void;
155
+ wasRead(): void;
156
+ wasNotAssigned(): void;
157
+ wasNotRead(): void;
158
+ wasAssignedNTimes(expectedSetCallCount?: number): void;
159
+ wasAssigned(): void;
160
+ nthAssignment(index: number): AssignmentVerification<TValue>;
161
+ firstAssignment(): AssignmentVerification<TValue>;
162
+ lastAssignment(): AssignmentVerification<TValue>;
163
+ firstRead(): ReadVerification<TValue>;
164
+ lastRead(): ReadVerification<TValue>;
165
+ nthRead(index: number): ReadVerification<TValue>;
166
+ }
167
+
168
+ declare class InvocationVerification<const TArgs extends Array<unknown>, TReturn> {
169
+ #private;
170
+ constructor(call: InvocationRecord<TArgs, TReturn>);
171
+ wasBefore(otherInvocation: InvocationVerification<TArgs, TReturn>): void;
172
+ wasAfter(otherInvocation: InvocationVerification<TArgs, TReturn>): void;
173
+ returned(validator: (result: TReturn) => boolean): void;
174
+ threw(validator: (error: unknown) => boolean): void;
175
+ }
176
+
177
+ declare class FunctionVerification<const TArgs extends Array<unknown>, TReturn> {
178
+ #private;
179
+ constructor(functionStubMetadata: FunctionStubMetadata<TArgs, TReturn>);
180
+ wasCalled(): void;
181
+ wasNotCalled(): void;
182
+ wasCalledTimes(expectedCallCount: number): void;
183
+ nthInvocation(invocationIndex: number): InvocationVerification<TArgs, TReturn>;
184
+ firstInvocation(): InvocationVerification<TArgs, TReturn>;
185
+ lastInvocation(): InvocationVerification<TArgs, TReturn>;
186
+ invocationMatching(predicate: (...args: TArgs) => boolean): InvocationVerification<TArgs, TReturn>;
187
+ }
188
+
189
+ type Verifiable<T> = {
190
+ readonly [TKey in keyof T]: T[TKey] extends (...args: infer TArgs) => infer TReturn ? FunctionVerification<TArgs, TReturn> : PropertyVerification<T[TKey]>;
191
+ };
192
+
193
+ declare function verify<const TObject extends object>(mockOrSpy: TObject & {
194
+ [mock$1]: {
195
+ stubs: {
196
+ -readonly [TKey in keyof TObject]?: Stub<TObject, TKey>;
197
+ };
198
+ };
199
+ }): Verifiable<TObject>;
200
+
201
+ export { answer, callThrough, defaultValue, delegateTo, fixedValue, mock, noop, returnFixed, returnSerial, spy, stub, throwOnCall, trackValue, verify, when };
package/index.js ADDED
@@ -0,0 +1,571 @@
1
+ const mock$1 = Symbol("mock");
2
+ const stub$1 = Symbol("stub");
3
+
4
+ function spy(realObject) {
5
+ const stubs = {};
6
+ return new Proxy(realObject, {
7
+ set(target, propertyName, newValue, _receiver) {
8
+ if (!stubs.hasOwnProperty(propertyName)) {
9
+ throw new Error(`No stub defined for property "${String(propertyName)}".`);
10
+ }
11
+ const stubMetadata = stubs[propertyName][stub$1];
12
+ if (stubMetadata.type !== "property") {
13
+ throw new Error(`Cannot set value of non-property stub "${String(propertyName)}".`);
14
+ }
15
+ stubMetadata.set(target, propertyName, newValue); // Cast to any is needed as TObject[TKey] does not work due to TS limitations
16
+ return true;
17
+ },
18
+ get(target, propertyName, _receiver) {
19
+ if (propertyName === mock$1) {
20
+ return { stubs };
21
+ }
22
+ if (!stubs.hasOwnProperty(propertyName)) {
23
+ throw new Error(`No stub defined for property "${String(propertyName)}".`);
24
+ }
25
+ const stubMetadata = stubs[propertyName][stub$1];
26
+ if (stubMetadata.type === "function") {
27
+ const returnValue = function (...args) {
28
+ return stubMetadata.invoke(realObject, propertyName, ...args);
29
+ };
30
+ return returnValue;
31
+ }
32
+ else {
33
+ return stubMetadata.get(target, propertyName);
34
+ }
35
+ },
36
+ has(target, propertyName) {
37
+ return propertyName === mock$1 || Reflect.has(target, propertyName);
38
+ }
39
+ });
40
+ }
41
+
42
+ function mock(clazz) {
43
+ const instance = Object.create(clazz.prototype);
44
+ return spy(instance);
45
+ }
46
+
47
+ function stub(mockOrSpy) {
48
+ const stubs = mockOrSpy[mock$1].stubs;
49
+ return new Proxy({}, {
50
+ set(_target, propertyName, newValue, _receiver) {
51
+ if (typeof newValue !== "object" || newValue === null || !Reflect.has(newValue, stub$1)) {
52
+ throw new Error(`Stub value for property "${String(propertyName)}" must be a stub created by a stub factory function.`);
53
+ }
54
+ if (stubs.hasOwnProperty(propertyName)) {
55
+ throw new Error(`Stub for property "${String(propertyName)}" is already defined.`);
56
+ }
57
+ stubs[propertyName] = newValue;
58
+ return true;
59
+ },
60
+ get(_target, propertyName, _receiver) {
61
+ throw new Error(`Cannot get stub for property "${String(propertyName)}". Stubs can only be set, not retrieved.`);
62
+ }
63
+ });
64
+ }
65
+
66
+ function runCatching(fn, thisArg, ...args) {
67
+ try {
68
+ const result = fn.apply(thisArg, args);
69
+ return { result: "success", returnValue: result };
70
+ }
71
+ catch (error) {
72
+ return { result: "error", error };
73
+ }
74
+ }
75
+
76
+ function answer(answerFunction) {
77
+ const metadata = {
78
+ calls: []
79
+ };
80
+ return {
81
+ [stub$1]: {
82
+ invoke: function (thisArg, _propertyName, ...args) {
83
+ const result = runCatching(answerFunction, thisArg, ...args);
84
+ metadata.calls.push({
85
+ args,
86
+ ...(result.result === "success" ? { returnValue: result.returnValue } : { exception: result.error }),
87
+ invocationTime: new Date()
88
+ });
89
+ if (result.result === "error") {
90
+ throw result.error;
91
+ }
92
+ return result.returnValue;
93
+ },
94
+ type: "function",
95
+ metadata,
96
+ }
97
+ };
98
+ }
99
+
100
+ function callThrough() {
101
+ const metadata = {
102
+ calls: []
103
+ };
104
+ return {
105
+ [stub$1]: {
106
+ invoke: function (thisArg, propertyName, ...args) {
107
+ const result = runCatching((...args) => thisArg[propertyName](...args), thisArg, ...args);
108
+ metadata.calls.push({
109
+ args,
110
+ ...(result.result === "success" ? { returnValue: result.returnValue } : { exception: result.error }),
111
+ invocationTime: new Date()
112
+ });
113
+ if (result.result === "error") {
114
+ throw result.error;
115
+ }
116
+ return result.returnValue;
117
+ },
118
+ type: "function",
119
+ metadata,
120
+ }
121
+ };
122
+ }
123
+
124
+ function delegateTo(delegateFunction) {
125
+ const metadata = {
126
+ calls: []
127
+ };
128
+ return {
129
+ [stub$1]: {
130
+ invoke: function (thisArg, _propertyName, ...args) {
131
+ metadata.calls.push({
132
+ args,
133
+ returnValue: delegateFunction.apply(thisArg, args),
134
+ invocationTime: new Date()
135
+ });
136
+ return delegateFunction.apply(thisArg, args);
137
+ },
138
+ type: "function",
139
+ metadata,
140
+ }
141
+ };
142
+ }
143
+
144
+ function returnFixed(returnValue) {
145
+ const metadata = {
146
+ calls: []
147
+ };
148
+ return {
149
+ [stub$1]: {
150
+ invoke: function (_thisArg, _propertyName, ...args) {
151
+ metadata.calls.push({
152
+ args,
153
+ returnValue,
154
+ invocationTime: new Date()
155
+ });
156
+ return returnValue;
157
+ },
158
+ type: "function",
159
+ metadata,
160
+ }
161
+ };
162
+ }
163
+
164
+ function returnSerial(...values) {
165
+ const metadata = {
166
+ calls: []
167
+ };
168
+ return {
169
+ [stub$1]: {
170
+ type: "function",
171
+ metadata,
172
+ invoke: function (_thisArg, _propertyName, ...args) {
173
+ const index = metadata.calls.length;
174
+ if (index === values.length) {
175
+ const error = new Error(`No more return values left in returnSerial stub (called ${index + 1} times, but only ${values.length} values were provided)`);
176
+ metadata.calls.push({
177
+ args,
178
+ invocationTime: new Date(),
179
+ exception: error,
180
+ });
181
+ throw error;
182
+ }
183
+ const returnValue = values[index];
184
+ metadata.calls.push({
185
+ args,
186
+ returnValue,
187
+ invocationTime: new Date()
188
+ });
189
+ return returnValue;
190
+ }
191
+ }
192
+ };
193
+ }
194
+
195
+ function noop() {
196
+ const metadata = {
197
+ calls: []
198
+ };
199
+ return {
200
+ [stub$1]: {
201
+ invoke: function (_thisArg, _propertyName, ...args) {
202
+ metadata.calls.push({
203
+ args,
204
+ invocationTime: new Date()
205
+ });
206
+ },
207
+ type: "function",
208
+ metadata,
209
+ }
210
+ };
211
+ }
212
+
213
+ function throwOnCall(errorFactory) {
214
+ const metadata = {
215
+ calls: []
216
+ };
217
+ return {
218
+ [stub$1]: {
219
+ invoke: function (_thisArg, _propertyName, ...args) {
220
+ const error = errorFactory();
221
+ metadata.calls.push({
222
+ args,
223
+ exception: error,
224
+ invocationTime: new Date()
225
+ });
226
+ throw error;
227
+ },
228
+ type: "function",
229
+ metadata,
230
+ }
231
+ };
232
+ }
233
+
234
+ class OngoingFunctionStub {
235
+ get [stub$1]() {
236
+ return {
237
+ invoke: (thisArg, propertyName, ...args) => this.#invoke(thisArg, propertyName, ...args),
238
+ type: "function",
239
+ metadata: {
240
+ calls: [
241
+ ...(this.#parentStub ? this.#parentStub[stub$1].metadata.calls : []),
242
+ ...this.#stub[stub$1].metadata.calls
243
+ ]
244
+ }
245
+ };
246
+ }
247
+ #argumentPredicates;
248
+ #stub;
249
+ #parentStub;
250
+ constructor(argumentPredicates, stub, parentStub) {
251
+ this.#argumentPredicates = argumentPredicates;
252
+ this.#stub = stub;
253
+ this.#parentStub = parentStub;
254
+ }
255
+ when(...argumentPredicates) {
256
+ return new OngoingFunctionStubBuilder(argumentPredicates, this);
257
+ }
258
+ #invoke(thisArg, propertyName, ...args) {
259
+ if (this.#parentStub && this.#parentStub.#matchesArguments(args)) {
260
+ return this.#parentStub.#invoke(thisArg, propertyName, ...args);
261
+ }
262
+ else if (this.#matchesArguments(args)) {
263
+ return this.#stub[stub$1].invoke(thisArg, propertyName, ...args);
264
+ }
265
+ else {
266
+ throw new Error("No matching stub found for the given arguments.");
267
+ }
268
+ }
269
+ #matchesArguments(args) {
270
+ return args.every((arg, index) => this.#argumentPredicates[index](arg));
271
+ }
272
+ }
273
+ class OngoingFunctionStubBuilder {
274
+ get [stub$1]() {
275
+ return {
276
+ invoke: (_thisArg, _propertyName, ..._args) => {
277
+ throw new Error("Cannot invoke a stub builder. Please complete the stub definition using 'then' before invoking.");
278
+ },
279
+ type: "function",
280
+ metadata: {
281
+ calls: []
282
+ }
283
+ };
284
+ }
285
+ #argumentPredicates;
286
+ #parentStub;
287
+ constructor(argumentPredicates, parentStub) {
288
+ this.#argumentPredicates = argumentPredicates;
289
+ this.#parentStub = parentStub;
290
+ }
291
+ then(stub) {
292
+ return new OngoingFunctionStub(this.#argumentPredicates, stub, this.#parentStub);
293
+ }
294
+ }
295
+ function when(...argumentPredicates) {
296
+ return new OngoingFunctionStubBuilder(argumentPredicates);
297
+ }
298
+
299
+ function defaultValue(value) {
300
+ const metadata = {
301
+ reads: [],
302
+ assignments: []
303
+ };
304
+ let currentValue = value;
305
+ return {
306
+ [stub$1]: {
307
+ type: "property",
308
+ get(_thisArg, _propertyName) {
309
+ metadata.reads.push({
310
+ value: currentValue,
311
+ readTime: new Date()
312
+ });
313
+ return currentValue;
314
+ },
315
+ set(_thisArg, _propertyName, newValue) {
316
+ metadata.assignments.push({
317
+ newValue,
318
+ assignmentTime: new Date()
319
+ });
320
+ currentValue = newValue;
321
+ },
322
+ metadata
323
+ }
324
+ };
325
+ }
326
+
327
+ function fixedValue(value) {
328
+ const metadata = {
329
+ reads: [],
330
+ assignments: []
331
+ };
332
+ return {
333
+ [stub$1]: {
334
+ type: "property",
335
+ get(_thisArg, _propertyName) {
336
+ metadata.reads.push({
337
+ value,
338
+ readTime: new Date()
339
+ });
340
+ return value;
341
+ },
342
+ set(_thisArg, _propertyName, _newValue) {
343
+ metadata.assignments.push({
344
+ newValue: _newValue,
345
+ assignmentTime: new Date()
346
+ });
347
+ throw new Error("Cannot set value of a fixedValue property stub.");
348
+ },
349
+ metadata
350
+ }
351
+ };
352
+ }
353
+
354
+ function trackValue() {
355
+ const metadata = {
356
+ reads: [],
357
+ assignments: [],
358
+ };
359
+ return {
360
+ [stub$1]: {
361
+ type: "property",
362
+ get(thisArg, propertyName) {
363
+ const currentValue = thisArg[propertyName];
364
+ metadata.reads.push({
365
+ value: currentValue,
366
+ readTime: new Date(),
367
+ });
368
+ return currentValue;
369
+ },
370
+ set(thisArg, propertyName, newValue) {
371
+ thisArg[propertyName] = newValue;
372
+ metadata.assignments.push({
373
+ newValue,
374
+ assignmentTime: new Date(),
375
+ });
376
+ },
377
+ metadata
378
+ }
379
+ };
380
+ }
381
+
382
+ class AssignmentVerification {
383
+ #assignmentRecord;
384
+ constructor(assignmentRecord) {
385
+ this.#assignmentRecord = assignmentRecord;
386
+ }
387
+ wasBefore(otherAssignment) {
388
+ if (this.#assignmentRecord.assignmentTime >= otherAssignment.#assignmentRecord.assignmentTime) {
389
+ throw new Error(`Expected assignment at ${this.#assignmentRecord.assignmentTime.toISOString()} to be before assignment at ${otherAssignment.#assignmentRecord.assignmentTime.toISOString()}, but it was not.`);
390
+ }
391
+ }
392
+ wasAfter(otherAssignment) {
393
+ if (this.#assignmentRecord.assignmentTime <= otherAssignment.#assignmentRecord.assignmentTime) {
394
+ throw new Error(`Expected assignment at ${this.#assignmentRecord.assignmentTime.toISOString()} to be after assignment at ${otherAssignment.#assignmentRecord.assignmentTime.toISOString()}, but it was not.`);
395
+ }
396
+ }
397
+ hasValue(validator) {
398
+ if (!validator(this.#assignmentRecord.newValue)) {
399
+ throw new Error(`The assigned value did not satisfy the provided validator.`);
400
+ }
401
+ }
402
+ }
403
+
404
+ class ReadVerification {
405
+ #readRecord;
406
+ constructor(read) {
407
+ this.#readRecord = read;
408
+ }
409
+ wasBefore(otherRead) {
410
+ if (this.#readRecord.readTime >= otherRead.#readRecord.readTime) {
411
+ throw new Error(`Expected read at ${this.#readRecord.readTime.toISOString()} to be before read at ${otherRead.#readRecord.readTime.toISOString()}, but it was not.`);
412
+ }
413
+ }
414
+ wasAfter(otherRead) {
415
+ if (this.#readRecord.readTime <= otherRead.#readRecord.readTime) {
416
+ throw new Error(`Expected read at ${this.#readRecord.readTime.toISOString()} to be after read at ${otherRead.#readRecord.readTime.toISOString()}, but it was not.`);
417
+ }
418
+ }
419
+ hadValue(validator) {
420
+ if (!validator(this.#readRecord.value)) {
421
+ throw new Error(`The read value did not satisfy the provided validator.`);
422
+ }
423
+ }
424
+ }
425
+
426
+ class PropertyVerification {
427
+ #propertyStubMetadata;
428
+ constructor(propertyStubMetadata) {
429
+ this.#propertyStubMetadata = propertyStubMetadata;
430
+ }
431
+ wasReadNTimes(expectedGetCallCount = 1) {
432
+ const actualGetCallCount = this.#propertyStubMetadata.reads.length;
433
+ if (actualGetCallCount !== expectedGetCallCount) {
434
+ throw new Error(`Expected property getter to be called ${expectedGetCallCount} times, but was called ${actualGetCallCount} times.`);
435
+ }
436
+ }
437
+ wasRead() {
438
+ this.wasReadNTimes(1);
439
+ }
440
+ wasNotAssigned() {
441
+ this.wasAssignedNTimes(0);
442
+ }
443
+ wasNotRead() {
444
+ this.wasReadNTimes(0);
445
+ }
446
+ wasAssignedNTimes(expectedSetCallCount = 1) {
447
+ const actualSetCallCount = this.#propertyStubMetadata.assignments.length;
448
+ if (actualSetCallCount !== expectedSetCallCount) {
449
+ throw new Error(`Expected property setter to be called ${expectedSetCallCount} times, but was called ${actualSetCallCount} times.`);
450
+ }
451
+ }
452
+ wasAssigned() {
453
+ this.wasAssignedNTimes(1);
454
+ }
455
+ nthAssignment(index) {
456
+ if (index < 0 || index >= this.#propertyStubMetadata.assignments.length) {
457
+ throw new Error(`Assignment index ${index} is out of bounds. There are only ${this.#propertyStubMetadata.assignments.length} assignments.`);
458
+ }
459
+ return new AssignmentVerification(this.#propertyStubMetadata.assignments[index]);
460
+ }
461
+ firstAssignment() {
462
+ return this.nthAssignment(0);
463
+ }
464
+ lastAssignment() {
465
+ return this.nthAssignment(this.#propertyStubMetadata.assignments.length - 1);
466
+ }
467
+ firstRead() {
468
+ return this.nthRead(0);
469
+ }
470
+ lastRead() {
471
+ return this.nthRead(this.#propertyStubMetadata.reads.length - 1);
472
+ }
473
+ nthRead(index) {
474
+ if (index < 0 || index >= this.#propertyStubMetadata.reads.length) {
475
+ throw new Error(`Read index ${index} is out of bounds. There are only ${this.#propertyStubMetadata.reads.length} reads.`);
476
+ }
477
+ return new ReadVerification(this.#propertyStubMetadata.reads[index]);
478
+ }
479
+ }
480
+
481
+ class InvocationVerification {
482
+ #call;
483
+ constructor(call) {
484
+ this.#call = call;
485
+ }
486
+ wasBefore(otherInvocation) {
487
+ if (this.#call.invocationTime >= otherInvocation.#call.invocationTime) {
488
+ throw new Error(`Expected invocation at ${this.#call.invocationTime.toISOString()} to be before invocation at ${otherInvocation.#call.invocationTime.toISOString()}, but it was not.`);
489
+ }
490
+ }
491
+ wasAfter(otherInvocation) {
492
+ if (this.#call.invocationTime <= otherInvocation.#call.invocationTime) {
493
+ throw new Error(`Expected invocation at ${this.#call.invocationTime.toISOString()} to be after invocation at ${otherInvocation.#call.invocationTime.toISOString()}, but it was not.`);
494
+ }
495
+ }
496
+ returned(validator) {
497
+ if (this.#call.exception) {
498
+ throw new Error(`Expected invocation to return a value, but it threw an exception.`);
499
+ }
500
+ if (!validator(this.#call.returnValue)) {
501
+ throw new Error(`The returned value did not satisfy the provided validator.`);
502
+ }
503
+ }
504
+ threw(validator) {
505
+ if (!this.#call.exception) {
506
+ throw new Error(`Expected invocation to throw an exception, but it did not.`);
507
+ }
508
+ if (!validator(this.#call.exception)) {
509
+ throw new Error(`The thrown exception did not satisfy the provided validator.`);
510
+ }
511
+ }
512
+ }
513
+
514
+ class FunctionVerification {
515
+ #functionStubMetadata;
516
+ constructor(functionStubMetadata) {
517
+ this.#functionStubMetadata = functionStubMetadata;
518
+ }
519
+ wasCalled() {
520
+ this.wasCalledTimes(1);
521
+ }
522
+ wasNotCalled() {
523
+ this.wasCalledTimes(0);
524
+ }
525
+ wasCalledTimes(expectedCallCount) {
526
+ const actualCallCount = this.#functionStubMetadata.calls.length;
527
+ if (actualCallCount !== expectedCallCount) {
528
+ throw new Error(`Expected function to be called ${expectedCallCount} times, but was called ${actualCallCount} times.`);
529
+ }
530
+ }
531
+ nthInvocation(invocationIndex) {
532
+ if (invocationIndex < 0 || invocationIndex >= this.#functionStubMetadata.calls.length) {
533
+ throw new Error(`Invocation index ${invocationIndex} is out of bounds. There are only ${this.#functionStubMetadata.calls.length} invocations.`);
534
+ }
535
+ const call = this.#functionStubMetadata.calls[invocationIndex];
536
+ return new InvocationVerification(call);
537
+ }
538
+ firstInvocation() {
539
+ return this.nthInvocation(0);
540
+ }
541
+ lastInvocation() {
542
+ return this.nthInvocation(this.#functionStubMetadata.calls.length - 1);
543
+ }
544
+ invocationMatching(predicate) {
545
+ const call = this.#functionStubMetadata.calls.find(call => predicate(...call.args));
546
+ if (!call) {
547
+ throw new Error(`No invocation found matching the provided predicate.`);
548
+ }
549
+ return new InvocationVerification(call);
550
+ }
551
+ }
552
+
553
+ function verify(mockOrSpy) {
554
+ return new Proxy({}, {
555
+ get(_target, propertyName, _receiver) {
556
+ if (!mockOrSpy[mock$1].stubs.hasOwnProperty(propertyName)) {
557
+ throw new Error(`No stub defined for property "${String(propertyName)}".`);
558
+ }
559
+ const stub = mockOrSpy[mock$1].stubs[propertyName];
560
+ const stubMetadata = stub[stub$1];
561
+ if (stubMetadata.type === "function") {
562
+ return new FunctionVerification(stubMetadata.metadata);
563
+ }
564
+ else {
565
+ return new PropertyVerification(stubMetadata.metadata);
566
+ }
567
+ }
568
+ });
569
+ }
570
+
571
+ export { answer, callThrough, defaultValue, delegateTo, fixedValue, mock, noop, returnFixed, returnSerial, spy, stub, throwOnCall, trackValue, verify, when };
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "prostub",
3
+ "version": "1.0.0",
4
+ "description": "A TypeScript library for creating mocks.",
5
+ "license": "MIT",
6
+ "peerDependencies": {
7
+ "tslib": "^2.8.1"
8
+ },
9
+ "type": "module",
10
+ "main": "index.mjs",
11
+ "types": "index.d.ts",
12
+ "scripts": {
13
+ "test": "echo \"If you read this, all tests succeeded in the pipeline. Refer to the source-code repository to verify.\""
14
+ }
15
+ }