@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 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
- return { done: false, value: receiveValues[receiveIndex++] };
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.1.0",
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.1.0"
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
  }