@grest-ts/testkit 0.0.6 → 0.0.7

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 (46) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +413 -413
  3. package/dist/src/runner/isolated-loader.mjs +91 -91
  4. package/dist/src/runner/worker-loader.mjs +49 -49
  5. package/dist/tsconfig.publish.tsbuildinfo +1 -1
  6. package/package.json +12 -12
  7. package/src/GGBundleTest.ts +89 -89
  8. package/src/GGTest.ts +318 -318
  9. package/src/GGTestContext.ts +74 -74
  10. package/src/GGTestRunner.ts +308 -308
  11. package/src/GGTestRuntime.ts +265 -265
  12. package/src/GGTestRuntimeWorker.ts +159 -159
  13. package/src/GGTestSharedRef.ts +116 -116
  14. package/src/GGTestkitExtensionsDiscovery.ts +26 -26
  15. package/src/IGGLocalDiscoveryServer.ts +16 -16
  16. package/src/callOn/GGCallOnSelector.ts +61 -61
  17. package/src/callOn/GGContractClass.implement.ts +43 -43
  18. package/src/callOn/GGTestActionForLocatorOnCall.ts +134 -134
  19. package/src/callOn/TestableIPC.ts +81 -81
  20. package/src/callOn/callOn.ts +224 -224
  21. package/src/callOn/registerOnCallHandler.ts +123 -123
  22. package/src/index-node.ts +64 -64
  23. package/src/mockable/GGMockable.ts +22 -22
  24. package/src/mockable/GGMockableCall.ts +45 -45
  25. package/src/mockable/GGMockableIPC.ts +20 -20
  26. package/src/mockable/GGMockableInterceptor.ts +44 -44
  27. package/src/mockable/GGMockableInterceptorsServer.ts +69 -69
  28. package/src/mockable/mockable.ts +71 -71
  29. package/src/runner/InlineRunner.ts +47 -47
  30. package/src/runner/IsolatedRunner.ts +179 -179
  31. package/src/runner/RuntimeRunner.ts +15 -15
  32. package/src/runner/WorkerRunner.ts +179 -179
  33. package/src/runner/isolated-loader.mjs +91 -91
  34. package/src/runner/worker-loader.mjs +49 -49
  35. package/src/testers/GGCallInterceptor.ts +224 -224
  36. package/src/testers/GGMockWith.ts +92 -92
  37. package/src/testers/GGSpyWith.ts +115 -115
  38. package/src/testers/GGTestAction.ts +332 -332
  39. package/src/testers/GGTestComponent.ts +16 -16
  40. package/src/testers/GGTestSelector.ts +223 -223
  41. package/src/testers/IGGTestInterceptor.ts +10 -10
  42. package/src/testers/IGGTestWith.ts +15 -15
  43. package/src/testers/RuntimeSelector.ts +151 -151
  44. package/src/utils/GGExpectations.ts +78 -78
  45. package/src/utils/GGTestError.ts +36 -36
  46. package/src/utils/captureStack.ts +53 -53
