@reckona/mreact-test-utils 0.0.66 → 0.0.68
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.js.map +1 -1
- package/package.json +6 -5
- package/src/index.ts +182 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
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\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\nexport type AppFixtureRenderOptions = Omit<RenderAppRequestOptions, \"appDir\" | \"request\"> & {\n request?: RequestInit | undefined;\n origin?: string | undefined;\n};\n\nexport interface DehydratedQueryState {\n queries: Array<{\n data: unknown;\n queryHash: string;\n queryKey: readonly unknown[];\n updatedAt: number;\n }>;\n}\n\nexport interface ComponentRenderResult {\n readonly container: HTMLElement;\n rerender(value: ComponentRenderInput): void;\n unmount(): void;\n}\n\nexport type ComponentRenderInput = RenderValue | (() => RenderValue);\n\nexport type RouteHandler<TContext = undefined> = (\n request: Request,\n context: TContext,\n) => Response | Promise<Response>;\n\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\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\nexport async function responseText(response: Response): Promise<string> {\n return response.text();\n}\n\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\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\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\nexport async function flushReactive(): Promise<void> {\n await flushEffects();\n}\n\nexport function createCellMock<T>(initial: T): Cell<T> {\n return cell(initial);\n}\n\nexport function createComputedMock<T>(fn: () => T): ReadonlyCell<T> {\n return computed(fn);\n}\n\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.68",
|
|
4
4
|
"description": "Integration test helpers for mreact app-router applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fixtures",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"dist/**/*.js",
|
|
25
25
|
"dist/**/*.js.map",
|
|
26
26
|
"dist/**/*.d.ts",
|
|
27
|
-
"dist/**/*.d.ts.map"
|
|
27
|
+
"dist/**/*.d.ts.map",
|
|
28
|
+
"src/**/*"
|
|
28
29
|
],
|
|
29
30
|
"type": "module",
|
|
30
31
|
"sideEffects": false,
|
|
@@ -39,8 +40,8 @@
|
|
|
39
40
|
"access": "public"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
|
-
"@reckona/mreact-reactive-core": "0.0.
|
|
43
|
-
"@reckona/mreact-
|
|
44
|
-
"@reckona/mreact-
|
|
43
|
+
"@reckona/mreact-reactive-core": "0.0.68",
|
|
44
|
+
"@reckona/mreact-router": "0.0.68",
|
|
45
|
+
"@reckona/mreact-reactive-dom": "0.0.68"
|
|
45
46
|
}
|
|
46
47
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
batchAsync,
|
|
6
|
+
cell,
|
|
7
|
+
computed,
|
|
8
|
+
type Cell,
|
|
9
|
+
type ReadonlyCell,
|
|
10
|
+
} from "@reckona/mreact-reactive-core";
|
|
11
|
+
import { flushEffects } from "@reckona/mreact-reactive-core/testing";
|
|
12
|
+
import { createRoot, type Dispose, type RenderValue } from "@reckona/mreact-reactive-dom";
|
|
13
|
+
import {
|
|
14
|
+
isNotFoundError,
|
|
15
|
+
isRedirectError,
|
|
16
|
+
renderAppRequest,
|
|
17
|
+
type RenderAppRequestOptions,
|
|
18
|
+
} from "@reckona/mreact-router";
|
|
19
|
+
|
|
20
|
+
export interface AppFixture {
|
|
21
|
+
readonly appDir: string;
|
|
22
|
+
render(path: string, options?: AppFixtureRenderOptions | undefined): Promise<Response>;
|
|
23
|
+
write(path: string, contents: string): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AppFixtureRenderOptions = Omit<RenderAppRequestOptions, "appDir" | "request"> & {
|
|
27
|
+
request?: RequestInit | undefined;
|
|
28
|
+
origin?: string | undefined;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export interface DehydratedQueryState {
|
|
32
|
+
queries: Array<{
|
|
33
|
+
data: unknown;
|
|
34
|
+
queryHash: string;
|
|
35
|
+
queryKey: readonly unknown[];
|
|
36
|
+
updatedAt: number;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ComponentRenderResult {
|
|
41
|
+
readonly container: HTMLElement;
|
|
42
|
+
rerender(value: ComponentRenderInput): void;
|
|
43
|
+
unmount(): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type ComponentRenderInput = RenderValue | (() => RenderValue);
|
|
47
|
+
|
|
48
|
+
export type RouteHandler<TContext = undefined> = (
|
|
49
|
+
request: Request,
|
|
50
|
+
context: TContext,
|
|
51
|
+
) => Response | Promise<Response>;
|
|
52
|
+
|
|
53
|
+
export interface ComponentRenderOptions {
|
|
54
|
+
container?: HTMLElement | undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const queryStateScriptPattern =
|
|
58
|
+
/<script\b[^>]*\bid=(?:"__mreact_query_state"|'__mreact_query_state')[^>]*>([\s\S]*?)<\/script>/i;
|
|
59
|
+
|
|
60
|
+
export async function createAppFixture(prefix = "mreact-app-fixture"): Promise<AppFixture> {
|
|
61
|
+
const appDir = await mkdtemp(join(tmpdir(), `${prefix}-`));
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
appDir,
|
|
65
|
+
render(path, options = {}) {
|
|
66
|
+
const origin = options.origin ?? "http://local.test";
|
|
67
|
+
const request = new Request(new URL(path, origin), options.request);
|
|
68
|
+
const { origin: _origin, request: _request, ...routerOptions } = options;
|
|
69
|
+
|
|
70
|
+
void _origin;
|
|
71
|
+
void _request;
|
|
72
|
+
|
|
73
|
+
return renderAppRequest({
|
|
74
|
+
...routerOptions,
|
|
75
|
+
appDir,
|
|
76
|
+
request,
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
async write(path, contents) {
|
|
80
|
+
const file = join(appDir, path);
|
|
81
|
+
await mkdir(dirname(file), { recursive: true });
|
|
82
|
+
await writeFile(file, contents);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function responseText(response: Response): Promise<string> {
|
|
88
|
+
return response.text();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function invokeRouteHandler<TContext = undefined>(
|
|
92
|
+
handler: RouteHandler<TContext>,
|
|
93
|
+
request: Request,
|
|
94
|
+
context?: TContext,
|
|
95
|
+
): Promise<Response> {
|
|
96
|
+
try {
|
|
97
|
+
const response = await handler(request, context as TContext);
|
|
98
|
+
|
|
99
|
+
return response instanceof Response
|
|
100
|
+
? response
|
|
101
|
+
: new Response("Invalid route response", { status: 500 });
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof Response) {
|
|
104
|
+
return error;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (isRedirectError(error)) {
|
|
108
|
+
return new Response(null, {
|
|
109
|
+
headers: { location: error.location },
|
|
110
|
+
status: error.status,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (isNotFoundError(error)) {
|
|
115
|
+
return new Response("Not Found", { status: 404 });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function render(
|
|
123
|
+
value: ComponentRenderInput,
|
|
124
|
+
options: ComponentRenderOptions = {},
|
|
125
|
+
): ComponentRenderResult {
|
|
126
|
+
const container = options.container ?? document.createElement("div");
|
|
127
|
+
let current = value;
|
|
128
|
+
let dispose = mountComponent(container, current);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
container,
|
|
132
|
+
rerender(next) {
|
|
133
|
+
dispose();
|
|
134
|
+
current = next;
|
|
135
|
+
dispose = mountComponent(container, current);
|
|
136
|
+
},
|
|
137
|
+
unmount() {
|
|
138
|
+
dispose();
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function act<T>(fn: () => Promise<T> | T): Promise<T> {
|
|
144
|
+
const result = await batchAsync(fn);
|
|
145
|
+
await flushReactive();
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function flushReactive(): Promise<void> {
|
|
150
|
+
await flushEffects();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function createCellMock<T>(initial: T): Cell<T> {
|
|
154
|
+
return cell(initial);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function createComputedMock<T>(fn: () => T): ReadonlyCell<T> {
|
|
158
|
+
return computed(fn);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function readQueryState(html: string): DehydratedQueryState | undefined {
|
|
162
|
+
const encoded = queryStateScriptPattern.exec(html)?.[1];
|
|
163
|
+
|
|
164
|
+
if (encoded === undefined) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return JSON.parse(unescapeJsonForHtml(encoded)) as DehydratedQueryState;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function unescapeJsonForHtml(value: string): string {
|
|
172
|
+
return value
|
|
173
|
+
.replaceAll("\\u003c", "<")
|
|
174
|
+
.replaceAll("\\u003e", ">")
|
|
175
|
+
.replaceAll("\\u0026", "&")
|
|
176
|
+
.replaceAll("\\u2028", "\u2028")
|
|
177
|
+
.replaceAll("\\u2029", "\u2029");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function mountComponent(container: HTMLElement, value: ComponentRenderInput): Dispose {
|
|
181
|
+
return createRoot(container, () => (typeof value === "function" ? value() : value));
|
|
182
|
+
}
|