@go-go-scope/testing 2.1.0 → 2.3.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/dist/index.d.mts +97 -21
- package/dist/index.mjs +114 -2
- package/package.json +4 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,97 @@
|
|
|
1
1
|
import * as go_go_scope from 'go-go-scope';
|
|
2
|
-
import { Scope, TaskOptions } from 'go-go-scope';
|
|
2
|
+
import { Task, Result, Scope, TaskOptions } from 'go-go-scope';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Assertion helpers for testing go-go-scope tasks
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Assertion result with helper methods
|
|
10
|
+
*/
|
|
11
|
+
interface TaskAssertion<T> {
|
|
12
|
+
/** Assert task resolves without error */
|
|
13
|
+
toResolve: () => Promise<void>;
|
|
14
|
+
/** Assert task resolves with specific value */
|
|
15
|
+
toResolveWith: (expected: T) => Promise<void>;
|
|
16
|
+
/** Assert task resolves within timeout */
|
|
17
|
+
toResolveWithin: (timeoutMs: number) => TaskAssertion<T>;
|
|
18
|
+
/** Assert task rejects with error */
|
|
19
|
+
toReject: () => Promise<void>;
|
|
20
|
+
/** Assert task rejects with specific error message */
|
|
21
|
+
toRejectWith: (message: string | RegExp) => Promise<void>;
|
|
22
|
+
/** Assert task fails with specific error type */
|
|
23
|
+
toRejectWithType: <E extends Error>(errorClass: new (...args: never[]) => E) => Promise<void>;
|
|
24
|
+
/** Get the result tuple for manual assertions */
|
|
25
|
+
result: () => Promise<Result<Error, T>>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create assertion helpers for a task.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { expectTask } from '@go-go-scope/testing'
|
|
33
|
+
*
|
|
34
|
+
* test('task succeeds', async () => {
|
|
35
|
+
* const s = scope()
|
|
36
|
+
* await expectTask(s.task(() => Promise.resolve('done')))
|
|
37
|
+
* .toResolveWith('done')
|
|
38
|
+
* })
|
|
39
|
+
*
|
|
40
|
+
* test('task fails', async () => {
|
|
41
|
+
* const s = scope()
|
|
42
|
+
* await expectTask(s.task(() => Promise.reject(new Error('fail'))))
|
|
43
|
+
* .toRejectWith('fail')
|
|
44
|
+
* })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare function expectTask<T>(task: Task<Result<Error, T>>): TaskAssertion<T>;
|
|
48
|
+
/**
|
|
49
|
+
* Assert that a task resolves successfully.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* import { assertResolves } from '@go-go-scope/testing'
|
|
54
|
+
*
|
|
55
|
+
* test('task succeeds', async () => {
|
|
56
|
+
* const s = scope()
|
|
57
|
+
* const [err, result] = await assertResolves(s.task(() => Promise.resolve('done')))
|
|
58
|
+
* expect(result).toBe('done')
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare function assertResolves<T>(task: Task<Result<Error, T>>): Promise<Result<Error, T>>;
|
|
63
|
+
/**
|
|
64
|
+
* Assert that a task rejects with an error.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* import { assertRejects } from '@go-go-scope/testing'
|
|
69
|
+
*
|
|
70
|
+
* test('task fails', async () => {
|
|
71
|
+
* const s = scope()
|
|
72
|
+
* const err = await assertRejects(s.task(() => Promise.reject(new Error('fail'))))
|
|
73
|
+
* expect(err.message).toBe('fail')
|
|
74
|
+
* })
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
declare function assertRejects<T>(task: Task<Result<Error, T>>): Promise<Error>;
|
|
78
|
+
/**
|
|
79
|
+
* Assert that a task resolves within a timeout.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* import { assertResolvesWithin } from '@go-go-scope/testing'
|
|
84
|
+
*
|
|
85
|
+
* test('task is fast', async () => {
|
|
86
|
+
* const s = scope()
|
|
87
|
+
* const [err, result] = await assertResolvesWithin(
|
|
88
|
+
* s.task(() => fetchData()),
|
|
89
|
+
* 1000
|
|
90
|
+
* )
|
|
91
|
+
* })
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
declare function assertResolvesWithin<T>(task: Task<Result<Error, T>>, timeoutMs: number): Promise<Result<Error, T>>;
|
|
3
95
|
|
|
4
96
|
/**
|
|
5
97
|
* Time travel testing utilities for go-go-scope
|
|
@@ -114,28 +206,10 @@ declare function createTestScope(options?: {
|
|
|
114
206
|
timeout?: number;
|
|
115
207
|
concurrency?: number;
|
|
116
208
|
}): Promise<{
|
|
117
|
-
scope: go_go_scope.Scope
|
|
209
|
+
scope: go_go_scope.Scope<Record<string, unknown>>;
|
|
118
210
|
time: TimeController;
|
|
119
211
|
}>;
|
|
120
212
|
|
|
121
|
-
/**
|
|
122
|
-
* Test utilities for go-go-scope
|
|
123
|
-
*
|
|
124
|
-
* Provides helper functions and mocks for testing code that uses go-go-scope.
|
|
125
|
-
*
|
|
126
|
-
* @example
|
|
127
|
-
* ```typescript
|
|
128
|
-
* import { createMockScope } from '@go-go-scope/testing'
|
|
129
|
-
*
|
|
130
|
-
* const s = createMockScope({
|
|
131
|
-
* autoAdvanceTimers: true,
|
|
132
|
-
* deterministic: true
|
|
133
|
-
* })
|
|
134
|
-
*
|
|
135
|
-
* await s.task(() => fetchData())
|
|
136
|
-
* ```
|
|
137
|
-
*/
|
|
138
|
-
|
|
139
213
|
/**
|
|
140
214
|
* Options for creating a mock scope
|
|
141
215
|
*/
|
|
@@ -160,6 +234,8 @@ interface TaskCall {
|
|
|
160
234
|
fn: (ctx: {
|
|
161
235
|
services: Record<string, never>;
|
|
162
236
|
signal: AbortSignal;
|
|
237
|
+
logger: go_go_scope.Logger;
|
|
238
|
+
context: Record<string, unknown>;
|
|
163
239
|
}) => Promise<unknown>;
|
|
164
240
|
options?: TaskOptions;
|
|
165
241
|
}
|
|
@@ -418,5 +494,5 @@ declare function createTimeTravelController(): {
|
|
|
418
494
|
printTimeline(): void;
|
|
419
495
|
};
|
|
420
496
|
|
|
421
|
-
export { assertScopeDisposed, createControlledTimer, createMockChannel, createMockScope, createSpy, createTestScope, createTimeController, createTimeTravelController, flushPromises };
|
|
497
|
+
export { assertRejects, assertResolves, assertResolvesWithin, assertScopeDisposed, createControlledTimer, createMockChannel, createMockScope, createSpy, createTestScope, createTimeController, createTimeTravelController, expectTask, flushPromises };
|
|
422
498
|
export type { MockChannel, MockScope, MockScopeOptions, Spy, TaskCall };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,116 @@
|
|
|
1
1
|
import { Scope } from 'go-go-scope';
|
|
2
2
|
|
|
3
|
+
function expectTask(task) {
|
|
4
|
+
let resolvedResult;
|
|
5
|
+
let resolved = false;
|
|
6
|
+
const getResult = async () => {
|
|
7
|
+
if (!resolved) {
|
|
8
|
+
resolvedResult = await task;
|
|
9
|
+
resolved = true;
|
|
10
|
+
}
|
|
11
|
+
return resolvedResult;
|
|
12
|
+
};
|
|
13
|
+
const assertion = {
|
|
14
|
+
toResolve: async () => {
|
|
15
|
+
const [err] = await getResult();
|
|
16
|
+
if (err) {
|
|
17
|
+
throw new Error(`Expected task to resolve, but rejected: ${err.message}`);
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
toResolveWith: async (expected) => {
|
|
21
|
+
const [err, result] = await getResult();
|
|
22
|
+
if (err) {
|
|
23
|
+
throw new Error(`Expected task to resolve with ${JSON.stringify(expected)}, but rejected: ${err.message}`);
|
|
24
|
+
}
|
|
25
|
+
if (result !== expected) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Expected task to resolve with ${JSON.stringify(expected)}, but got ${JSON.stringify(result)}`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
toResolveWithin: (timeoutMs) => {
|
|
32
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
33
|
+
setTimeout(() => {
|
|
34
|
+
reject(new Error(`Task did not resolve within ${timeoutMs}ms`));
|
|
35
|
+
}, timeoutMs);
|
|
36
|
+
});
|
|
37
|
+
const racingAssertion = {
|
|
38
|
+
toResolve: async () => {
|
|
39
|
+
await Promise.race([assertion.toResolve(), timeoutPromise]);
|
|
40
|
+
},
|
|
41
|
+
toResolveWith: async (expected) => {
|
|
42
|
+
await Promise.race([assertion.toResolveWith(expected), timeoutPromise]);
|
|
43
|
+
},
|
|
44
|
+
toResolveWithin: () => racingAssertion,
|
|
45
|
+
toReject: async () => {
|
|
46
|
+
await Promise.race([assertion.toReject(), timeoutPromise]);
|
|
47
|
+
},
|
|
48
|
+
toRejectWith: async (message) => {
|
|
49
|
+
await Promise.race([assertion.toRejectWith(message), timeoutPromise]);
|
|
50
|
+
},
|
|
51
|
+
toRejectWithType: async (errorClass) => {
|
|
52
|
+
await Promise.race([assertion.toRejectWithType(errorClass), timeoutPromise]);
|
|
53
|
+
},
|
|
54
|
+
result: getResult
|
|
55
|
+
};
|
|
56
|
+
return racingAssertion;
|
|
57
|
+
},
|
|
58
|
+
toReject: async () => {
|
|
59
|
+
const [err] = await getResult();
|
|
60
|
+
if (!err) {
|
|
61
|
+
throw new Error("Expected task to reject, but resolved successfully");
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
toRejectWith: async (message) => {
|
|
65
|
+
const [err] = await getResult();
|
|
66
|
+
if (!err) {
|
|
67
|
+
throw new Error(`Expected task to reject with "${message}", but resolved successfully`);
|
|
68
|
+
}
|
|
69
|
+
const matches = typeof message === "string" ? err.message === message : message.test(err.message);
|
|
70
|
+
if (!matches) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Expected error message "${message}", but got "${err.message}"`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
toRejectWithType: async (errorClass) => {
|
|
77
|
+
const [err] = await getResult();
|
|
78
|
+
if (!err) {
|
|
79
|
+
throw new Error(`Expected task to reject with ${errorClass.name}, but resolved successfully`);
|
|
80
|
+
}
|
|
81
|
+
if (!(err instanceof errorClass)) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Expected error to be instance of ${errorClass.name}, but got ${err.constructor.name}`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
result: getResult
|
|
88
|
+
};
|
|
89
|
+
return assertion;
|
|
90
|
+
}
|
|
91
|
+
async function assertResolves(task) {
|
|
92
|
+
const result = await task;
|
|
93
|
+
if (result[0]) {
|
|
94
|
+
throw new Error(`Expected task to resolve, but rejected: ${result[0].message}`);
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
async function assertRejects(task) {
|
|
99
|
+
const [err] = await task;
|
|
100
|
+
if (!err) {
|
|
101
|
+
throw new Error("Expected task to reject, but resolved successfully");
|
|
102
|
+
}
|
|
103
|
+
return err;
|
|
104
|
+
}
|
|
105
|
+
async function assertResolvesWithin(task, timeoutMs) {
|
|
106
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
reject(new Error(`Task did not resolve within ${timeoutMs}ms`));
|
|
109
|
+
}, timeoutMs);
|
|
110
|
+
});
|
|
111
|
+
return Promise.race([task, timeoutPromise]);
|
|
112
|
+
}
|
|
113
|
+
|
|
3
114
|
function createTimeController() {
|
|
4
115
|
let currentTime = 0;
|
|
5
116
|
const pendingTimeouts = [];
|
|
@@ -280,7 +391,8 @@ function createMockChannel() {
|
|
|
280
391
|
if (closed || receiveIndex >= receiveValues.length) {
|
|
281
392
|
return { done: true, value: void 0 };
|
|
282
393
|
}
|
|
283
|
-
|
|
394
|
+
const value = receiveValues[receiveIndex++];
|
|
395
|
+
return { done: false, value };
|
|
284
396
|
},
|
|
285
397
|
async return() {
|
|
286
398
|
return { done: true, value: void 0 };
|
|
@@ -439,4 +551,4 @@ function createTimeTravelController() {
|
|
|
439
551
|
};
|
|
440
552
|
}
|
|
441
553
|
|
|
442
|
-
export { assertScopeDisposed, createControlledTimer, createMockChannel, createMockScope, createSpy, createTestScope, createTimeController, createTimeTravelController, flushPromises };
|
|
554
|
+
export { assertRejects, assertResolves, assertResolvesWithin, assertScopeDisposed, createControlledTimer, createMockChannel, createMockScope, createSpy, createTestScope, createTimeController, createTimeTravelController, expectTask, flushPromises };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-go-scope/testing",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Testing utilities for go-go-scope - mocks, spies, and time control",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"go-go-scope": "2.
|
|
39
|
+
"go-go-scope": "2.3.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@biomejs/biome": "^2.4.4",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"build": "pkgroll --clean-dist",
|
|
50
50
|
"lint": "biome check --write src/",
|
|
51
51
|
"test": "echo 'No tests yet'",
|
|
52
|
-
"clean": "rm -rf dist"
|
|
52
|
+
"clean": "rm -rf dist",
|
|
53
|
+
"typecheck": "tsc --noEmit"
|
|
53
54
|
}
|
|
54
55
|
}
|