@jterrazz/test 15.1.0 → 15.2.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/ambiguity.js +23 -4
- package/dist/artifacts.js +68 -160
- package/dist/browser/index.js +2477 -0
- package/dist/checker.js +9 -3
- package/dist/index.d.ts +807 -497
- package/dist/index.js +3034 -2770
- package/dist/intercept.js +108 -58
- package/dist/oxlint.cjs +461 -34
- package/dist/oxlint.d.cts +13 -1
- package/dist/oxlint.d.ts +13 -1
- package/dist/oxlint.js +461 -34
- package/dist/playwright.adapter.js +95 -22
- package/dist/queue.js +1 -1
- package/dist/vitest.d.ts +100 -2
- package/dist/vitest.js +375 -3
- package/package.json +37 -5
package/dist/intercept.js
CHANGED
|
@@ -1,101 +1,151 @@
|
|
|
1
1
|
import { t as ContractQueue } from "./queue.js";
|
|
2
|
-
//#region src/integrations/msw/
|
|
3
|
-
let mswModule = null;
|
|
4
|
-
let mswHttp = null;
|
|
5
|
-
async function loadMsw() {
|
|
6
|
-
if (!mswModule) {
|
|
7
|
-
mswModule = await import("msw/node");
|
|
8
|
-
mswHttp = await import("msw");
|
|
9
|
-
}
|
|
10
|
-
return {
|
|
11
|
-
node: mswModule,
|
|
12
|
-
msw: mswHttp
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
let serverInstance = null;
|
|
2
|
+
//#region src/integrations/msw/handlers.ts
|
|
16
3
|
/**
|
|
17
|
-
*
|
|
4
|
+
* What a chain with zero contracts gets where the facet's scope is KNOWN but
|
|
5
|
+
* not total — the node engine, which shares a process with everything else the
|
|
6
|
+
* test does. Nothing was declared, so nothing is guarded and nothing has to be
|
|
7
|
+
* torn down (CONVENTIONS D7, known scope).
|
|
18
8
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
9
|
+
const NO_CONTRACTS = {
|
|
10
|
+
cleanup: () => {},
|
|
11
|
+
violation: () => null
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* A contract answers the request that was made, never a copy of an earlier one.
|
|
15
|
+
*
|
|
16
|
+
* In a page the reply travels through a real service worker and a real HTTP
|
|
17
|
+
* cache, so a second render of the same component can be served the FIRST
|
|
18
|
+
* test's answer and never reach the queue at all — a contract that was
|
|
19
|
+
* exhausted, or replaced, silently keeps answering. Stated as a default, so a
|
|
20
|
+
* spec whose subject IS caching can still say otherwise.
|
|
21
|
+
*/
|
|
22
|
+
const NEVER_CACHED = { "cache-control": "no-store" };
|
|
23
|
+
/** Turn a contract response into the msw reply, body kind by body kind. */
|
|
26
24
|
function toMswResponse(msw, response) {
|
|
27
25
|
const { body, headers, status = 200 } = response;
|
|
26
|
+
if (response.transport === "network-error") return msw.HttpResponse.error();
|
|
28
27
|
if (body === null || body === void 0) return new msw.HttpResponse(null, {
|
|
29
|
-
headers
|
|
28
|
+
headers: {
|
|
29
|
+
...NEVER_CACHED,
|
|
30
|
+
...headers
|
|
31
|
+
},
|
|
30
32
|
status
|
|
31
33
|
});
|
|
32
34
|
if (typeof body === "string") return new msw.HttpResponse(body, {
|
|
33
35
|
headers: {
|
|
36
|
+
...NEVER_CACHED,
|
|
34
37
|
"content-type": "text/plain; charset=utf-8",
|
|
35
38
|
...headers
|
|
36
39
|
},
|
|
37
40
|
status
|
|
38
41
|
});
|
|
39
42
|
return msw.HttpResponse.json(body, {
|
|
40
|
-
headers
|
|
43
|
+
headers: {
|
|
44
|
+
...NEVER_CACHED,
|
|
45
|
+
...headers
|
|
46
|
+
},
|
|
41
47
|
status
|
|
42
48
|
});
|
|
43
49
|
}
|
|
44
50
|
/**
|
|
45
|
-
*
|
|
46
|
-
* records any request no contract accepted
|
|
47
|
-
*
|
|
51
|
+
* The handlers one chain's contracts become, plus the trailing catch-all that
|
|
52
|
+
* records any request no contract accepted (CONVENTIONS D7). The caller decides
|
|
53
|
+
* what to do with the recorded violation — the api chain rethrows it, the
|
|
54
|
+
* component chain fails the render. An EMPTY contract list is still a guard:
|
|
55
|
+
* the catch-all alone says "this subject has no network".
|
|
56
|
+
*
|
|
57
|
+
* `bypass` names the traffic that is not the subject's: under node there is
|
|
58
|
+
* none, in a page it is everything the runner fetches to BE a page.
|
|
48
59
|
*/
|
|
49
|
-
|
|
50
|
-
if (contracts.length === 0) return {
|
|
51
|
-
cleanup: () => {},
|
|
52
|
-
violation: () => null
|
|
53
|
-
};
|
|
54
|
-
await ensureContractServer();
|
|
55
|
-
const { msw } = await loadMsw();
|
|
60
|
+
function buildContractHandlers(msw, contracts, bypass) {
|
|
56
61
|
const queue = new ContractQueue(contracts);
|
|
57
62
|
let violation = null;
|
|
58
63
|
const recordViolation = (method, url) => {
|
|
59
64
|
violation ??= queue.unmatchedError(method, url);
|
|
60
|
-
return msw.HttpResponse.json({ error: `@jterrazz/test strict contracts: unmatched request ${method} ${url}` }, {
|
|
65
|
+
return msw.HttpResponse.json({ error: `@jterrazz/test strict contracts: unmatched request ${method} ${url}` }, {
|
|
66
|
+
headers: NEVER_CACHED,
|
|
67
|
+
status: 501
|
|
68
|
+
});
|
|
61
69
|
};
|
|
62
70
|
const handlers = [];
|
|
63
71
|
for (const route of queue.routes) for (const method of route.methods) {
|
|
64
72
|
const handlerFn = method === "*" ? msw.http.all : msw.http[method.toLowerCase()];
|
|
65
73
|
if (!handlerFn) continue;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const rawText = await request.clone().text();
|
|
69
|
-
if (rawText) try {
|
|
70
|
-
body = JSON.parse(rawText);
|
|
71
|
-
} catch {
|
|
72
|
-
body = rawText;
|
|
73
|
-
}
|
|
74
|
-
const headers = {};
|
|
75
|
-
request.headers.forEach((value, key) => {
|
|
76
|
-
headers[key.toLowerCase()] = value;
|
|
77
|
-
});
|
|
78
|
-
const observed = {
|
|
79
|
-
body,
|
|
80
|
-
headers,
|
|
81
|
-
method: request.method.toUpperCase(),
|
|
82
|
-
url: request.url
|
|
83
|
-
};
|
|
74
|
+
handlers.push(handlerFn(route.url, async ({ request }) => {
|
|
75
|
+
const observed = await observe(request);
|
|
84
76
|
const contract = queue.take(observed);
|
|
85
77
|
if (!contract) return recordViolation(request.method, request.url);
|
|
86
78
|
const response = typeof contract.response === "function" ? contract.response(observed) : contract.response;
|
|
87
|
-
if (response.delay) await new Promise((
|
|
79
|
+
if (response.delay) await new Promise((resolve) => setTimeout(resolve, response.delay));
|
|
88
80
|
return toMswResponse(msw, response);
|
|
89
|
-
});
|
|
90
|
-
handlers.push(handler);
|
|
81
|
+
}));
|
|
91
82
|
}
|
|
92
|
-
handlers.push(msw.http.all("*", ({ request }) => recordViolation(request.method, request.url)));
|
|
83
|
+
handlers.push(msw.http.all("*", ({ request }) => bypass?.(request.url) === true ? msw.passthrough() : recordViolation(request.method, request.url)));
|
|
84
|
+
return {
|
|
85
|
+
handlers,
|
|
86
|
+
violation: () => violation ?? queue.requiredError()
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** The request as the queue matches it — body parsed when it is JSON. */
|
|
90
|
+
async function observe(request) {
|
|
91
|
+
let body = null;
|
|
92
|
+
const rawText = await request.clone().text();
|
|
93
|
+
if (rawText) try {
|
|
94
|
+
body = JSON.parse(rawText);
|
|
95
|
+
} catch {
|
|
96
|
+
body = rawText;
|
|
97
|
+
}
|
|
98
|
+
const headers = {};
|
|
99
|
+
request.headers.forEach((value, key) => {
|
|
100
|
+
headers[key.toLowerCase()] = value;
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
body,
|
|
104
|
+
headers,
|
|
105
|
+
method: request.method.toUpperCase(),
|
|
106
|
+
url: request.url
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/integrations/msw/intercept.ts
|
|
111
|
+
let mswModule = null;
|
|
112
|
+
let mswHttp = null;
|
|
113
|
+
async function loadMsw() {
|
|
114
|
+
if (mswModule === null) {
|
|
115
|
+
mswModule = await import("msw/node");
|
|
116
|
+
mswHttp = await import("msw");
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
msw: mswHttp,
|
|
120
|
+
node: mswModule
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
let serverInstance = null;
|
|
124
|
+
/**
|
|
125
|
+
* Start the MSW server (once per process).
|
|
126
|
+
*/
|
|
127
|
+
async function ensureContractServer() {
|
|
128
|
+
if (serverInstance !== null) return;
|
|
129
|
+
const { node } = await loadMsw();
|
|
130
|
+
serverInstance = node.setupServer();
|
|
131
|
+
serverInstance.listen({ onUnhandledRequest: "bypass" });
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Register a chain's contracts as MSW handlers. A trailing catch-all handler
|
|
135
|
+
* records any request no contract accepted — the builder rethrows it as the
|
|
136
|
+
* spec failure (rejecting the action promise, never an unhandled rejection).
|
|
137
|
+
*/
|
|
138
|
+
async function registerContracts(contracts) {
|
|
139
|
+
if (contracts.length === 0) return NO_CONTRACTS;
|
|
140
|
+
await ensureContractServer();
|
|
141
|
+
const { msw } = await loadMsw();
|
|
142
|
+
const { handlers, violation } = buildContractHandlers(msw, contracts);
|
|
93
143
|
serverInstance.use(...handlers);
|
|
94
144
|
return {
|
|
95
145
|
cleanup: () => {
|
|
96
146
|
serverInstance.resetHandlers();
|
|
97
147
|
},
|
|
98
|
-
violation
|
|
148
|
+
violation
|
|
99
149
|
};
|
|
100
150
|
}
|
|
101
151
|
//#endregion
|