track-cli 4.0.3 → 4.1.0-rc1
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/esm/_dnt.shims.d.ts +1 -1
- package/esm/_dnt.test_shims.d.ts +20 -0
- package/esm/_dnt.test_shims.js +77 -0
- package/esm/deps/deno.land/std@0.195.0/_util/diff.d.ts +26 -0
- package/esm/deps/deno.land/std@0.195.0/_util/diff.js +311 -0
- package/esm/deps/deno.land/std@0.195.0/assert/_constants.d.ts +1 -0
- package/esm/deps/deno.land/std@0.195.0/assert/_constants.js +2 -0
- package/esm/deps/deno.land/std@0.195.0/assert/_format.d.ts +1 -0
- package/esm/deps/deno.land/std@0.195.0/assert/_format.js +23 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_almost_equals.d.ts +18 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_almost_equals.js +32 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_array_includes.d.ts +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_array_includes.js +38 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_equals.d.ts +17 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_equals.js +45 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_exists.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_exists.js +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_false.d.ts +4 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_false.js +7 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_instance_of.d.ts +8 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_instance_of.js +38 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_is_error.d.ts +7 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_is_error.js +26 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_match.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_match.js +13 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_equals.d.ts +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_equals.js +37 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_instance_of.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_instance_of.js +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_match.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_match.js +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_strict_equals.d.ts +11 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_strict_equals.js +20 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_object_match.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_object_match.js +78 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_rejects.d.ts +64 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_rejects.js +50 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_strict_equals.d.ts +23 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_strict_equals.js +60 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_string_includes.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_string_includes.js +13 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_throws.d.ts +54 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_throws.js +44 -0
- package/esm/deps/deno.land/std@0.195.0/assert/equal.d.ts +6 -0
- package/esm/deps/deno.land/std@0.195.0/assert/equal.js +102 -0
- package/esm/deps/deno.land/std@0.195.0/assert/fail.d.ts +4 -0
- package/esm/deps/deno.land/std@0.195.0/assert/fail.js +9 -0
- package/esm/deps/deno.land/std@0.195.0/assert/mod.d.ts +32 -0
- package/esm/deps/deno.land/std@0.195.0/assert/mod.js +33 -0
- package/esm/deps/deno.land/std@0.195.0/assert/unimplemented.d.ts +2 -0
- package/esm/deps/deno.land/std@0.195.0/assert/unimplemented.js +7 -0
- package/esm/deps/deno.land/std@0.195.0/assert/unreachable.d.ts +2 -0
- package/esm/deps/deno.land/std@0.195.0/assert/unreachable.js +6 -0
- package/esm/deps/deno.land/std@0.195.0/testing/_test_suite.d.ts +70 -0
- package/esm/deps/deno.land/std@0.195.0/testing/_test_suite.js +321 -0
- package/esm/deps/deno.land/std@0.195.0/testing/asserts.d.ts +329 -0
- package/esm/deps/deno.land/std@0.195.0/testing/asserts.js +330 -0
- package/esm/deps/deno.land/std@0.195.0/testing/bdd.d.ts +440 -0
- package/esm/deps/deno.land/std@0.195.0/testing/bdd.js +215 -0
- package/esm/deps/deno.land/std@0.195.0/testing/mock.d.ts +110 -0
- package/esm/deps/deno.land/std@0.195.0/testing/mock.js +746 -0
- package/esm/src/action/clone.js +4 -3
- package/esm/src/action/frontend-template-switch.d.ts +1 -0
- package/esm/src/action/frontend-template-switch.js +56 -0
- package/esm/src/action/frontend-template.d.ts +2 -0
- package/esm/src/action/frontend-template.js +12 -0
- package/esm/src/main.js +9 -2
- package/esm/src/meta.d.ts +1 -1
- package/esm/src/meta.js +108 -25
- package/esm/src/orca/client.js +1 -1
- package/esm/src/shared/config.d.ts +1 -0
- package/esm/src/shared/file.d.ts +1 -0
- package/esm/src/shared/file.js +32 -0
- package/esm/src/shared/mod.d.ts +1 -1
- package/esm/src/shared/mod.js +10 -2
- package/esm/src/shared/types.d.ts +15 -0
- package/esm/src/shared/types.js +1 -0
- package/esm/src/track/client.d.ts +4 -1
- package/esm/src/track/test.d.ts +5 -2
- package/esm/src/track/test.js +18 -2
- package/esm/src/track/training.d.ts +4 -1
- package/esm/src/track/training.js +9 -0
- package/esm/test/shared/config_test.d.ts +1 -0
- package/esm/test/shared/config_test.js +57 -0
- package/esm/test/shared/file_test.d.ts +1 -0
- package/esm/test/shared/file_test.js +265 -0
- package/esm/test/shared/mod_test.d.ts +1 -0
- package/esm/test/shared/mod_test.js +353 -0
- package/package.json +2 -1
- package/test_runner.js +186 -0
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
2
|
+
/** A mocking and spying library.
|
|
3
|
+
*
|
|
4
|
+
* Test spies are function stand-ins that are used to assert if a function's
|
|
5
|
+
* internal behavior matches expectations. Test spies on methods keep the original
|
|
6
|
+
* behavior but allow you to test how the method is called and what it returns.
|
|
7
|
+
* Test stubs are an extension of test spies that also replaces the original
|
|
8
|
+
* methods behavior.
|
|
9
|
+
*
|
|
10
|
+
* ## Spying
|
|
11
|
+
*
|
|
12
|
+
* Say we have two functions, `square` and `multiply`, if we want to assert that
|
|
13
|
+
* the `multiply` function is called during execution of the `square` function we
|
|
14
|
+
* need a way to spy on the `multiply` function. There are a few ways to achieve
|
|
15
|
+
* this with Spies, one is to have the `square` function take the `multiply`
|
|
16
|
+
* multiply as a parameter.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* // https://deno.land/std@$STD_VERSION/testing/mock_examples/parameter_injection.ts
|
|
20
|
+
* export function multiply(a: number, b: number): number {
|
|
21
|
+
* return a * b;
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* export function square(
|
|
25
|
+
* multiplyFn: (a: number, b: number) => number,
|
|
26
|
+
* value: number,
|
|
27
|
+
* ): number {
|
|
28
|
+
* return multiplyFn(value, value);
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* This way, we can call `square(multiply, value)` in the application code or wrap
|
|
33
|
+
* a spy function around the `multiply` function and call
|
|
34
|
+
* `square(multiplySpy, value)` in the testing code.
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* // https://deno.land/std@$STD_VERSION/testing/mock_examples/parameter_injection_test.ts
|
|
38
|
+
* import {
|
|
39
|
+
* assertSpyCall,
|
|
40
|
+
* assertSpyCalls,
|
|
41
|
+
* spy,
|
|
42
|
+
* } from "https://deno.land/std@$STD_VERSION/testing/mock.ts";
|
|
43
|
+
* import { assertEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_equals.ts";
|
|
44
|
+
* import {
|
|
45
|
+
* multiply,
|
|
46
|
+
* square,
|
|
47
|
+
* } from "https://deno.land/std@$STD_VERSION/testing/mock_examples/parameter_injection.ts";
|
|
48
|
+
*
|
|
49
|
+
* Deno.test("square calls multiply and returns results", () => {
|
|
50
|
+
* const multiplySpy = spy(multiply);
|
|
51
|
+
*
|
|
52
|
+
* assertEquals(square(multiplySpy, 5), 25);
|
|
53
|
+
*
|
|
54
|
+
* // asserts that multiplySpy was called at least once and details about the first call.
|
|
55
|
+
* assertSpyCall(multiplySpy, 0, {
|
|
56
|
+
* args: [5, 5],
|
|
57
|
+
* returned: 25,
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* // asserts that multiplySpy was only called once.
|
|
61
|
+
* assertSpyCalls(multiplySpy, 1);
|
|
62
|
+
* });
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* If you prefer not adding additional parameters for testing purposes only, you
|
|
66
|
+
* can use spy to wrap a method on an object instead. In the following example, the
|
|
67
|
+
* exported `_internals` object has the `multiply` function we want to call as a
|
|
68
|
+
* method and the `square` function calls `_internals.multiply` instead of
|
|
69
|
+
* `multiply`.
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* // https://deno.land/std@$STD_VERSION/testing/mock_examples/internals_injection.ts
|
|
73
|
+
* export function multiply(a: number, b: number): number {
|
|
74
|
+
* return a * b;
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
* export function square(value: number): number {
|
|
78
|
+
* return _internals.multiply(value, value);
|
|
79
|
+
* }
|
|
80
|
+
*
|
|
81
|
+
* export const _internals = { multiply };
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* This way, we can call `square(value)` in both the application code and testing
|
|
85
|
+
* code. Then spy on the `multiply` method on the `_internals` object in the
|
|
86
|
+
* testing code to be able to spy on how the `square` function calls the `multiply`
|
|
87
|
+
* function.
|
|
88
|
+
*
|
|
89
|
+
* ```ts
|
|
90
|
+
* // https://deno.land/std@$STD_VERSION/testing/mock_examples/internals_injection_test.ts
|
|
91
|
+
* import {
|
|
92
|
+
* assertSpyCall,
|
|
93
|
+
* assertSpyCalls,
|
|
94
|
+
* spy,
|
|
95
|
+
* } from "https://deno.land/std@$STD_VERSION/testing/mock.ts";
|
|
96
|
+
* import { assertEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_equals.ts";
|
|
97
|
+
* import {
|
|
98
|
+
* _internals,
|
|
99
|
+
* square,
|
|
100
|
+
* } from "https://deno.land/std@$STD_VERSION/testing/mock_examples/internals_injection.ts";
|
|
101
|
+
*
|
|
102
|
+
* Deno.test("square calls multiply and returns results", () => {
|
|
103
|
+
* const multiplySpy = spy(_internals, "multiply");
|
|
104
|
+
*
|
|
105
|
+
* try {
|
|
106
|
+
* assertEquals(square(5), 25);
|
|
107
|
+
* } finally {
|
|
108
|
+
* // unwraps the multiply method on the _internals object
|
|
109
|
+
* multiplySpy.restore();
|
|
110
|
+
* }
|
|
111
|
+
*
|
|
112
|
+
* // asserts that multiplySpy was called at least once and details about the first call.
|
|
113
|
+
* assertSpyCall(multiplySpy, 0, {
|
|
114
|
+
* args: [5, 5],
|
|
115
|
+
* returned: 25,
|
|
116
|
+
* });
|
|
117
|
+
*
|
|
118
|
+
* // asserts that multiplySpy was only called once.
|
|
119
|
+
* assertSpyCalls(multiplySpy, 1);
|
|
120
|
+
* });
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* One difference you may have noticed between these two examples is that in the
|
|
124
|
+
* second we call the `restore` method on `multiplySpy` function. That is needed to
|
|
125
|
+
* remove the spy wrapper from the `_internals` object's `multiply` method. The
|
|
126
|
+
* `restore` method is called in a finally block to ensure that it is restored
|
|
127
|
+
* whether or not the assertion in the try block is successful. The `restore`
|
|
128
|
+
* method didn't need to be called in the first example because the `multiply`
|
|
129
|
+
* function was not modified in any way like the `_internals` object was in the
|
|
130
|
+
* second example.
|
|
131
|
+
*
|
|
132
|
+
* ## Stubbing
|
|
133
|
+
*
|
|
134
|
+
* Say we have two functions, `randomMultiple` and `randomInt`, if we want to
|
|
135
|
+
* assert that `randomInt` is called during execution of `randomMultiple` we need a
|
|
136
|
+
* way to spy on the `randomInt` function. That could be done with either of the
|
|
137
|
+
* spying techniques previously mentioned. To be able to verify that the
|
|
138
|
+
* `randomMultiple` function returns the value we expect it to for what `randomInt`
|
|
139
|
+
* returns, the easiest way would be to replace the `randomInt` function's behavior
|
|
140
|
+
* with more predictable behavior.
|
|
141
|
+
*
|
|
142
|
+
* You could use the first spying technique to do that but that would require
|
|
143
|
+
* adding a `randomInt` parameter to the `randomMultiple` function.
|
|
144
|
+
*
|
|
145
|
+
* You could also use the second spying technique to do that, but your assertions
|
|
146
|
+
* would not be as predictable due to the `randomInt` function returning random
|
|
147
|
+
* values.
|
|
148
|
+
*
|
|
149
|
+
* Say we want to verify it returns correct values for both negative and positive
|
|
150
|
+
* random integers. We could easily do that with stubbing. The below example is
|
|
151
|
+
* similar to the second spying technique example but instead of passing the call
|
|
152
|
+
* through to the original `randomInt` function, we are going to replace
|
|
153
|
+
* `randomInt` with a function that returns pre-defined values.
|
|
154
|
+
*
|
|
155
|
+
* ```ts
|
|
156
|
+
* // https://deno.land/std@$STD_VERSION/testing/mock_examples/random.ts
|
|
157
|
+
* export function randomInt(lowerBound: number, upperBound: number): number {
|
|
158
|
+
* return lowerBound + Math.floor(Math.random() * (upperBound - lowerBound));
|
|
159
|
+
* }
|
|
160
|
+
*
|
|
161
|
+
* export function randomMultiple(value: number): number {
|
|
162
|
+
* return value * _internals.randomInt(-10, 10);
|
|
163
|
+
* }
|
|
164
|
+
*
|
|
165
|
+
* export const _internals = { randomInt };
|
|
166
|
+
* ```
|
|
167
|
+
*
|
|
168
|
+
* The mock module includes some helper functions to make creating common stubs
|
|
169
|
+
* easy. The `returnsNext` function takes an array of values we want it to return
|
|
170
|
+
* on consecutive calls.
|
|
171
|
+
*
|
|
172
|
+
* ```ts
|
|
173
|
+
* // https://deno.land/std@$STD_VERSION/testing/mock_examples/random_test.ts
|
|
174
|
+
* import {
|
|
175
|
+
* assertSpyCall,
|
|
176
|
+
* assertSpyCalls,
|
|
177
|
+
* returnsNext,
|
|
178
|
+
* stub,
|
|
179
|
+
* } from "https://deno.land/std@$STD_VERSION/testing/mock.ts";
|
|
180
|
+
* import { assertEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_equals.ts";
|
|
181
|
+
* import {
|
|
182
|
+
* _internals,
|
|
183
|
+
* randomMultiple,
|
|
184
|
+
* } from "https://deno.land/std@$STD_VERSION/testing/mock_examples/random.ts";
|
|
185
|
+
*
|
|
186
|
+
* Deno.test("randomMultiple uses randomInt to generate random multiples between -10 and 10 times the value", () => {
|
|
187
|
+
* const randomIntStub = stub(_internals, "randomInt", returnsNext([-3, 3]));
|
|
188
|
+
*
|
|
189
|
+
* try {
|
|
190
|
+
* assertEquals(randomMultiple(5), -15);
|
|
191
|
+
* assertEquals(randomMultiple(5), 15);
|
|
192
|
+
* } finally {
|
|
193
|
+
* // unwraps the randomInt method on the _internals object
|
|
194
|
+
* randomIntStub.restore();
|
|
195
|
+
* }
|
|
196
|
+
*
|
|
197
|
+
* // asserts that randomIntStub was called at least once and details about the first call.
|
|
198
|
+
* assertSpyCall(randomIntStub, 0, {
|
|
199
|
+
* args: [-10, 10],
|
|
200
|
+
* returned: -3,
|
|
201
|
+
* });
|
|
202
|
+
* // asserts that randomIntStub was called at least twice and details about the second call.
|
|
203
|
+
* assertSpyCall(randomIntStub, 1, {
|
|
204
|
+
* args: [-10, 10],
|
|
205
|
+
* returned: 3,
|
|
206
|
+
* });
|
|
207
|
+
*
|
|
208
|
+
* // asserts that randomIntStub was only called twice.
|
|
209
|
+
* assertSpyCalls(randomIntStub, 2);
|
|
210
|
+
* });
|
|
211
|
+
* ```
|
|
212
|
+
*
|
|
213
|
+
* ## Faking time
|
|
214
|
+
*
|
|
215
|
+
* Say we have a function that has time based behavior that we would like to test.
|
|
216
|
+
* With real time, that could cause tests to take much longer than they should. If
|
|
217
|
+
* you fake time, you could simulate how your function would behave over time
|
|
218
|
+
* starting from any point in time. Below is an example where we want to test that
|
|
219
|
+
* the callback is called every second.
|
|
220
|
+
*
|
|
221
|
+
* ```ts
|
|
222
|
+
* // https://deno.land/std@$STD_VERSION/testing/mock_examples/interval.ts
|
|
223
|
+
* export function secondInterval(cb: () => void): number {
|
|
224
|
+
* return setInterval(cb, 1000);
|
|
225
|
+
* }
|
|
226
|
+
* ```
|
|
227
|
+
*
|
|
228
|
+
* With `FakeTime` we can do that. When the `FakeTime` instance is created, it
|
|
229
|
+
* splits from real time. The `Date`, `setTimeout`, `clearTimeout`, `setInterval`
|
|
230
|
+
* and `clearInterval` globals are replaced with versions that use the fake time
|
|
231
|
+
* until real time is restored. You can control how time ticks forward with the
|
|
232
|
+
* `tick` method on the `FakeTime` instance.
|
|
233
|
+
*
|
|
234
|
+
* ```ts
|
|
235
|
+
* // https://deno.land/std@$STD_VERSION/testing/mock_examples/interval_test.ts
|
|
236
|
+
* import {
|
|
237
|
+
* assertSpyCalls,
|
|
238
|
+
* spy,
|
|
239
|
+
* } from "https://deno.land/std@$STD_VERSION/testing/mock.ts";
|
|
240
|
+
* import { FakeTime } from "https://deno.land/std@$STD_VERSION/testing/time.ts";
|
|
241
|
+
* import { secondInterval } from "https://deno.land/std@$STD_VERSION/testing/mock_examples/interval.ts";
|
|
242
|
+
*
|
|
243
|
+
* Deno.test("secondInterval calls callback every second and stops after being cleared", () => {
|
|
244
|
+
* const time = new FakeTime();
|
|
245
|
+
*
|
|
246
|
+
* try {
|
|
247
|
+
* const cb = spy();
|
|
248
|
+
* const intervalId = secondInterval(cb);
|
|
249
|
+
* assertSpyCalls(cb, 0);
|
|
250
|
+
* time.tick(500);
|
|
251
|
+
* assertSpyCalls(cb, 0);
|
|
252
|
+
* time.tick(500);
|
|
253
|
+
* assertSpyCalls(cb, 1);
|
|
254
|
+
* time.tick(3500);
|
|
255
|
+
* assertSpyCalls(cb, 4);
|
|
256
|
+
*
|
|
257
|
+
* clearInterval(intervalId);
|
|
258
|
+
* time.tick(1000);
|
|
259
|
+
* assertSpyCalls(cb, 4);
|
|
260
|
+
* } finally {
|
|
261
|
+
* time.restore();
|
|
262
|
+
* }
|
|
263
|
+
* });
|
|
264
|
+
* ```
|
|
265
|
+
*
|
|
266
|
+
* This module is browser compatible.
|
|
267
|
+
*
|
|
268
|
+
* @module
|
|
269
|
+
*/
|
|
270
|
+
import { assertEquals, AssertionError, assertIsError, assertRejects, } from "./asserts.js";
|
|
271
|
+
/** An error related to spying on a function or instance method. */
|
|
272
|
+
export class MockError extends Error {
|
|
273
|
+
constructor(message) {
|
|
274
|
+
super(message);
|
|
275
|
+
this.name = "MockError";
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function functionSpy(func) {
|
|
279
|
+
const original = func ?? (() => { }), calls = [];
|
|
280
|
+
const spy = function (...args) {
|
|
281
|
+
const call = { args };
|
|
282
|
+
if (this)
|
|
283
|
+
call.self = this;
|
|
284
|
+
try {
|
|
285
|
+
call.returned = original.apply(this, args);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
call.error = error;
|
|
289
|
+
calls.push(call);
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
calls.push(call);
|
|
293
|
+
return call.returned;
|
|
294
|
+
};
|
|
295
|
+
Object.defineProperties(spy, {
|
|
296
|
+
original: {
|
|
297
|
+
enumerable: true,
|
|
298
|
+
value: original,
|
|
299
|
+
},
|
|
300
|
+
calls: {
|
|
301
|
+
enumerable: true,
|
|
302
|
+
value: calls,
|
|
303
|
+
},
|
|
304
|
+
restored: {
|
|
305
|
+
enumerable: true,
|
|
306
|
+
get: () => false,
|
|
307
|
+
},
|
|
308
|
+
restore: {
|
|
309
|
+
enumerable: true,
|
|
310
|
+
value: () => {
|
|
311
|
+
throw new MockError("function cannot be restored");
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
return spy;
|
|
316
|
+
}
|
|
317
|
+
/** Checks if a function is a spy. */
|
|
318
|
+
function isSpy(func) {
|
|
319
|
+
const spy = func;
|
|
320
|
+
return typeof spy === "function" &&
|
|
321
|
+
typeof spy.original === "function" &&
|
|
322
|
+
typeof spy.restored === "boolean" &&
|
|
323
|
+
typeof spy.restore === "function" &&
|
|
324
|
+
Array.isArray(spy.calls);
|
|
325
|
+
}
|
|
326
|
+
// deno-lint-ignore no-explicit-any
|
|
327
|
+
const sessions = [];
|
|
328
|
+
// deno-lint-ignore no-explicit-any
|
|
329
|
+
function getSession() {
|
|
330
|
+
if (sessions.length === 0)
|
|
331
|
+
sessions.push(new Set());
|
|
332
|
+
return sessions[sessions.length - 1];
|
|
333
|
+
}
|
|
334
|
+
// deno-lint-ignore no-explicit-any
|
|
335
|
+
function registerMock(spy) {
|
|
336
|
+
const session = getSession();
|
|
337
|
+
session.add(spy);
|
|
338
|
+
}
|
|
339
|
+
// deno-lint-ignore no-explicit-any
|
|
340
|
+
function unregisterMock(spy) {
|
|
341
|
+
const session = getSession();
|
|
342
|
+
session.delete(spy);
|
|
343
|
+
}
|
|
344
|
+
export function mockSession(func) {
|
|
345
|
+
if (func) {
|
|
346
|
+
return function (...args) {
|
|
347
|
+
const id = sessions.length;
|
|
348
|
+
sessions.push(new Set());
|
|
349
|
+
try {
|
|
350
|
+
return func.apply(this, args);
|
|
351
|
+
}
|
|
352
|
+
finally {
|
|
353
|
+
restore(id);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
sessions.push(new Set());
|
|
359
|
+
return sessions.length - 1;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/** Creates an async session that tracks all mocks created before the promise resolves. */
|
|
363
|
+
export function mockSessionAsync(func) {
|
|
364
|
+
return async function (...args) {
|
|
365
|
+
const id = sessions.length;
|
|
366
|
+
sessions.push(new Set());
|
|
367
|
+
try {
|
|
368
|
+
return await func.apply(this, args);
|
|
369
|
+
}
|
|
370
|
+
finally {
|
|
371
|
+
restore(id);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Restores all mocks registered in the current session that have not already been restored.
|
|
377
|
+
* If an id is provided, it will restore all mocks registered in the session associed with that id that have not already been restored.
|
|
378
|
+
*/
|
|
379
|
+
export function restore(id) {
|
|
380
|
+
id ??= (sessions.length || 1) - 1;
|
|
381
|
+
while (id < sessions.length) {
|
|
382
|
+
const session = sessions.pop();
|
|
383
|
+
if (session) {
|
|
384
|
+
for (const value of session) {
|
|
385
|
+
value.restore();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/** Wraps an instance method with a Spy. */
|
|
391
|
+
function methodSpy(self, property) {
|
|
392
|
+
if (typeof self[property] !== "function") {
|
|
393
|
+
throw new MockError("property is not an instance method");
|
|
394
|
+
}
|
|
395
|
+
if (isSpy(self[property])) {
|
|
396
|
+
throw new MockError("already spying on instance method");
|
|
397
|
+
}
|
|
398
|
+
const propertyDescriptor = Object.getOwnPropertyDescriptor(self, property);
|
|
399
|
+
if (propertyDescriptor && !propertyDescriptor.configurable) {
|
|
400
|
+
throw new MockError("cannot spy on non configurable instance method");
|
|
401
|
+
}
|
|
402
|
+
const original = self[property], calls = [];
|
|
403
|
+
let restored = false;
|
|
404
|
+
const spy = function (...args) {
|
|
405
|
+
const call = { args };
|
|
406
|
+
if (this)
|
|
407
|
+
call.self = this;
|
|
408
|
+
try {
|
|
409
|
+
call.returned = original.apply(this, args);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
call.error = error;
|
|
413
|
+
calls.push(call);
|
|
414
|
+
throw error;
|
|
415
|
+
}
|
|
416
|
+
calls.push(call);
|
|
417
|
+
return call.returned;
|
|
418
|
+
};
|
|
419
|
+
Object.defineProperties(spy, {
|
|
420
|
+
original: {
|
|
421
|
+
enumerable: true,
|
|
422
|
+
value: original,
|
|
423
|
+
},
|
|
424
|
+
calls: {
|
|
425
|
+
enumerable: true,
|
|
426
|
+
value: calls,
|
|
427
|
+
},
|
|
428
|
+
restored: {
|
|
429
|
+
enumerable: true,
|
|
430
|
+
get: () => restored,
|
|
431
|
+
},
|
|
432
|
+
restore: {
|
|
433
|
+
enumerable: true,
|
|
434
|
+
value: () => {
|
|
435
|
+
if (restored) {
|
|
436
|
+
throw new MockError("instance method already restored");
|
|
437
|
+
}
|
|
438
|
+
if (propertyDescriptor) {
|
|
439
|
+
Object.defineProperty(self, property, propertyDescriptor);
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
delete self[property];
|
|
443
|
+
}
|
|
444
|
+
restored = true;
|
|
445
|
+
unregisterMock(spy);
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
Object.defineProperty(self, property, {
|
|
450
|
+
configurable: true,
|
|
451
|
+
enumerable: propertyDescriptor?.enumerable,
|
|
452
|
+
writable: propertyDescriptor?.writable,
|
|
453
|
+
value: spy,
|
|
454
|
+
});
|
|
455
|
+
registerMock(spy);
|
|
456
|
+
return spy;
|
|
457
|
+
}
|
|
458
|
+
export function spy(funcOrSelf, property) {
|
|
459
|
+
const spy = typeof property !== "undefined"
|
|
460
|
+
? methodSpy(funcOrSelf, property)
|
|
461
|
+
: typeof funcOrSelf === "function"
|
|
462
|
+
? functionSpy(funcOrSelf)
|
|
463
|
+
: functionSpy();
|
|
464
|
+
return spy;
|
|
465
|
+
}
|
|
466
|
+
export function stub(self, property, func) {
|
|
467
|
+
if (self[property] !== undefined && typeof self[property] !== "function") {
|
|
468
|
+
throw new MockError("property is not an instance method");
|
|
469
|
+
}
|
|
470
|
+
if (isSpy(self[property])) {
|
|
471
|
+
throw new MockError("already spying on instance method");
|
|
472
|
+
}
|
|
473
|
+
const propertyDescriptor = Object.getOwnPropertyDescriptor(self, property);
|
|
474
|
+
if (propertyDescriptor && !propertyDescriptor.configurable) {
|
|
475
|
+
throw new MockError("cannot spy on non configurable instance method");
|
|
476
|
+
}
|
|
477
|
+
const fake = func ?? (() => { });
|
|
478
|
+
const original = self[property], calls = [];
|
|
479
|
+
let restored = false;
|
|
480
|
+
const stub = function (...args) {
|
|
481
|
+
const call = { args };
|
|
482
|
+
if (this)
|
|
483
|
+
call.self = this;
|
|
484
|
+
try {
|
|
485
|
+
call.returned = fake.apply(this, args);
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
call.error = error;
|
|
489
|
+
calls.push(call);
|
|
490
|
+
throw error;
|
|
491
|
+
}
|
|
492
|
+
calls.push(call);
|
|
493
|
+
return call.returned;
|
|
494
|
+
};
|
|
495
|
+
Object.defineProperties(stub, {
|
|
496
|
+
original: {
|
|
497
|
+
enumerable: true,
|
|
498
|
+
value: original,
|
|
499
|
+
},
|
|
500
|
+
fake: {
|
|
501
|
+
enumerable: true,
|
|
502
|
+
value: fake,
|
|
503
|
+
},
|
|
504
|
+
calls: {
|
|
505
|
+
enumerable: true,
|
|
506
|
+
value: calls,
|
|
507
|
+
},
|
|
508
|
+
restored: {
|
|
509
|
+
enumerable: true,
|
|
510
|
+
get: () => restored,
|
|
511
|
+
},
|
|
512
|
+
restore: {
|
|
513
|
+
enumerable: true,
|
|
514
|
+
value: () => {
|
|
515
|
+
if (restored) {
|
|
516
|
+
throw new MockError("instance method already restored");
|
|
517
|
+
}
|
|
518
|
+
if (propertyDescriptor) {
|
|
519
|
+
Object.defineProperty(self, property, propertyDescriptor);
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
delete self[property];
|
|
523
|
+
}
|
|
524
|
+
restored = true;
|
|
525
|
+
unregisterMock(stub);
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
Object.defineProperty(self, property, {
|
|
530
|
+
configurable: true,
|
|
531
|
+
enumerable: propertyDescriptor?.enumerable,
|
|
532
|
+
writable: propertyDescriptor?.writable,
|
|
533
|
+
value: stub,
|
|
534
|
+
});
|
|
535
|
+
registerMock(stub);
|
|
536
|
+
return stub;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Asserts that a spy is called as much as expected and no more.
|
|
540
|
+
*/
|
|
541
|
+
export function assertSpyCalls(spy, expectedCalls) {
|
|
542
|
+
try {
|
|
543
|
+
assertEquals(spy.calls.length, expectedCalls);
|
|
544
|
+
}
|
|
545
|
+
catch (e) {
|
|
546
|
+
assertIsError(e);
|
|
547
|
+
let message = spy.calls.length < expectedCalls
|
|
548
|
+
? "spy not called as much as expected:\n"
|
|
549
|
+
: "spy called more than expected:\n";
|
|
550
|
+
message += e.message.split("\n").slice(1).join("\n");
|
|
551
|
+
throw new AssertionError(message);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Asserts that a spy is called as expected.
|
|
556
|
+
*/
|
|
557
|
+
export function assertSpyCall(spy, callIndex, expected) {
|
|
558
|
+
if (spy.calls.length < (callIndex + 1)) {
|
|
559
|
+
throw new AssertionError("spy not called as much as expected");
|
|
560
|
+
}
|
|
561
|
+
const call = spy.calls[callIndex];
|
|
562
|
+
if (expected) {
|
|
563
|
+
if (expected.args) {
|
|
564
|
+
try {
|
|
565
|
+
assertEquals(call.args, expected.args);
|
|
566
|
+
}
|
|
567
|
+
catch (e) {
|
|
568
|
+
assertIsError(e);
|
|
569
|
+
throw new AssertionError("spy not called with expected args:\n" +
|
|
570
|
+
e.message.split("\n").slice(1).join("\n"));
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if ("self" in expected) {
|
|
574
|
+
try {
|
|
575
|
+
assertEquals(call.self, expected.self);
|
|
576
|
+
}
|
|
577
|
+
catch (e) {
|
|
578
|
+
assertIsError(e);
|
|
579
|
+
let message = expected.self
|
|
580
|
+
? "spy not called as method on expected self:\n"
|
|
581
|
+
: "spy not expected to be called as method on object:\n";
|
|
582
|
+
message += e.message.split("\n").slice(1).join("\n");
|
|
583
|
+
throw new AssertionError(message);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if ("returned" in expected) {
|
|
587
|
+
if ("error" in expected) {
|
|
588
|
+
throw new TypeError("do not expect error and return, only one should be expected");
|
|
589
|
+
}
|
|
590
|
+
if (call.error) {
|
|
591
|
+
throw new AssertionError("spy call did not return expected value, an error was thrown.");
|
|
592
|
+
}
|
|
593
|
+
try {
|
|
594
|
+
assertEquals(call.returned, expected.returned);
|
|
595
|
+
}
|
|
596
|
+
catch (e) {
|
|
597
|
+
assertIsError(e);
|
|
598
|
+
throw new AssertionError("spy call did not return expected value:\n" +
|
|
599
|
+
e.message.split("\n").slice(1).join("\n"));
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if ("error" in expected) {
|
|
603
|
+
if ("returned" in call) {
|
|
604
|
+
throw new AssertionError("spy call did not throw an error, a value was returned.");
|
|
605
|
+
}
|
|
606
|
+
assertIsError(call.error, expected.error?.Class, expected.error?.msgIncludes);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Asserts that an async spy is called as expected.
|
|
612
|
+
*/
|
|
613
|
+
export async function assertSpyCallAsync(spy, callIndex, expected) {
|
|
614
|
+
const expectedSync = expected && { ...expected };
|
|
615
|
+
if (expectedSync) {
|
|
616
|
+
delete expectedSync.returned;
|
|
617
|
+
delete expectedSync.error;
|
|
618
|
+
}
|
|
619
|
+
assertSpyCall(spy, callIndex, expectedSync);
|
|
620
|
+
const call = spy.calls[callIndex];
|
|
621
|
+
if (call.error) {
|
|
622
|
+
throw new AssertionError("spy call did not return a promise, an error was thrown.");
|
|
623
|
+
}
|
|
624
|
+
if (call.returned !== Promise.resolve(call.returned)) {
|
|
625
|
+
throw new AssertionError("spy call did not return a promise, a value was returned.");
|
|
626
|
+
}
|
|
627
|
+
if (expected) {
|
|
628
|
+
if ("returned" in expected) {
|
|
629
|
+
if ("error" in expected) {
|
|
630
|
+
throw new TypeError("do not expect error and return, only one should be expected");
|
|
631
|
+
}
|
|
632
|
+
if (call.error) {
|
|
633
|
+
throw new AssertionError("spy call did not return expected value, an error was thrown.");
|
|
634
|
+
}
|
|
635
|
+
let expectedResolved;
|
|
636
|
+
try {
|
|
637
|
+
expectedResolved = await expected.returned;
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
throw new TypeError("do not expect rejected promise, expect error instead");
|
|
641
|
+
}
|
|
642
|
+
let resolved;
|
|
643
|
+
try {
|
|
644
|
+
resolved = await call.returned;
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
throw new AssertionError("spy call returned promise was rejected");
|
|
648
|
+
}
|
|
649
|
+
try {
|
|
650
|
+
assertEquals(resolved, expectedResolved);
|
|
651
|
+
}
|
|
652
|
+
catch (e) {
|
|
653
|
+
assertIsError(e);
|
|
654
|
+
throw new AssertionError("spy call did not resolve to expected value:\n" +
|
|
655
|
+
e.message.split("\n").slice(1).join("\n"));
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if ("error" in expected) {
|
|
659
|
+
await assertRejects(() => Promise.resolve(call.returned), expected.error?.Class ?? Error, expected.error?.msgIncludes ?? "");
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Asserts that a spy is called with a specific arg as expected.
|
|
665
|
+
*/
|
|
666
|
+
export function assertSpyCallArg(spy, callIndex, argIndex, expected) {
|
|
667
|
+
assertSpyCall(spy, callIndex);
|
|
668
|
+
const call = spy.calls[callIndex];
|
|
669
|
+
const arg = call.args[argIndex];
|
|
670
|
+
assertEquals(arg, expected);
|
|
671
|
+
return arg;
|
|
672
|
+
}
|
|
673
|
+
export function assertSpyCallArgs(spy, callIndex, argsStart, argsEnd, expected) {
|
|
674
|
+
assertSpyCall(spy, callIndex);
|
|
675
|
+
const call = spy.calls[callIndex];
|
|
676
|
+
if (!expected) {
|
|
677
|
+
expected = argsEnd;
|
|
678
|
+
argsEnd = undefined;
|
|
679
|
+
}
|
|
680
|
+
if (!expected) {
|
|
681
|
+
expected = argsStart;
|
|
682
|
+
argsStart = undefined;
|
|
683
|
+
}
|
|
684
|
+
const args = typeof argsEnd === "number"
|
|
685
|
+
? call.args.slice(argsStart, argsEnd)
|
|
686
|
+
: typeof argsStart === "number"
|
|
687
|
+
? call.args.slice(argsStart)
|
|
688
|
+
: call.args;
|
|
689
|
+
assertEquals(args, expected);
|
|
690
|
+
return args;
|
|
691
|
+
}
|
|
692
|
+
/** Creates a function that returns the instance the method was called on. */
|
|
693
|
+
export function returnsThis() {
|
|
694
|
+
return function () {
|
|
695
|
+
return this;
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
/** Creates a function that returns one of its arguments. */
|
|
699
|
+
// deno-lint-ignore no-explicit-any
|
|
700
|
+
export function returnsArg(idx) {
|
|
701
|
+
return function (...args) {
|
|
702
|
+
return args[idx];
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
/** Creates a function that returns its arguments or a subset of them. If end is specified, it will return arguments up to but not including the end. */
|
|
706
|
+
export function returnsArgs(start = 0, end) {
|
|
707
|
+
return function (...args) {
|
|
708
|
+
return args.slice(start, end);
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
/** Creates a function that returns the iterable values. Any iterable values that are errors will be thrown. */
|
|
712
|
+
export function returnsNext(values) {
|
|
713
|
+
const gen = (function* returnsValue() {
|
|
714
|
+
yield* values;
|
|
715
|
+
})();
|
|
716
|
+
let calls = 0;
|
|
717
|
+
return function () {
|
|
718
|
+
const next = gen.next();
|
|
719
|
+
if (next.done) {
|
|
720
|
+
throw new MockError(`not expected to be called more than ${calls} times`);
|
|
721
|
+
}
|
|
722
|
+
calls++;
|
|
723
|
+
const { value } = next;
|
|
724
|
+
if (value instanceof Error)
|
|
725
|
+
throw value;
|
|
726
|
+
return value;
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
/** Creates a function that resolves the awaited iterable values. Any awaited iterable values that are errors will be thrown. */
|
|
730
|
+
export function resolvesNext(iterable) {
|
|
731
|
+
const gen = (async function* returnsValue() {
|
|
732
|
+
yield* iterable;
|
|
733
|
+
})();
|
|
734
|
+
let calls = 0;
|
|
735
|
+
return async function () {
|
|
736
|
+
const next = await gen.next();
|
|
737
|
+
if (next.done) {
|
|
738
|
+
throw new MockError(`not expected to be called more than ${calls} times`);
|
|
739
|
+
}
|
|
740
|
+
calls++;
|
|
741
|
+
const { value } = next;
|
|
742
|
+
if (value instanceof Error)
|
|
743
|
+
throw value;
|
|
744
|
+
return value;
|
|
745
|
+
};
|
|
746
|
+
}
|