@reckona/mreact-test-utils 0.0.160 → 0.0.162
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.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +16 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { type Cell, type ReadonlyCell } from "@reckona/mreact-reactive-core";
|
|
2
2
|
import { type RenderValue } from "@reckona/mreact-reactive-dom";
|
|
3
3
|
import { type RenderAppRequestOptions } from "@reckona/mreact-router";
|
|
4
|
+
/** Temporary app fixture for rendering and writing app-router files in tests. */
|
|
4
5
|
export interface AppFixture {
|
|
5
6
|
readonly appDir: string;
|
|
6
7
|
render(path: string, options?: AppFixtureRenderOptions | undefined): Promise<Response>;
|
|
7
8
|
write(path: string, contents: string): Promise<void>;
|
|
8
9
|
}
|
|
10
|
+
/** Options passed when rendering a request with an app fixture. */
|
|
9
11
|
export type AppFixtureRenderOptions = Omit<RenderAppRequestOptions, "appDir" | "request"> & {
|
|
10
12
|
request?: RequestInit | undefined;
|
|
11
13
|
origin?: string | undefined;
|
|
12
14
|
};
|
|
15
|
+
/** Query state decoded from an SSR hydration script. */
|
|
13
16
|
export interface DehydratedQueryState {
|
|
14
17
|
queries: Array<{
|
|
15
18
|
data: unknown;
|
|
@@ -18,23 +21,36 @@ export interface DehydratedQueryState {
|
|
|
18
21
|
updatedAt: number;
|
|
19
22
|
}>;
|
|
20
23
|
}
|
|
24
|
+
/** Result returned by the component render helper. */
|
|
21
25
|
export interface ComponentRenderResult {
|
|
22
26
|
readonly container: HTMLElement;
|
|
23
27
|
rerender(value: ComponentRenderInput): void;
|
|
24
28
|
unmount(): void;
|
|
25
29
|
}
|
|
30
|
+
/** Component render input accepted by render and rerender. */
|
|
26
31
|
export type ComponentRenderInput = RenderValue | (() => RenderValue);
|
|
32
|
+
/** Route handler signature accepted by invokeRouteHandler. */
|
|
27
33
|
export type RouteHandler<TContext = undefined> = (request: Request, context: TContext) => Response | Promise<Response>;
|
|
34
|
+
/** Options for mounting a component render in tests. */
|
|
28
35
|
export interface ComponentRenderOptions {
|
|
29
36
|
container?: HTMLElement | undefined;
|
|
30
37
|
}
|
|
38
|
+
/** Creates a temporary app fixture directory for integration tests. */
|
|
31
39
|
export declare function createAppFixture(prefix?: string): Promise<AppFixture>;
|
|
40
|
+
/** Reads a response body as text. */
|
|
32
41
|
export declare function responseText(response: Response): Promise<string>;
|
|
42
|
+
/** Invokes a route handler and normalizes redirect, not-found, and invalid responses. */
|
|
33
43
|
export declare function invokeRouteHandler<TContext = undefined>(handler: RouteHandler<TContext>, request: Request, context?: TContext): Promise<Response>;
|
|
44
|
+
/** Renders a reactive component value into a DOM container for tests. */
|
|
34
45
|
export declare function render(value: ComponentRenderInput, options?: ComponentRenderOptions): ComponentRenderResult;
|
|
46
|
+
/** Runs a test action and flushes reactive effects after it settles. */
|
|
35
47
|
export declare function act<T>(fn: () => Promise<T> | T): Promise<T>;
|
|
48
|
+
/** Flushes queued reactive effects for tests. */
|
|
36
49
|
export declare function flushReactive(): Promise<void>;
|
|
50
|
+
/** Creates a real reactive cell for tests that need controllable state. */
|
|
37
51
|
export declare function createCellMock<T>(initial: T): Cell<T>;
|
|
52
|
+
/** Creates a real computed reactive value for tests. */
|
|
38
53
|
export declare function createComputedMock<T>(fn: () => T): ReadonlyCell<T>;
|
|
54
|
+
/** Reads dehydrated query state from rendered HTML. */
|
|
39
55
|
export declare function readQueryState(html: string): DehydratedQueryState | undefined;
|
|
40
56
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,IAAI,EACT,KAAK,YAAY,EAClB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAA4B,KAAK,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAIL,KAAK,uBAAuB,EAC7B,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvF,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD;AAED,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,uBAAuB,EAAE,QAAQ,GAAG,SAAS,CAAC,GAAG;IAC1F,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,OAAO,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC5C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG,CAAC,MAAM,WAAW,CAAC,CAAC;AAErE,MAAM,MAAM,YAAY,CAAC,QAAQ,GAAG,SAAS,IAAI,CAC/C,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,QAAQ,KACd,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElC,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CACrC;AAKD,wBAAsB,gBAAgB,CAAC,MAAM,SAAuB,GAAG,OAAO,CAAC,UAAU,CAAC,CAyBzF;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAEtE;AAED,wBAAsB,kBAAkB,CAAC,QAAQ,GAAG,SAAS,EAC3D,OAAO,EAAE,YAAY,CAAC,QAAQ,CAAC,EAC/B,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,QAAQ,CAAC,CAyBnB;AAED,wBAAgB,MAAM,CACpB,KAAK,EAAE,oBAAoB,EAC3B,OAAO,GAAE,sBAA2B,GACnC,qBAAqB,CAgBvB;AAED,wBAAsB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAIjE;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnD;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAErD;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAElE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAQ7E"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { flushEffects } from "@reckona/mreact-reactive-core/testing";
|
|
|
6
6
|
import { createRoot } from "@reckona/mreact-reactive-dom";
|
|
7
7
|
import { isNotFoundError, isRedirectError, renderAppRequest, } from "@reckona/mreact-router";
|
|
8
8
|
const queryStateScriptPattern = /<script\b[^>]*\bid=(?:"__mreact_query_state"|'__mreact_query_state')[^>]*>([\s\S]*?)<\/script>/i;
|
|
9
|
+
/** Creates a temporary app fixture directory for integration tests. */
|
|
9
10
|
export async function createAppFixture(prefix = "mreact-app-fixture") {
|
|
10
11
|
const appDir = await mkdtemp(join(tmpdir(), `${prefix}-`));
|
|
11
12
|
return {
|
|
@@ -29,9 +30,11 @@ export async function createAppFixture(prefix = "mreact-app-fixture") {
|
|
|
29
30
|
},
|
|
30
31
|
};
|
|
31
32
|
}
|
|
33
|
+
/** Reads a response body as text. */
|
|
32
34
|
export async function responseText(response) {
|
|
33
35
|
return response.text();
|
|
34
36
|
}
|
|
37
|
+
/** Invokes a route handler and normalizes redirect, not-found, and invalid responses. */
|
|
35
38
|
export async function invokeRouteHandler(handler, request, context) {
|
|
36
39
|
try {
|
|
37
40
|
const response = await handler(request, context);
|
|
@@ -55,6 +58,7 @@ export async function invokeRouteHandler(handler, request, context) {
|
|
|
55
58
|
throw error;
|
|
56
59
|
}
|
|
57
60
|
}
|
|
61
|
+
/** Renders a reactive component value into a DOM container for tests. */
|
|
58
62
|
export function render(value, options = {}) {
|
|
59
63
|
const container = options.container ?? document.createElement("div");
|
|
60
64
|
let current = value;
|
|
@@ -71,20 +75,25 @@ export function render(value, options = {}) {
|
|
|
71
75
|
},
|
|
72
76
|
};
|
|
73
77
|
}
|
|
78
|
+
/** Runs a test action and flushes reactive effects after it settles. */
|
|
74
79
|
export async function act(fn) {
|
|
75
80
|
const result = await batchAsync(fn);
|
|
76
81
|
await flushReactive();
|
|
77
82
|
return result;
|
|
78
83
|
}
|
|
84
|
+
/** Flushes queued reactive effects for tests. */
|
|
79
85
|
export async function flushReactive() {
|
|
80
86
|
await flushEffects();
|
|
81
87
|
}
|
|
88
|
+
/** Creates a real reactive cell for tests that need controllable state. */
|
|
82
89
|
export function createCellMock(initial) {
|
|
83
90
|
return cell(initial);
|
|
84
91
|
}
|
|
92
|
+
/** Creates a real computed reactive value for tests. */
|
|
85
93
|
export function createComputedMock(fn) {
|
|
86
94
|
return computed(fn);
|
|
87
95
|
}
|
|
96
|
+
/** Reads dehydrated query state from rendered HTML. */
|
|
88
97
|
export function readQueryState(html) {
|
|
89
98
|
const encoded = queryStateScriptPattern.exec(html)?.[1];
|
|
90
99
|
if (encoded === undefined) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EACL,UAAU,EACV,IAAI,EACJ,QAAQ,GAGT,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAE,UAAU,EAAkC,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EACL,eAAe,EACf,eAAe,EACf,gBAAgB,GAEjB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, mkdtemp, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport {\n batchAsync,\n cell,\n computed,\n type Cell,\n type ReadonlyCell,\n} from \"@reckona/mreact-reactive-core\";\nimport { flushEffects } from \"@reckona/mreact-reactive-core/testing\";\nimport { createRoot, type Dispose, type RenderValue } from \"@reckona/mreact-reactive-dom\";\nimport {\n isNotFoundError,\n isRedirectError,\n renderAppRequest,\n type RenderAppRequestOptions,\n} from \"@reckona/mreact-router\";\n\n/** Temporary app fixture for rendering and writing app-router files in tests. */\nexport interface AppFixture {\n readonly appDir: string;\n render(path: string, options?: AppFixtureRenderOptions | undefined): Promise<Response>;\n write(path: string, contents: string): Promise<void>;\n}\n\n/** Options passed when rendering a request with an app fixture. */\nexport type AppFixtureRenderOptions = Omit<RenderAppRequestOptions, \"appDir\" | \"request\"> & {\n request?: RequestInit | undefined;\n origin?: string | undefined;\n};\n\n/** Query state decoded from an SSR hydration script. */\nexport interface DehydratedQueryState {\n queries: Array<{\n data: unknown;\n queryHash: string;\n queryKey: readonly unknown[];\n updatedAt: number;\n }>;\n}\n\n/** Result returned by the component render helper. */\nexport interface ComponentRenderResult {\n readonly container: HTMLElement;\n rerender(value: ComponentRenderInput): void;\n unmount(): void;\n}\n\n/** Component render input accepted by render and rerender. */\nexport type ComponentRenderInput = RenderValue | (() => RenderValue);\n\n/** Route handler signature accepted by invokeRouteHandler. */\nexport type RouteHandler<TContext = undefined> = (\n request: Request,\n context: TContext,\n) => Response | Promise<Response>;\n\n/** Options for mounting a component render in tests. */\nexport interface ComponentRenderOptions {\n container?: HTMLElement | undefined;\n}\n\nconst queryStateScriptPattern =\n /<script\\b[^>]*\\bid=(?:\"__mreact_query_state\"|'__mreact_query_state')[^>]*>([\\s\\S]*?)<\\/script>/i;\n\n/** Creates a temporary app fixture directory for integration tests. */\nexport async function createAppFixture(prefix = \"mreact-app-fixture\"): Promise<AppFixture> {\n const appDir = await mkdtemp(join(tmpdir(), `${prefix}-`));\n\n return {\n appDir,\n render(path, options = {}) {\n const origin = options.origin ?? \"http://local.test\";\n const request = new Request(new URL(path, origin), options.request);\n const { origin: _origin, request: _request, ...routerOptions } = options;\n\n void _origin;\n void _request;\n\n return renderAppRequest({\n ...routerOptions,\n appDir,\n request,\n });\n },\n async write(path, contents) {\n const file = join(appDir, path);\n await mkdir(dirname(file), { recursive: true });\n await writeFile(file, contents);\n },\n };\n}\n\n/** Reads a response body as text. */\nexport async function responseText(response: Response): Promise<string> {\n return response.text();\n}\n\n/** Invokes a route handler and normalizes redirect, not-found, and invalid responses. */\nexport async function invokeRouteHandler<TContext = undefined>(\n handler: RouteHandler<TContext>,\n request: Request,\n context?: TContext,\n): Promise<Response> {\n try {\n const response = await handler(request, context as TContext);\n\n return response instanceof Response\n ? response\n : new Response(\"Invalid route response\", { status: 500 });\n } catch (error) {\n if (error instanceof Response) {\n return error;\n }\n\n if (isRedirectError(error)) {\n return new Response(null, {\n headers: { location: error.location },\n status: error.status,\n });\n }\n\n if (isNotFoundError(error)) {\n return new Response(\"Not Found\", { status: 404 });\n }\n\n throw error;\n }\n}\n\n/** Renders a reactive component value into a DOM container for tests. */\nexport function render(\n value: ComponentRenderInput,\n options: ComponentRenderOptions = {},\n): ComponentRenderResult {\n const container = options.container ?? document.createElement(\"div\");\n let current = value;\n let dispose = mountComponent(container, current);\n\n return {\n container,\n rerender(next) {\n dispose();\n current = next;\n dispose = mountComponent(container, current);\n },\n unmount() {\n dispose();\n },\n };\n}\n\n/** Runs a test action and flushes reactive effects after it settles. */\nexport async function act<T>(fn: () => Promise<T> | T): Promise<T> {\n const result = await batchAsync(fn);\n await flushReactive();\n return result;\n}\n\n/** Flushes queued reactive effects for tests. */\nexport async function flushReactive(): Promise<void> {\n await flushEffects();\n}\n\n/** Creates a real reactive cell for tests that need controllable state. */\nexport function createCellMock<T>(initial: T): Cell<T> {\n return cell(initial);\n}\n\n/** Creates a real computed reactive value for tests. */\nexport function createComputedMock<T>(fn: () => T): ReadonlyCell<T> {\n return computed(fn);\n}\n\n/** Reads dehydrated query state from rendered HTML. */\nexport function readQueryState(html: string): DehydratedQueryState | undefined {\n const encoded = queryStateScriptPattern.exec(html)?.[1];\n\n if (encoded === undefined) {\n return undefined;\n }\n\n return JSON.parse(unescapeJsonForHtml(encoded)) as DehydratedQueryState;\n}\n\nfunction unescapeJsonForHtml(value: string): string {\n return value\n .replaceAll(\"\\\\u003c\", \"<\")\n .replaceAll(\"\\\\u003e\", \">\")\n .replaceAll(\"\\\\u0026\", \"&\")\n .replaceAll(\"\\\\u2028\", \"\\u2028\")\n .replaceAll(\"\\\\u2029\", \"\\u2029\");\n}\n\nfunction mountComponent(container: HTMLElement, value: ComponentRenderInput): Dispose {\n return createRoot(container, () => (typeof value === \"function\" ? value() : value));\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reckona/mreact-test-utils",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.162",
|
|
4
4
|
"description": "Integration test helpers for mreact app-router applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fixtures",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@reckona/mreact-reactive-core": "0.0.
|
|
44
|
-
"@reckona/mreact-reactive-dom": "0.0.
|
|
45
|
-
"@reckona/mreact-router": "0.0.
|
|
43
|
+
"@reckona/mreact-reactive-core": "0.0.162",
|
|
44
|
+
"@reckona/mreact-reactive-dom": "0.0.162",
|
|
45
|
+
"@reckona/mreact-router": "0.0.162"
|
|
46
46
|
}
|
|
47
47
|
}
|
package/src/index.ts
CHANGED
|
@@ -17,17 +17,20 @@ import {
|
|
|
17
17
|
type RenderAppRequestOptions,
|
|
18
18
|
} from "@reckona/mreact-router";
|
|
19
19
|
|
|
20
|
+
/** Temporary app fixture for rendering and writing app-router files in tests. */
|
|
20
21
|
export interface AppFixture {
|
|
21
22
|
readonly appDir: string;
|
|
22
23
|
render(path: string, options?: AppFixtureRenderOptions | undefined): Promise<Response>;
|
|
23
24
|
write(path: string, contents: string): Promise<void>;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
/** Options passed when rendering a request with an app fixture. */
|
|
26
28
|
export type AppFixtureRenderOptions = Omit<RenderAppRequestOptions, "appDir" | "request"> & {
|
|
27
29
|
request?: RequestInit | undefined;
|
|
28
30
|
origin?: string | undefined;
|
|
29
31
|
};
|
|
30
32
|
|
|
33
|
+
/** Query state decoded from an SSR hydration script. */
|
|
31
34
|
export interface DehydratedQueryState {
|
|
32
35
|
queries: Array<{
|
|
33
36
|
data: unknown;
|
|
@@ -37,19 +40,23 @@ export interface DehydratedQueryState {
|
|
|
37
40
|
}>;
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
/** Result returned by the component render helper. */
|
|
40
44
|
export interface ComponentRenderResult {
|
|
41
45
|
readonly container: HTMLElement;
|
|
42
46
|
rerender(value: ComponentRenderInput): void;
|
|
43
47
|
unmount(): void;
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
/** Component render input accepted by render and rerender. */
|
|
46
51
|
export type ComponentRenderInput = RenderValue | (() => RenderValue);
|
|
47
52
|
|
|
53
|
+
/** Route handler signature accepted by invokeRouteHandler. */
|
|
48
54
|
export type RouteHandler<TContext = undefined> = (
|
|
49
55
|
request: Request,
|
|
50
56
|
context: TContext,
|
|
51
57
|
) => Response | Promise<Response>;
|
|
52
58
|
|
|
59
|
+
/** Options for mounting a component render in tests. */
|
|
53
60
|
export interface ComponentRenderOptions {
|
|
54
61
|
container?: HTMLElement | undefined;
|
|
55
62
|
}
|
|
@@ -57,6 +64,7 @@ export interface ComponentRenderOptions {
|
|
|
57
64
|
const queryStateScriptPattern =
|
|
58
65
|
/<script\b[^>]*\bid=(?:"__mreact_query_state"|'__mreact_query_state')[^>]*>([\s\S]*?)<\/script>/i;
|
|
59
66
|
|
|
67
|
+
/** Creates a temporary app fixture directory for integration tests. */
|
|
60
68
|
export async function createAppFixture(prefix = "mreact-app-fixture"): Promise<AppFixture> {
|
|
61
69
|
const appDir = await mkdtemp(join(tmpdir(), `${prefix}-`));
|
|
62
70
|
|
|
@@ -84,10 +92,12 @@ export async function createAppFixture(prefix = "mreact-app-fixture"): Promise<A
|
|
|
84
92
|
};
|
|
85
93
|
}
|
|
86
94
|
|
|
95
|
+
/** Reads a response body as text. */
|
|
87
96
|
export async function responseText(response: Response): Promise<string> {
|
|
88
97
|
return response.text();
|
|
89
98
|
}
|
|
90
99
|
|
|
100
|
+
/** Invokes a route handler and normalizes redirect, not-found, and invalid responses. */
|
|
91
101
|
export async function invokeRouteHandler<TContext = undefined>(
|
|
92
102
|
handler: RouteHandler<TContext>,
|
|
93
103
|
request: Request,
|
|
@@ -119,6 +129,7 @@ export async function invokeRouteHandler<TContext = undefined>(
|
|
|
119
129
|
}
|
|
120
130
|
}
|
|
121
131
|
|
|
132
|
+
/** Renders a reactive component value into a DOM container for tests. */
|
|
122
133
|
export function render(
|
|
123
134
|
value: ComponentRenderInput,
|
|
124
135
|
options: ComponentRenderOptions = {},
|
|
@@ -140,24 +151,29 @@ export function render(
|
|
|
140
151
|
};
|
|
141
152
|
}
|
|
142
153
|
|
|
154
|
+
/** Runs a test action and flushes reactive effects after it settles. */
|
|
143
155
|
export async function act<T>(fn: () => Promise<T> | T): Promise<T> {
|
|
144
156
|
const result = await batchAsync(fn);
|
|
145
157
|
await flushReactive();
|
|
146
158
|
return result;
|
|
147
159
|
}
|
|
148
160
|
|
|
161
|
+
/** Flushes queued reactive effects for tests. */
|
|
149
162
|
export async function flushReactive(): Promise<void> {
|
|
150
163
|
await flushEffects();
|
|
151
164
|
}
|
|
152
165
|
|
|
166
|
+
/** Creates a real reactive cell for tests that need controllable state. */
|
|
153
167
|
export function createCellMock<T>(initial: T): Cell<T> {
|
|
154
168
|
return cell(initial);
|
|
155
169
|
}
|
|
156
170
|
|
|
171
|
+
/** Creates a real computed reactive value for tests. */
|
|
157
172
|
export function createComputedMock<T>(fn: () => T): ReadonlyCell<T> {
|
|
158
173
|
return computed(fn);
|
|
159
174
|
}
|
|
160
175
|
|
|
176
|
+
/** Reads dehydrated query state from rendered HTML. */
|
|
161
177
|
export function readQueryState(html: string): DehydratedQueryState | undefined {
|
|
162
178
|
const encoded = queryStateScriptPattern.exec(html)?.[1];
|
|
163
179
|
|