@@ -1,69 +1,69 @@
1
- import type {GGMockableInterceptor} from "./GGMockableInterceptor";
2
- import {GGTestComponent} from "../testers/GGTestComponent";
3
- import {GGTestRunner} from "../GGTestRunner";
4
- import {GGMockableIPC} from "./GGMockableIPC";
5
-
6
- export const CALL_THROUGH = "__spyCallThrough|migo0am5g0htea";
7
-
8
- export class GGMockableInterceptorsServer implements GGTestComponent {
9
-
10
- private readonly interceptors: Map<string, GGMockableInterceptor> = new Map();
11
-
12
- constructor(runner: GGTestRunner) {
13
- const server = runner.ipcServer;
14
- server.onFrameworkMessage(GGMockableIPC.testServer.call, async (body) => {
15
- const key = body.className + "." + body.methodName;
16
- const handler = this.interceptors.get(key);
17
-
18
- if (!handler) {
19
- // No mock configured - call through to real implementation
20
- // This allows testable() to invoke real methods on @mockable services
21
- // throw new Error(
22
- // `Expected handler to be set for mockable '${key}'!\n` +
23
- // "Did you forget to call .with(...)?"
24
- // );
25
- return CALL_THROUGH;
26
- }
27
-
28
- // onRequest validates input and returns mock data (or undefined for spy)
29
- const result = await handler.onRequest(body.callArgs);
30
-
31
- if (handler.passThrough) {
32
- // Spy mode - signal worker to call through
33
- return CALL_THROUGH;
34
- } else {
35
- // Mock mode - return the mock data
36
- return result;
37
- }
38
- });
39
-
40
- server.onFrameworkMessage(GGMockableIPC.testServer.spyResult, async (body) => {
41
- const key = body.className + "." + body.methodName;
42
- const handler = this.interceptors.get(key);
43
-
44
- if (!handler || !handler.passThrough) {
45
- // No spy handler configured - just ignore the result
46
- // This allows testable() to call through without requiring spyOn()
47
- // throw new Error(`Expected spy handler for '${key}'`);
48
- return;
49
- }
50
-
51
- // Validate the response from the real implementation
52
- await handler.onResponse(body.callResult);
53
- });
54
- }
55
-
56
- public addInterceptor(interceptor: GGMockableInterceptor) {
57
- this.interceptors.set(interceptor.getKey(), interceptor);
58
- }
59
-
60
- public deleteInterceptor(interceptor: GGMockableInterceptor) {
61
- this.interceptors.delete(interceptor.getKey());
62
- }
63
-
64
- public async teardown(): Promise<void> {
65
- this.interceptors.clear();
66
- }
67
- }
68
-
69
- GGTestRunner.registerExtension(GGMockableInterceptorsServer);
1
+ import type {GGMockableInterceptor} from "./GGMockableInterceptor";
2
+ import {GGTestComponent} from "../testers/GGTestComponent";
3
+ import {GGTestRunner} from "../GGTestRunner";
4
+ import {GGMockableIPC} from "./GGMockableIPC";
5
+
6
+ export const CALL_THROUGH = "__spyCallThrough|migo0am5g0htea";
7
+
8
+ export class GGMockableInterceptorsServer implements GGTestComponent {
9
+
10
+ private readonly interceptors: Map<string, GGMockableInterceptor> = new Map();
11
+
12
+ constructor(runner: GGTestRunner) {
13
+ const server = runner.ipcServer;
14
+ server.onFrameworkMessage(GGMockableIPC.testServer.call, async (body) => {
15
+ const key = body.className + "." + body.methodName;
16
+ const handler = this.interceptors.get(key);
17
+
18
+ if (!handler) {
19
+ // No mock configured - call through to real implementation
20
+ // This allows testable() to invoke real methods on @mockable services
21
+ // throw new Error(
22
+ // `Expected handler to be set for mockable '${key}'!\n` +
23
+ // "Did you forget to call .with(...)?"
24
+ // );
25
+ return CALL_THROUGH;
26
+ }
27
+
28
+ // onRequest validates input and returns mock data (or undefined for spy)
29
+ const result = await handler.onRequest(body.callArgs);
30
+
31
+ if (handler.passThrough) {
32
+ // Spy mode - signal worker to call through
33
+ return CALL_THROUGH;
34
+ } else {
35
+ // Mock mode - return the mock data
36
+ return result;
37
+ }
38
+ });
39
+
40
+ server.onFrameworkMessage(GGMockableIPC.testServer.spyResult, async (body) => {
41
+ const key = body.className + "." + body.methodName;
42
+ const handler = this.interceptors.get(key);
43
+
44
+ if (!handler || !handler.passThrough) {
45
+ // No spy handler configured - just ignore the result
46
+ // This allows testable() to call through without requiring spyOn()
47
+ // throw new Error(`Expected spy handler for '${key}'`);
48
+ return;
49
+ }
50
+
51
+ // Validate the response from the real implementation
52
+ await handler.onResponse(body.callResult);
53
+ });
54
+ }
55
+
56
+ public addInterceptor(interceptor: GGMockableInterceptor) {
57
+ this.interceptors.set(interceptor.getKey(), interceptor);
58
+ }
59
+
60
+ public deleteInterceptor(interceptor: GGMockableInterceptor) {
61
+ this.interceptors.delete(interceptor.getKey());
62
+ }
63
+
64
+ public async teardown(): Promise<void> {
65
+ this.interceptors.clear();
66
+ }
67
+ }
68
+
69
+ GGTestRunner.registerExtension(GGMockableInterceptorsServer);
@@ -1,71 +1,71 @@
1
- /**
2
- * Mockable decorator and test helpers.
3
- *
4
- * The @mockable decorator is re-exported from mockable-runtime.ts for production use.
5
- * mockBy() and spyOn() are test-only utilities that require the full testkit.
6
- */
7
-
8
- // Test-only imports - these pull in testkit infrastructure
9
- import {GGMockableInterceptor} from "./GGMockableInterceptor";
10
- import {GGMockWith} from "../testers/GGMockWith";
11
- import {GGSpyWith} from "../testers/GGSpyWith";
12
-
13
- // ============================================================================
14
- // Type helpers for mockBy/spyOn
15
- // ============================================================================
16
-
17
- /**
18
- * Type for mock access - maps class methods to GGMockWith
19
- */
20
- type MockAccess<T> = {
21
- [K in keyof T]: T[K] extends (...args: infer A) => Promise<infer R>
22
- ? GGMockWith<A extends [infer Single] ? (Single extends object ? Single : Record<string, Single>) : Record<string, any>, Awaited<R>, never>
23
- : never
24
- };
25
-
26
- /**
27
- * Type for spy access - maps class methods to GGSpyWith
28
- */
29
- type SpyAccess<T> = {
30
- [K in keyof T]: T[K] extends (...args: infer A) => Promise<infer R>
31
- ? GGSpyWith<A extends [infer Single] ? (Single extends object ? Single : Record<string, Single>) : Record<string, any>, Awaited<R>, never>
32
- : never
33
- };
34
-
35
- /**
36
- * Get mock access for a @mockable class.
37
- * Use this to create mock expectations in tests.
38
- *
39
- * @example
40
- * ```typescript
41
- * .with(mockOf(AddressResolverService).resolveAddress
42
- * .toEqual({address: "123 Main St"})
43
- * .andReturn({lat: 40.7, lng: -74.0}))
44
- * ```
45
- */
46
- export function mockOf<T>(cls: new (...args: any[]) => T): MockAccess<T> {
47
- return new Proxy({} as any, {
48
- get(_, methodName: string) {
49
- return new GGMockWith(GGMockableInterceptor, {className: cls.name, methodName})
50
- }
51
- });
52
- }
53
-
54
- /**
55
- * Get spy access for a @mockable class.
56
- * Use this to create spy expectations in tests - the real method will be called.
57
- *
58
- * @example
59
- * ```typescript
60
- * .with(spyOn(AddressResolverService).resolveAddress
61
- * .toEqual({address: "123 Main St"})
62
- * .responseToMatchObject({lat: 40.7}))
63
- * ```
64
- */
65
- export function spyOn<T>(cls: new (...args: any[]) => T): SpyAccess<T> {
66
- return new Proxy({} as any, {
67
- get(_, methodName: string) {
68
- return new GGSpyWith(GGMockableInterceptor, {className: cls.name, methodName})
69
- }
70
- });
71
- }
1
+ /**
2
+ * Mockable decorator and test helpers.
3
+ *
4
+ * The @mockable decorator is re-exported from mockable-runtime.ts for production use.
5
+ * mockBy() and spyOn() are test-only utilities that require the full testkit.
6
+ */
7
+
8
+ // Test-only imports - these pull in testkit infrastructure
9
+ import {GGMockableInterceptor} from "./GGMockableInterceptor";
10
+ import {GGMockWith} from "../testers/GGMockWith";
11
+ import {GGSpyWith} from "../testers/GGSpyWith";
12
+
13
+ // ============================================================================
14
+ // Type helpers for mockBy/spyOn
15
+ // ============================================================================
16
+
17
+ /**
18
+ * Type for mock access - maps class methods to GGMockWith
19
+ */
20
+ type MockAccess<T> = {
21
+ [K in keyof T]: T[K] extends (...args: infer A) => Promise<infer R>
22
+ ? GGMockWith<A extends [infer Single] ? (Single extends object ? Single : Record<string, Single>) : Record<string, any>, Awaited<R>, never>
23
+ : never
24
+ };
25
+
26
+ /**
27
+ * Type for spy access - maps class methods to GGSpyWith
28
+ */
29
+ type SpyAccess<T> = {
30
+ [K in keyof T]: T[K] extends (...args: infer A) => Promise<infer R>
31
+ ? GGSpyWith<A extends [infer Single] ? (Single extends object ? Single : Record<string, Single>) : Record<string, any>, Awaited<R>, never>
32
+ : never
33
+ };
34
+
35
+ /**
36
+ * Get mock access for a @mockable class.
37
+ * Use this to create mock expectations in tests.
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * .with(mockOf(AddressResolverService).resolveAddress
42
+ * .toEqual({address: "123 Main St"})
43
+ * .andReturn({lat: 40.7, lng: -74.0}))
44
+ * ```
45
+ */
46
+ export function mockOf<T>(cls: new (...args: any[]) => T): MockAccess<T> {
47
+ return new Proxy({} as any, {
48
+ get(_, methodName: string) {
49
+ return new GGMockWith(GGMockableInterceptor, {className: cls.name, methodName})
50
+ }
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Get spy access for a @mockable class.
56
+ * Use this to create spy expectations in tests - the real method will be called.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * .with(spyOn(AddressResolverService).resolveAddress
61
+ * .toEqual({address: "123 Main St"})
62
+ * .responseToMatchObject({lat: 40.7}))
63
+ * ```
64
+ */
65
+ export function spyOn<T>(cls: new (...args: any[]) => T): SpyAccess<T> {
66
+ return new Proxy({} as any, {
67
+ get(_, methodName: string) {
68
+ return new GGSpyWith(GGMockableInterceptor, {className: cls.name, methodName})
69
+ }
70
+ });
71
+ }
@@ -1,47 +1,47 @@
1
- import type {RuntimeRunner} from "./RuntimeRunner";
2
- import {GGTestRuntimeWorker} from "../GGTestRuntimeWorker";
3
- import {GGTestEnvConfig} from "../GGTestRuntime";
4
- import {GGLocatorScope} from "@grest-ts/locator";
5
-
6
- export class InlineRunner implements RuntimeRunner {
7
-
8
- private controlClient?: GGTestRuntimeWorker;
9
-
10
- constructor(
11
- private readonly config: GGTestEnvConfig,
12
- private readonly runtimeFactory?: () => any
13
- ) {
14
- }
15
-
16
- async start(): Promise<void> {
17
- // Use setTimeout + enterBlank() to create a fully isolated async context.
18
- // This gives the inline runtime its own context tree where GGLog.init()
19
- // creates an independent log context, similar to how Worker/Isolated modes
20
- // naturally get separate contexts by running in different threads/processes.
21
- const factory = this.runtimeFactory;
22
- await new Promise<void>((resolve, reject) => {
23
- setTimeout(async () => {
24
- try {
25
- // Create blank context - inline runtime should not inherit test context
26
- new GGLocatorScope("GGInlineRunner").enter();
27
- this.controlClient = new GGTestRuntimeWorker(this.config);
28
- // Pass factory to avoid dynamic import() which causes duplicate
29
- // module loading in Vite/vitest environments
30
- await this.controlClient.start(factory);
31
- resolve();
32
- } catch (err) {
33
- reject(err);
34
- }
35
- }, 0);
36
- });
37
- }
38
-
39
- async stopRuntime(): Promise<void> {
40
- await this.controlClient?.stopRuntime();
41
- }
42
-
43
- async shutdown(): Promise<void> {
44
- await this.controlClient?.shutdown();
45
- this.controlClient = undefined;
46
- }
47
- }
1
+ import type {RuntimeRunner} from "./RuntimeRunner";
2
+ import {GGTestRuntimeWorker} from "../GGTestRuntimeWorker";
3
+ import {GGTestEnvConfig} from "../GGTestRuntime";
4
+ import {GGLocatorScope} from "@grest-ts/locator";
5
+
6
+ export class InlineRunner implements RuntimeRunner {
7
+
8
+ private controlClient?: GGTestRuntimeWorker;
9
+
10
+ constructor(
11
+ private readonly config: GGTestEnvConfig,
12
+ private readonly runtimeFactory?: () => any
13
+ ) {
14
+ }
15
+
16
+ async start(): Promise<void> {
17
+ // Use setTimeout + enterBlank() to create a fully isolated async context.
18
+ // This gives the inline runtime its own context tree where GGLog.init()
19
+ // creates an independent log context, similar to how Worker/Isolated modes
20
+ // naturally get separate contexts by running in different threads/processes.
21
+ const factory = this.runtimeFactory;
22
+ await new Promise<void>((resolve, reject) => {
23
+ setTimeout(async () => {
24
+ try {
25
+ // Create blank context - inline runtime should not inherit test context
26
+ new GGLocatorScope("GGInlineRunner").enter();
27
+ this.controlClient = new GGTestRuntimeWorker(this.config);
28
+ // Pass factory to avoid dynamic import() which causes duplicate
29
+ // module loading in Vite/vitest environments
30
+ await this.controlClient.start(factory);
31
+ resolve();
32
+ } catch (err) {
33
+ reject(err);
34
+ }
35
+ }, 0);
36
+ });
37
+ }
38
+
39
+ async stopRuntime(): Promise<void> {
40
+ await this.controlClient?.stopRuntime();
41
+ }
42
+
43
+ async shutdown(): Promise<void> {
44
+ await this.controlClient?.shutdown();
45
+ this.controlClient = undefined;
46
+ }
47
+ }