@ereo/testing 0.1.6

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.
@@ -0,0 +1,116 @@
1
+ /**
2
+ * @ereo/testing - Loader Testing
3
+ *
4
+ * Utilities for testing route loaders.
5
+ */
6
+ import type { LoaderFunction, RouteParams } from '@ereo/core';
7
+ import { type TestContextOptions, type TestContext } from './context';
8
+ import { type MockRequestOptions } from './request';
9
+ /**
10
+ * Options for testing a loader.
11
+ */
12
+ export interface LoaderTestOptions<P = RouteParams> {
13
+ /** Route parameters */
14
+ params?: P;
15
+ /** Request options */
16
+ request?: MockRequestOptions;
17
+ /** Context options */
18
+ context?: TestContextOptions;
19
+ }
20
+ /**
21
+ * Result of testing a loader.
22
+ */
23
+ export interface LoaderTestResult<T = unknown> {
24
+ /** The loader's return value */
25
+ data: T;
26
+ /** The test context (for inspection) */
27
+ context: TestContext;
28
+ /** The request used */
29
+ request: Request;
30
+ /** Execution time in milliseconds */
31
+ duration: number;
32
+ }
33
+ /**
34
+ * Test a loader function directly.
35
+ *
36
+ * @example
37
+ * import { testLoader } from '@ereo/testing';
38
+ * import { loader } from './routes/blog/[slug]';
39
+ *
40
+ * test('loads blog post', async () => {
41
+ * const result = await testLoader(loader, {
42
+ * params: { slug: 'my-post' },
43
+ * });
44
+ *
45
+ * expect(result.data.title).toBe('My Post');
46
+ * expect(result.context.getCacheOperations()).toHaveLength(1);
47
+ * });
48
+ */
49
+ export declare function testLoader<T = unknown, P = RouteParams>(loader: LoaderFunction<T, P>, options?: LoaderTestOptions<P>): Promise<LoaderTestResult<T>>;
50
+ /**
51
+ * Create a reusable loader tester with preset options.
52
+ *
53
+ * @example
54
+ * const testPostLoader = createLoaderTester(loader, {
55
+ * context: { store: { user: testUser } },
56
+ * });
57
+ *
58
+ * test('loads post with user context', async () => {
59
+ * const result = await testPostLoader({ params: { slug: 'test' } });
60
+ * expect(result.data).toBeDefined();
61
+ * });
62
+ */
63
+ export declare function createLoaderTester<T = unknown, P = RouteParams>(loader: LoaderFunction<T, P>, baseOptions?: LoaderTestOptions<P>): (overrides?: Partial<LoaderTestOptions<P>>) => Promise<LoaderTestResult<T>>;
64
+ /**
65
+ * Test multiple loaders in parallel (for testing combined loaders).
66
+ *
67
+ * @example
68
+ * const results = await testLoadersParallel([
69
+ * { loader: userLoader, params: { id: '1' } },
70
+ * { loader: postsLoader, params: {} },
71
+ * ]);
72
+ */
73
+ export declare function testLoadersParallel<T extends unknown[] = unknown[]>(loaders: Array<{
74
+ loader: LoaderFunction<T[number]>;
75
+ params?: RouteParams;
76
+ request?: MockRequestOptions;
77
+ context?: TestContextOptions;
78
+ }>): Promise<LoaderTestResult<T[number]>[]>;
79
+ /**
80
+ * Test loader with multiple param combinations.
81
+ *
82
+ * @example
83
+ * const results = await testLoaderMatrix(loader, {
84
+ * params: [
85
+ * { slug: 'post-1' },
86
+ * { slug: 'post-2' },
87
+ * { slug: 'non-existent' },
88
+ * ],
89
+ * });
90
+ *
91
+ * expect(results[0].data).toBeDefined();
92
+ * expect(results[2].data).toBeNull();
93
+ */
94
+ export declare function testLoaderMatrix<T = unknown, P = RouteParams>(loader: LoaderFunction<T, P>, options: {
95
+ params: P[];
96
+ request?: MockRequestOptions;
97
+ context?: TestContextOptions;
98
+ }): Promise<LoaderTestResult<T>[]>;
99
+ /**
100
+ * Test loader error handling.
101
+ *
102
+ * @example
103
+ * test('handles missing post', async () => {
104
+ * const result = await testLoaderError(loader, {
105
+ * params: { slug: 'non-existent' },
106
+ * });
107
+ *
108
+ * expect(result.error).toBeInstanceOf(NotFoundError);
109
+ * });
110
+ */
111
+ export declare function testLoaderError<P = RouteParams>(loader: LoaderFunction<unknown, P>, options?: LoaderTestOptions<P>): Promise<{
112
+ error: Error | null;
113
+ context: TestContext;
114
+ request: Request;
115
+ }>;
116
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAqB,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AACzF,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,WAAW;IAChD,uBAAuB;IACvB,MAAM,CAAC,EAAE,CAAC,CAAC;IACX,sBAAsB;IACtB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,sBAAsB;IACtB,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,OAAO;IAC3C,gCAAgC;IAChC,IAAI,EAAE,CAAC,CAAC;IACR,wCAAwC;IACxC,OAAO,EAAE,WAAW,CAAC;IACrB,uBAAuB;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,WAAW,EAC3D,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,EAC5B,OAAO,GAAE,iBAAiB,CAAC,CAAC,CAAM,GACjC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAe9B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,WAAW,EAC7D,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,EAC5B,WAAW,GAAE,iBAAiB,CAAC,CAAC,CAAM,IAExB,YAAW,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAM,KAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAc3F;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,OAAO,EAAE,GAAG,OAAO,EAAE,EACvE,OAAO,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B,CAAC,GACD,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAMxC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,WAAW,EACjE,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,EAC5B,OAAO,EAAE;IACP,MAAM,EAAE,CAAC,EAAE,CAAC;IACZ,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B,GACA,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAUhC;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,WAAW,EACnD,MAAM,EAAE,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,EAClC,OAAO,GAAE,iBAAiB,CAAC,CAAC,CAAM,GACjC,OAAO,CAAC;IACT,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC,CAeD"}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * @ereo/testing - Middleware Testing
3
+ *
4
+ * Utilities for testing middleware functions.
5
+ */
6
+ import type { MiddlewareHandler, NextFunction } from '@ereo/core';
7
+ import { type TestContextOptions, type TestContext } from './context';
8
+ import { type MockRequestOptions } from './request';
9
+ /**
10
+ * Options for testing middleware.
11
+ */
12
+ export interface MiddlewareTestOptions {
13
+ /** Request options */
14
+ request?: MockRequestOptions;
15
+ /** Context options */
16
+ context?: TestContextOptions;
17
+ /** Custom next function (defaults to returning 200 OK) */
18
+ next?: NextFunction;
19
+ /** Expected response from next (for pass-through testing) */
20
+ nextResponse?: Response;
21
+ }
22
+ /**
23
+ * Result of testing middleware.
24
+ */
25
+ export interface MiddlewareTestResult {
26
+ /** The response returned by middleware */
27
+ response: Response;
28
+ /** The test context (for inspection) */
29
+ context: TestContext;
30
+ /** The request used */
31
+ request: Request;
32
+ /** Whether next() was called */
33
+ nextCalled: boolean;
34
+ /** How many times next() was called */
35
+ nextCallCount: number;
36
+ /** Execution time in milliseconds */
37
+ duration: number;
38
+ }
39
+ /**
40
+ * Test a middleware function directly.
41
+ *
42
+ * @example
43
+ * import { testMiddleware } from '@ereo/testing';
44
+ * import { authMiddleware } from './middleware/auth';
45
+ *
46
+ * test('blocks unauthenticated requests', async () => {
47
+ * const result = await testMiddleware(authMiddleware, {
48
+ * request: { url: '/admin' },
49
+ * });
50
+ *
51
+ * expect(result.response.status).toBe(401);
52
+ * expect(result.nextCalled).toBe(false);
53
+ * });
54
+ *
55
+ * test('allows authenticated requests', async () => {
56
+ * const result = await testMiddleware(authMiddleware, {
57
+ * request: {
58
+ * url: '/admin',
59
+ * headers: { Authorization: 'Bearer valid-token' },
60
+ * },
61
+ * });
62
+ *
63
+ * expect(result.nextCalled).toBe(true);
64
+ * expect(result.response.status).toBe(200);
65
+ * });
66
+ */
67
+ export declare function testMiddleware(middleware: MiddlewareHandler, options?: MiddlewareTestOptions): Promise<MiddlewareTestResult>;
68
+ /**
69
+ * Create a reusable middleware tester with preset options.
70
+ *
71
+ * @example
72
+ * const testAuth = createMiddlewareTester(authMiddleware, {
73
+ * context: { env: { AUTH_SECRET: 'test-secret' } },
74
+ * });
75
+ *
76
+ * test('allows valid tokens', async () => {
77
+ * const result = await testAuth({
78
+ * request: { headers: { Authorization: 'Bearer valid' } },
79
+ * });
80
+ * expect(result.nextCalled).toBe(true);
81
+ * });
82
+ */
83
+ export declare function createMiddlewareTester(middleware: MiddlewareHandler, baseOptions?: MiddlewareTestOptions): (overrides?: Partial<MiddlewareTestOptions>) => Promise<MiddlewareTestResult>;
84
+ /**
85
+ * Test a chain of middleware functions.
86
+ *
87
+ * @example
88
+ * const result = await testMiddlewareChain([
89
+ * loggingMiddleware,
90
+ * authMiddleware,
91
+ * rateLimitMiddleware,
92
+ * ], {
93
+ * request: { url: '/api/data' },
94
+ * });
95
+ *
96
+ * expect(result.response.status).toBe(200);
97
+ * expect(result.middlewareResults[0].nextCalled).toBe(true);
98
+ */
99
+ export declare function testMiddlewareChain(middlewares: MiddlewareHandler[], options?: MiddlewareTestOptions): Promise<{
100
+ response: Response;
101
+ context: TestContext;
102
+ request: Request;
103
+ middlewareResults: Array<{
104
+ index: number;
105
+ nextCalled: boolean;
106
+ duration: number;
107
+ }>;
108
+ }>;
109
+ /**
110
+ * Test middleware with multiple request scenarios.
111
+ *
112
+ * @example
113
+ * const results = await testMiddlewareMatrix(authMiddleware, {
114
+ * requests: [
115
+ * { url: '/public' },
116
+ * { url: '/admin', headers: { Authorization: 'Bearer valid' } },
117
+ * { url: '/admin' }, // No auth
118
+ * ],
119
+ * });
120
+ *
121
+ * expect(results[0].response.status).toBe(200);
122
+ * expect(results[1].response.status).toBe(200);
123
+ * expect(results[2].response.status).toBe(401);
124
+ */
125
+ export declare function testMiddlewareMatrix(middleware: MiddlewareHandler, options: {
126
+ requests: MockRequestOptions[];
127
+ context?: TestContextOptions;
128
+ }): Promise<MiddlewareTestResult[]>;
129
+ /**
130
+ * Test middleware error handling.
131
+ *
132
+ * @example
133
+ * test('handles errors gracefully', async () => {
134
+ * const result = await testMiddlewareError(errorMiddleware, {
135
+ * next: async () => {
136
+ * throw new Error('Downstream error');
137
+ * },
138
+ * });
139
+ *
140
+ * expect(result.response.status).toBe(500);
141
+ * expect(result.error).toBeNull(); // Middleware caught the error
142
+ * });
143
+ */
144
+ export declare function testMiddlewareError(middleware: MiddlewareHandler, options: MiddlewareTestOptions & {
145
+ next: NextFunction;
146
+ }): Promise<{
147
+ response: Response | null;
148
+ error: Error | null;
149
+ context: TestContext;
150
+ }>;
151
+ /**
152
+ * Test that middleware modifies context correctly.
153
+ *
154
+ * @example
155
+ * test('sets user in context', async () => {
156
+ * const result = await testMiddlewareContext(authMiddleware, {
157
+ * request: { headers: { Authorization: 'Bearer valid' } },
158
+ * expectContextValues: {
159
+ * user: { id: expect.any(String), role: 'user' },
160
+ * },
161
+ * });
162
+ *
163
+ * expect(result.contextMatches).toBe(true);
164
+ * });
165
+ */
166
+ export declare function testMiddlewareContext(middleware: MiddlewareHandler, options: MiddlewareTestOptions & {
167
+ expectContextValues: Record<string, unknown>;
168
+ }): Promise<{
169
+ response: Response;
170
+ context: TestContext;
171
+ contextMatches: boolean;
172
+ contextDiff: Record<string, {
173
+ expected: unknown;
174
+ actual: unknown;
175
+ }>;
176
+ }>;
177
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * @ereo/testing - Component/Route Rendering
3
+ *
4
+ * Utilities for testing route components with their loaders.
5
+ */
6
+ import type { ComponentType, ReactElement } from 'react';
7
+ import type { RouteParams, RouteComponentProps, RouteModule } from '@ereo/core';
8
+ import { type TestContextOptions, type TestContext } from './context';
9
+ import { type MockRequestOptions } from './request';
10
+ /**
11
+ * Options for rendering a route.
12
+ */
13
+ export interface RenderRouteOptions<P = RouteParams> {
14
+ /** Route parameters */
15
+ params?: P;
16
+ /** Request options */
17
+ request?: MockRequestOptions;
18
+ /** Context options */
19
+ context?: TestContextOptions;
20
+ /** Initial loader data (skip loader execution) */
21
+ loaderData?: unknown;
22
+ /** Children to render */
23
+ children?: ReactElement;
24
+ }
25
+ /**
26
+ * Result of rendering a route.
27
+ */
28
+ export interface RenderResult<T = unknown> {
29
+ /** The rendered element */
30
+ element: ReactElement;
31
+ /** The loader data used */
32
+ loaderData: T;
33
+ /** The test context */
34
+ context: TestContext;
35
+ /** The request used */
36
+ request: Request;
37
+ /** Props passed to the component */
38
+ props: RouteComponentProps<T>;
39
+ }
40
+ /**
41
+ * Render a route component with its loader data.
42
+ *
43
+ * @example
44
+ * import { renderRoute } from '@ereo/testing';
45
+ * import { default as BlogPost, loader } from './routes/blog/[slug]';
46
+ *
47
+ * test('renders blog post', async () => {
48
+ * const result = await renderRoute(
49
+ * { default: BlogPost, loader },
50
+ * { params: { slug: 'my-post' } }
51
+ * );
52
+ *
53
+ * // Use with your testing library
54
+ * const { getByText } = render(result.element);
55
+ * expect(getByText('My Post')).toBeInTheDocument();
56
+ * });
57
+ */
58
+ export declare function renderRoute<T = unknown, P = RouteParams>(module: RouteModule, options?: RenderRouteOptions<P>): Promise<RenderResult<T>>;
59
+ /**
60
+ * Create a reusable route renderer.
61
+ *
62
+ * @example
63
+ * const renderBlogPost = createRouteRenderer(
64
+ * { default: BlogPost, loader },
65
+ * { context: { store: { user: testUser } } }
66
+ * );
67
+ *
68
+ * test('renders for authenticated user', async () => {
69
+ * const result = await renderBlogPost({ params: { slug: 'test' } });
70
+ * // ...
71
+ * });
72
+ */
73
+ export declare function createRouteRenderer<T = unknown, P = RouteParams>(module: RouteModule, baseOptions?: RenderRouteOptions<P>): (overrides?: Partial<RenderRouteOptions<P>>) => Promise<RenderResult<T>>;
74
+ /**
75
+ * Render a standalone component with props.
76
+ *
77
+ * @example
78
+ * const element = renderComponent(Counter, { count: 5 });
79
+ */
80
+ export declare function renderComponent<P extends object>(Component: ComponentType<P>, props: P): ReactElement;
81
+ /**
82
+ * Render a route with multiple param sets for snapshot testing.
83
+ *
84
+ * @example
85
+ * const renders = await renderRouteMatrix(
86
+ * { default: BlogPost, loader },
87
+ * {
88
+ * params: [
89
+ * { slug: 'post-1' },
90
+ * { slug: 'post-2' },
91
+ * ],
92
+ * }
93
+ * );
94
+ *
95
+ * renders.forEach((result, index) => {
96
+ * expect(result.element).toMatchSnapshot(`render-${index}`);
97
+ * });
98
+ */
99
+ export declare function renderRouteMatrix<T = unknown, P = RouteParams>(module: RouteModule, options: {
100
+ params: P[];
101
+ request?: MockRequestOptions;
102
+ context?: TestContextOptions;
103
+ }): Promise<RenderResult<T>[]>;
104
+ /**
105
+ * Test that a route renders without throwing.
106
+ *
107
+ * @example
108
+ * test('renders without errors', async () => {
109
+ * const result = await testRouteRenders(
110
+ * { default: BlogPost, loader },
111
+ * { params: { slug: 'test' } }
112
+ * );
113
+ *
114
+ * expect(result.renders).toBe(true);
115
+ * expect(result.error).toBeNull();
116
+ * });
117
+ */
118
+ export declare function testRouteRenders<P = RouteParams>(module: RouteModule, options?: RenderRouteOptions<P>): Promise<{
119
+ renders: boolean;
120
+ error: Error | null;
121
+ result: RenderResult | null;
122
+ }>;
123
+ /**
124
+ * Get the meta tags for a route.
125
+ *
126
+ * @example
127
+ * const meta = await getRouteMeta(
128
+ * { default: BlogPost, loader, meta: metaFn },
129
+ * { params: { slug: 'my-post' } }
130
+ * );
131
+ *
132
+ * expect(meta.find(m => m.title)).toEqual({ title: 'My Post' });
133
+ */
134
+ export declare function getRouteMeta<P = RouteParams>(module: RouteModule, options?: RenderRouteOptions<P>): Promise<ReturnType<NonNullable<RouteModule['meta']>>>;
135
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @ereo/testing - Request Utilities
3
+ *
4
+ * Create mock requests for testing.
5
+ */
6
+ /**
7
+ * Options for creating a mock request.
8
+ */
9
+ export interface MockRequestOptions {
10
+ /** HTTP method (default: GET) */
11
+ method?: string;
12
+ /** Request URL or path */
13
+ url?: string;
14
+ /** Request headers */
15
+ headers?: Record<string, string>;
16
+ /** Request body (for POST/PUT/PATCH) */
17
+ body?: BodyInit | Record<string, unknown>;
18
+ /** Query parameters */
19
+ searchParams?: Record<string, string | string[]>;
20
+ /** Form data */
21
+ formData?: Record<string, string | Blob>;
22
+ /** Cookies */
23
+ cookies?: Record<string, string>;
24
+ }
25
+ /**
26
+ * Create a mock Request object for testing.
27
+ *
28
+ * @example
29
+ * // Simple GET request
30
+ * const request = createMockRequest({ url: '/api/posts' });
31
+ *
32
+ * // POST with JSON body
33
+ * const request = createMockRequest({
34
+ * method: 'POST',
35
+ * url: '/api/posts',
36
+ * body: { title: 'Test Post' },
37
+ * });
38
+ *
39
+ * // POST with form data
40
+ * const request = createMockRequest({
41
+ * method: 'POST',
42
+ * url: '/api/login',
43
+ * formData: { email: 'test@example.com', password: 'secret' },
44
+ * });
45
+ */
46
+ export declare function createMockRequest(url?: string | MockRequestOptions, options?: MockRequestOptions): Request;
47
+ /**
48
+ * Create a POST request with form data (convenience function).
49
+ *
50
+ * @example
51
+ * const request = createFormRequest('/api/login', {
52
+ * email: 'test@example.com',
53
+ * password: 'secret',
54
+ * });
55
+ */
56
+ export declare function createFormRequest(url: string, data: Record<string, string | Blob>): Request;
57
+ /**
58
+ * Create mock FormData for testing.
59
+ *
60
+ * @example
61
+ * const formData = createMockFormData({
62
+ * email: 'test@example.com',
63
+ * password: 'secret',
64
+ * });
65
+ */
66
+ export declare function createMockFormData(data: Record<string, string | Blob | File>): FormData;
67
+ /**
68
+ * Create mock Headers for testing.
69
+ *
70
+ * @example
71
+ * const headers = createMockHeaders({
72
+ * 'Authorization': 'Bearer token123',
73
+ * 'Content-Type': 'application/json',
74
+ * });
75
+ */
76
+ export declare function createMockHeaders(data: Record<string, string>): Headers;
77
+ /**
78
+ * Parse JSON from a Response.
79
+ *
80
+ * @example
81
+ * const result = await testAction(action, options);
82
+ * const data = await parseJsonResponse<MyData>(result.response);
83
+ */
84
+ export declare function parseJsonResponse<T = unknown>(response: Response): Promise<T>;
85
+ /**
86
+ * Parse text from a Response.
87
+ */
88
+ export declare function parseTextResponse(response: Response): Promise<string>;
89
+ /**
90
+ * Create a mock File for testing file uploads.
91
+ *
92
+ * @example
93
+ * const file = createMockFile('test.txt', 'Hello World', 'text/plain');
94
+ */
95
+ export declare function createMockFile(name: string, content: string | Blob, type?: string): File;
96
+ /**
97
+ * Extract cookies from a Response.
98
+ *
99
+ * @example
100
+ * const cookies = extractCookies(response);
101
+ * expect(cookies.session).toBeDefined();
102
+ */
103
+ export declare function extractCookies(response: Response): Record<string, string>;
104
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sBAAsB;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,wCAAwC;IACxC,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,uBAAuB;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IACjD,gBAAgB;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,cAAc;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,kBAAkB,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAU1G;AAiED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAClC,OAAO,CAcT;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,GACzC,QAAQ,CAMV;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAEvE;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAOnF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAE3E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,IAAI,SAA6B,GAChC,IAAI,CAGN;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAazE"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @ereo/testing - Test Server
3
+ *
4
+ * Create a test server for integration testing.
5
+ */
6
+ import type { FrameworkConfig } from '@ereo/core';
7
+ /**
8
+ * Test server options.
9
+ */
10
+ export interface TestServerOptions {
11
+ /** Framework configuration */
12
+ config?: FrameworkConfig;
13
+ /** Port to run on (default: random available port) */
14
+ port?: number;
15
+ /** Routes directory */
16
+ routesDir?: string;
17
+ }
18
+ /**
19
+ * Test server interface.
20
+ */
21
+ export interface TestServer {
22
+ /** Server base URL */
23
+ url: string;
24
+ /** Port the server is running on */
25
+ port: number;
26
+ /** Make a request to the server */
27
+ fetch: (path: string, init?: RequestInit) => Promise<Response>;
28
+ /** Make a GET request */
29
+ get: (path: string, init?: Omit<RequestInit, 'method'>) => Promise<Response>;
30
+ /** Make a POST request */
31
+ post: (path: string, body?: unknown, init?: Omit<RequestInit, 'method' | 'body'>) => Promise<Response>;
32
+ /** Make a PUT request */
33
+ put: (path: string, body?: unknown, init?: Omit<RequestInit, 'method' | 'body'>) => Promise<Response>;
34
+ /** Make a DELETE request */
35
+ delete: (path: string, init?: Omit<RequestInit, 'method'>) => Promise<Response>;
36
+ /** Make a PATCH request */
37
+ patch: (path: string, body?: unknown, init?: Omit<RequestInit, 'method' | 'body'>) => Promise<Response>;
38
+ /** Submit a form */
39
+ submitForm: (path: string, formData: Record<string, string>, init?: Omit<RequestInit, 'method' | 'body'>) => Promise<Response>;
40
+ /** Stop the server */
41
+ stop: () => Promise<void>;
42
+ }
43
+ /**
44
+ * Create a test server for integration testing.
45
+ *
46
+ * @example
47
+ * import { createTestServer } from '@ereo/testing';
48
+ *
49
+ * describe('API routes', () => {
50
+ * let server: TestServer;
51
+ *
52
+ * beforeAll(async () => {
53
+ * server = await createTestServer({
54
+ * routesDir: './app/routes',
55
+ * });
56
+ * });
57
+ *
58
+ * afterAll(async () => {
59
+ * await server.stop();
60
+ * });
61
+ *
62
+ * test('GET /api/posts', async () => {
63
+ * const response = await server.get('/api/posts');
64
+ * expect(response.status).toBe(200);
65
+ *
66
+ * const posts = await response.json();
67
+ * expect(posts).toHaveLength(3);
68
+ * });
69
+ *
70
+ * test('POST /api/posts', async () => {
71
+ * const response = await server.post('/api/posts', {
72
+ * title: 'New Post',
73
+ * content: 'Content here',
74
+ * });
75
+ * expect(response.status).toBe(201);
76
+ * });
77
+ * });
78
+ */
79
+ export declare function createTestServer(options?: TestServerOptions): Promise<TestServer>;
80
+ /**
81
+ * Create a simple mock server for external API testing.
82
+ *
83
+ * @example
84
+ * const mockApi = await createMockServer({
85
+ * routes: {
86
+ * 'GET /users/1': { id: 1, name: 'Test User' },
87
+ * 'POST /users': (req) => ({ id: 2, ...req.body }),
88
+ * 'GET /users': [{ id: 1 }, { id: 2 }],
89
+ * },
90
+ * });
91
+ *
92
+ * // In your test, use mockApi.url as the API base URL
93
+ * process.env.API_URL = mockApi.url;
94
+ *
95
+ * // After test
96
+ * await mockApi.stop();
97
+ */
98
+ export declare function createMockServer(options: {
99
+ routes: Record<string, unknown | ((request: {
100
+ body?: unknown;
101
+ params?: Record<string, string>;
102
+ }) => unknown)>;
103
+ port?: number;
104
+ }): Promise<{
105
+ url: string;
106
+ port: number;
107
+ stop: () => Promise<void>;
108
+ }>;
109
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,8BAA8B;IAC9B,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,sBAAsB;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/D,yBAAyB;IACzB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7E,0BAA0B;IAC1B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,MAAM,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvG,yBAAyB;IACzB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,MAAM,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtG,4BAA4B;IAC5B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChF,2BAA2B;IAC3B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,MAAM,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxG,oBAAoB;IACpB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,MAAM,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/H,sBAAsB;IACtB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiH3F;AAoBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC9C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,CAAC,CAAC;IAC9G,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAgDpE"}