@jterrazz/test 17.0.0 → 18.0.1
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/browser/index.js +1 -2
- package/dist/browser/services.js +1 -2
- package/dist/browser/stubs.js +1 -2
- package/dist/{shared-index.browser.js → browser-shared-index.browser.js} +82 -40
- package/dist/checker.js +15 -824
- package/dist/in-place.global-setup.d.ts +1 -0
- package/dist/in-place.global-setup.js +1 -1
- package/dist/index.d.ts +2 -14
- package/dist/index.js +83 -204
- package/dist/mobile-session.global-setup.d.ts +1 -0
- package/dist/oxlint.cjs +2 -2
- package/dist/oxlint.js +67 -2321
- package/dist/route-coverage.global-setup.d.ts +1 -0
- package/dist/route-coverage.global-setup.js +1 -2
- package/dist/services.global-setup.d.ts +1 -1
- package/dist/services.global-setup.js +1 -2
- package/dist/services.js +1 -1
- package/dist/shared-appium.adapter.js +14 -14
- package/dist/shared-artifacts.js +7 -5
- package/dist/shared-derived-projects.js +23 -8
- package/dist/shared-in-place.js +1 -1
- package/dist/shared-ledger.js +1 -1
- package/dist/shared-listened-port.js +19 -0
- package/dist/shared-literate-plugin.js +2 -1
- package/dist/shared-pause.js +12 -1
- package/dist/shared-playwright.adapter.js +17 -19
- package/dist/shared-process-alive.js +13 -0
- package/dist/shared-record-key.js +13 -0
- package/dist/shared-serve.js +180 -69
- package/dist/shared-services.global-setup.js +4 -9
- package/dist/shared-session-ledger.js +10 -6
- package/dist/shared-spec-document.js +2 -64
- package/dist/shared-spec-grammar.js +66 -0
- package/dist/shared-specs-modules.js +1033 -0
- package/dist/{shared-stream-body.js → shared-stub-reply.js} +40 -3
- package/dist/shared-stub-server.js +30 -78
- package/dist/shared-temp-root.global-setup.js +3 -10
- package/dist/shared-testcontainers.adapter.js +111 -8
- package/dist/temp-root.global-setup.js +1 -2
- package/dist/vitest.d.ts +17 -22
- package/dist/vitest.js +37 -53
- package/docs/concepts/architecture.md +20 -20
- package/docs/concepts/conventions.md +2 -2
- package/docs/guides/cli-specs.md +2 -0
- package/docs/guides/develop.md +20 -17
- package/docs/guides/mobile-specs.md +3 -3
- package/docs/guides/release.md +4 -3
- package/docs/guides/website-specs.md +1 -1
- package/docs/reference/linting.md +2 -2
- package/docs/reference/services.md +13 -9
- package/docs/reference/stubs.md +4 -2
- package/docs/start/set-up-a-project.md +31 -17
- package/package.json +10 -10
|
@@ -109,6 +109,16 @@ var StubQueue = class {
|
|
|
109
109
|
};
|
|
110
110
|
//#endregion
|
|
111
111
|
//#region src/stubs/delivery/stream-body.ts
|
|
112
|
+
async function nextTask() {
|
|
113
|
+
const { port1, port2 } = new MessageChannel();
|
|
114
|
+
await new Promise((resolve) => {
|
|
115
|
+
port1.onmessage = () => {
|
|
116
|
+
port1.close();
|
|
117
|
+
resolve();
|
|
118
|
+
};
|
|
119
|
+
port2.postMessage(null);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
112
122
|
/** One serialisation for all three deliveries (msw server, msw worker, `node:http` stub). */
|
|
113
123
|
function toReadableStream(body) {
|
|
114
124
|
const encoder = new TextEncoder();
|
|
@@ -119,12 +129,39 @@ function toReadableStream(body) {
|
|
|
119
129
|
controller.close();
|
|
120
130
|
return;
|
|
121
131
|
}
|
|
122
|
-
if (
|
|
132
|
+
if (index > 0) {
|
|
133
|
+
await pause(body.delayBetweenChunks);
|
|
134
|
+
await nextTask();
|
|
135
|
+
}
|
|
123
136
|
index += 1;
|
|
124
137
|
controller.enqueue(encoder.encode(chunk));
|
|
125
138
|
} });
|
|
126
139
|
}
|
|
127
140
|
//#endregion
|
|
128
|
-
|
|
141
|
+
//#region src/stubs/delivery/stub-reply.ts
|
|
142
|
+
/** A request's body as a stub's filter reads it: the parsed JSON, the raw text when it is none, `null` when there is none. */
|
|
143
|
+
function requestBody(raw) {
|
|
144
|
+
if (raw.length === 0) return null;
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(raw);
|
|
147
|
+
} catch {
|
|
148
|
+
return raw;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** The reply a matched stub states for this request, once its delay has passed. */
|
|
152
|
+
async function replyOf(response, observed) {
|
|
153
|
+
const reply = typeof response === "function" ? response(observed) : response;
|
|
154
|
+
await pause(reply.delay ?? 0);
|
|
155
|
+
return reply;
|
|
156
|
+
}
|
|
157
|
+
/** The `error` of the 501 every delivery answers a request it refuses, whichever process serves it. */
|
|
158
|
+
function refusedReply(summary) {
|
|
159
|
+
return `@jterrazz/test strict stubs: ${summary}`;
|
|
160
|
+
}
|
|
161
|
+
function unmatchedSummary(method, url) {
|
|
162
|
+
return `unmatched request ${method} ${url}`;
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
export { toReadableStream as a, unmatchedSummary as i, replyOf as n, StubQueue as o, requestBody as r, refusedReply as t };
|
|
129
166
|
|
|
130
|
-
//# sourceMappingURL=shared-
|
|
167
|
+
//# sourceMappingURL=shared-stub-reply.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { n as refusal } from "./shared-refusal.js";
|
|
1
2
|
import { n as isStreamBody } from "./shared-exchange.js";
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
3
|
+
import { a as toReadableStream, i as unmatchedSummary, n as replyOf, o as StubQueue, r as requestBody, t as refusedReply } from "./shared-stub-reply.js";
|
|
4
|
+
import { METHODS } from "node:http";
|
|
4
5
|
//#region src/stubs/delivery/msw-handlers.ts
|
|
5
6
|
const NEVER_CACHED = { "cache-control": "no-store" };
|
|
6
7
|
function toMswResponse(msw, response) {
|
|
@@ -52,13 +53,14 @@ function handlerOf(msw, method) {
|
|
|
52
53
|
function buildStubHandlers(msw, stubs, bypass) {
|
|
53
54
|
const queue = new StubQueue(stubs);
|
|
54
55
|
let violation = null;
|
|
55
|
-
const
|
|
56
|
-
violation ??=
|
|
57
|
-
return msw.HttpResponse.json({ error:
|
|
56
|
+
const refuse = (error, summary) => {
|
|
57
|
+
violation ??= error;
|
|
58
|
+
return msw.HttpResponse.json({ error: refusedReply(summary) }, {
|
|
58
59
|
headers: NEVER_CACHED,
|
|
59
60
|
status: 501
|
|
60
61
|
});
|
|
61
62
|
};
|
|
63
|
+
const recordViolation = (method, url) => refuse(queue.unmatchedError(method, url), unmatchedSummary(method, url));
|
|
62
64
|
const handlers = [];
|
|
63
65
|
for (const route of queue.routes) for (const method of route.methods) {
|
|
64
66
|
const handlerFn = handlerOf(msw, method);
|
|
@@ -67,90 +69,25 @@ function buildStubHandlers(msw, stubs, bypass) {
|
|
|
67
69
|
const observed = await observe(request);
|
|
68
70
|
const stub = queue.take(observed);
|
|
69
71
|
if (!stub) return recordViolation(request.method, request.url);
|
|
70
|
-
|
|
71
|
-
await pause(response.delay ?? 0);
|
|
72
|
-
return toMswResponse(msw, response);
|
|
72
|
+
return toMswResponse(msw, await replyOf(stub.response, observed));
|
|
73
73
|
}));
|
|
74
74
|
}
|
|
75
75
|
handlers.push(msw.http.all("*", ({ request }) => bypass?.(request.url) === true ? msw.passthrough() : recordViolation(request.method, request.url)));
|
|
76
76
|
return {
|
|
77
77
|
handlers,
|
|
78
|
+
refuse,
|
|
78
79
|
violation: () => queue.verdict(violation)
|
|
79
80
|
};
|
|
80
81
|
}
|
|
81
82
|
async function observe(request) {
|
|
82
|
-
let body = null;
|
|
83
|
-
const rawText = await request.clone().text();
|
|
84
|
-
if (rawText) try {
|
|
85
|
-
body = JSON.parse(rawText);
|
|
86
|
-
} catch {
|
|
87
|
-
body = rawText;
|
|
88
|
-
}
|
|
89
83
|
return {
|
|
90
|
-
body,
|
|
84
|
+
body: requestBody(await request.clone().text()),
|
|
91
85
|
headers: Object.fromEntries(request.headers),
|
|
92
86
|
method: request.method.toUpperCase(),
|
|
93
87
|
url: request.url
|
|
94
88
|
};
|
|
95
89
|
}
|
|
96
90
|
//#endregion
|
|
97
|
-
//#region src/stubs/delivery/same-process/fetch-refusals.ts
|
|
98
|
-
const PROBE = "http://request-init.invalid/";
|
|
99
|
-
const probeOf = (init) => init.body instanceof ReadableStream ? {
|
|
100
|
-
...init,
|
|
101
|
-
body: new Blob()
|
|
102
|
-
} : init;
|
|
103
|
-
/**
|
|
104
|
-
* Wraps `underlying` so a RequestInit that fetch itself refuses rejects with fetch's own TypeError, before any stub can serve it.
|
|
105
|
-
* @see https://fetch.spec.whatwg.org/#dom-request
|
|
106
|
-
*/
|
|
107
|
-
function refusingWhatFetchRefuses(underlying) {
|
|
108
|
-
return async (input, init) => {
|
|
109
|
-
if (init === void 0) return await underlying(input);
|
|
110
|
-
const { method } = new Request(PROBE, probeOf(init));
|
|
111
|
-
return await underlying(input, {
|
|
112
|
-
...init,
|
|
113
|
-
method
|
|
114
|
-
});
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
//#endregion
|
|
118
|
-
//#region src/stubs/delivery/same-process/owned-body.ts
|
|
119
|
-
async function release(reader, reason) {
|
|
120
|
-
try {
|
|
121
|
-
await reader.cancel(reason);
|
|
122
|
-
} catch {}
|
|
123
|
-
}
|
|
124
|
-
function withOwnedBody(response) {
|
|
125
|
-
const { body } = response;
|
|
126
|
-
if (body === null) return response;
|
|
127
|
-
const reader = body.getReader();
|
|
128
|
-
const owned = new ReadableStream({
|
|
129
|
-
cancel: (reason) => {
|
|
130
|
-
release(reader, reason);
|
|
131
|
-
},
|
|
132
|
-
pull: async (controller) => {
|
|
133
|
-
const { done, value } = await reader.read();
|
|
134
|
-
if (done) controller.close();
|
|
135
|
-
else controller.enqueue(value);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
const reply = new Response(owned, {
|
|
139
|
-
headers: response.headers,
|
|
140
|
-
status: response.status,
|
|
141
|
-
statusText: response.statusText
|
|
142
|
-
});
|
|
143
|
-
Object.defineProperties(reply, {
|
|
144
|
-
redirected: { value: response.redirected },
|
|
145
|
-
type: { value: response.type },
|
|
146
|
-
url: { value: response.url }
|
|
147
|
-
});
|
|
148
|
-
return reply;
|
|
149
|
-
}
|
|
150
|
-
function owningBodies(underlying) {
|
|
151
|
-
return async (input, init) => withOwnedBody(await underlying(input, init));
|
|
152
|
-
}
|
|
153
|
-
//#endregion
|
|
154
91
|
//#region src/stubs/delivery/same-process/stub-server.ts
|
|
155
92
|
async function loadMsw() {
|
|
156
93
|
const [{ setupServer }, msw] = await Promise.all([import("msw/node"), import("msw")]);
|
|
@@ -160,25 +97,40 @@ async function loadMsw() {
|
|
|
160
97
|
};
|
|
161
98
|
}
|
|
162
99
|
let serverInstance = null;
|
|
163
|
-
let
|
|
100
|
+
let refuseOffTheWire = null;
|
|
101
|
+
function offTheWireError(request) {
|
|
102
|
+
return new Error(refusal("network-strict-after-first-stub", {
|
|
103
|
+
fix: "send a method in the capitals the HTTP parser reads (`PATCH`, not `patch`): fetch upper-cases DELETE, GET, HEAD, OPTIONS, POST and PUT, and sends any other as written",
|
|
104
|
+
found: `an outgoing request the network double cannot read off the wire, ${request.method} ${request.url}: its method is not one of node's \`http.METHODS\`, so no stub could answer it and it would reach the network`
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
function guardOffTheWireMethods() {
|
|
108
|
+
const next = globalThis.fetch;
|
|
109
|
+
globalThis.fetch = async (input, init) => {
|
|
110
|
+
const request = new Request(input, init);
|
|
111
|
+
if (refuseOffTheWire !== null && !METHODS.includes(request.method)) return refuseOffTheWire(request);
|
|
112
|
+
return await next(request);
|
|
113
|
+
};
|
|
114
|
+
}
|
|
164
115
|
async function ensureStubServer() {
|
|
165
116
|
if (serverInstance !== null) return serverInstance;
|
|
166
117
|
const { setupServer } = await loadMsw();
|
|
167
118
|
const server = setupServer();
|
|
168
|
-
server.listen({
|
|
119
|
+
server.listen({ onUnhandledFrame: "bypass" });
|
|
120
|
+
guardOffTheWireMethods();
|
|
169
121
|
serverInstance = server;
|
|
170
|
-
installedFetch = globalThis.fetch;
|
|
171
|
-
globalThis.fetch = owningBodies(refusingWhatFetchRefuses(installedFetch));
|
|
172
122
|
return server;
|
|
173
123
|
}
|
|
174
124
|
/** The trailing catch-all records any request no stub accepted; the chain's pipeline rethrows it. */
|
|
175
125
|
async function registerStubs(stubs) {
|
|
176
126
|
const server = await ensureStubServer();
|
|
177
127
|
const { msw } = await loadMsw();
|
|
178
|
-
const { handlers, violation } = buildStubHandlers(msw, stubs);
|
|
128
|
+
const { handlers, refuse, violation } = buildStubHandlers(msw, stubs);
|
|
179
129
|
server.use(...handlers);
|
|
130
|
+
refuseOffTheWire = (request) => refuse(offTheWireError(request), `unreadable request ${request.method} ${request.url}`);
|
|
180
131
|
return {
|
|
181
132
|
cleanup: () => {
|
|
133
|
+
refuseOffTheWire = null;
|
|
182
134
|
server.resetHandlers();
|
|
183
135
|
},
|
|
184
136
|
violation
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { a as EVIDENCE_DIR } from "./shared-artifacts.js";
|
|
2
2
|
import { t as setupEntry } from "./shared-setup-entry.js";
|
|
3
|
+
import { t as processAlive } from "./shared-process-alive.js";
|
|
3
4
|
import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
4
5
|
import { basename, dirname, join, resolve } from "node:path";
|
|
5
6
|
import { tmpdir } from "node:os";
|
|
6
7
|
//#region src/chain/paths/run-folders.ts
|
|
7
|
-
function isAlive(pid) {
|
|
8
|
-
try {
|
|
9
|
-
process.kill(pid, 0);
|
|
10
|
-
return true;
|
|
11
|
-
} catch (error) {
|
|
12
|
-
return error instanceof Error && "code" in error && error.code === "EPERM";
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
8
|
function runOf(entry) {
|
|
16
9
|
const pid = Number(entry.split("-")[0]);
|
|
17
10
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
@@ -20,7 +13,7 @@ function runOf(entry) {
|
|
|
20
13
|
function sweepOrphanRoots(parent) {
|
|
21
14
|
for (const entry of readdirSync(parent)) {
|
|
22
15
|
const pid = runOf(entry);
|
|
23
|
-
if (pid !== null && !
|
|
16
|
+
if (pid !== null && !processAlive(pid)) rmSync(join(parent, entry), {
|
|
24
17
|
force: true,
|
|
25
18
|
recursive: true
|
|
26
19
|
});
|
|
@@ -31,7 +24,7 @@ function sweepFinishedEvidence(folder) {
|
|
|
31
24
|
if (!existsSync(folder)) return;
|
|
32
25
|
for (const entry of readdirSync(folder)) {
|
|
33
26
|
const pid = runOf(entry);
|
|
34
|
-
if (pid === null || !
|
|
27
|
+
if (pid === null || !processAlive(pid)) rmSync(join(folder, entry), {
|
|
35
28
|
force: true,
|
|
36
29
|
recursive: true
|
|
37
30
|
});
|
|
@@ -1,10 +1,118 @@
|
|
|
1
1
|
import { t as isRecord } from "./shared-json-shape.js";
|
|
2
|
-
import { t as invariant } from "./shared-refusal.js";
|
|
2
|
+
import { n as refusal, t as invariant } from "./shared-refusal.js";
|
|
3
|
+
import { t as listenedPort } from "./shared-listened-port.js";
|
|
3
4
|
import { t as loadPeer } from "./shared-peer.js";
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
4
6
|
import { once } from "node:events";
|
|
5
7
|
import { createServer } from "node:http";
|
|
6
8
|
import { text } from "node:stream/consumers";
|
|
7
9
|
import { hostname } from "node:os";
|
|
10
|
+
//#region src/services/containers/docker-cli.ts
|
|
11
|
+
const DEFAULT_TIMEOUT = 1e4;
|
|
12
|
+
/** How every refusal of a run that asked for Docker names a daemon that did not answer. */
|
|
13
|
+
const DOCKER_ABSENT = "no Docker daemon answered";
|
|
14
|
+
/** Docker-aware mode reads what the daemon answers: a run where none answers is refused, never passed. */
|
|
15
|
+
function requireDockerDaemon() {
|
|
16
|
+
try {
|
|
17
|
+
execSync("docker version --format \"{{.Server.Version}}\"", {
|
|
18
|
+
stdio: "ignore",
|
|
19
|
+
timeout: DEFAULT_TIMEOUT
|
|
20
|
+
});
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw new Error(refusal({
|
|
23
|
+
fix: "start Docker (Docker Desktop, OrbStack, colima or another) and run again",
|
|
24
|
+
found: `specification.cli({ docker }) asked for Docker, and ${DOCKER_ABSENT}`,
|
|
25
|
+
read: "docs/guides/cli-specs.md#docker-aware-mode",
|
|
26
|
+
why: "Docker-aware mode reads the containers a run spawned, so it needs a Docker daemon."
|
|
27
|
+
}), { cause: error });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function findContainersByLabel(key, value) {
|
|
31
|
+
try {
|
|
32
|
+
return execSync(`docker ps -aq -f label=${key}=${value}`, {
|
|
33
|
+
encoding: "utf8",
|
|
34
|
+
timeout: DEFAULT_TIMEOUT
|
|
35
|
+
}).split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
36
|
+
} catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** A field of a value read off docker's JSON or a failed process: absent wherever the value does not hold it. */
|
|
41
|
+
function fieldOf(value, ...keys) {
|
|
42
|
+
if (typeof value !== "object" || value === null) return;
|
|
43
|
+
const key = keys.find((candidate) => candidate in value);
|
|
44
|
+
return key === void 0 ? void 0 : Reflect.get(value, key);
|
|
45
|
+
}
|
|
46
|
+
const streamOf = (error, key) => {
|
|
47
|
+
const stream = fieldOf(error, key);
|
|
48
|
+
return typeof stream === "string" ? stream : void 0;
|
|
49
|
+
};
|
|
50
|
+
function inspectContainer(id) {
|
|
51
|
+
const raw = execSync(`docker inspect ${id}`, {
|
|
52
|
+
encoding: "utf8",
|
|
53
|
+
timeout: DEFAULT_TIMEOUT
|
|
54
|
+
});
|
|
55
|
+
const parsed = JSON.parse(raw);
|
|
56
|
+
if (!Array.isArray(parsed)) return parsed;
|
|
57
|
+
return parsed[0];
|
|
58
|
+
}
|
|
59
|
+
function removeContainers(ids) {
|
|
60
|
+
if (ids.length === 0) return;
|
|
61
|
+
try {
|
|
62
|
+
execSync(`docker rm -f ${ids.join(" ")}`, {
|
|
63
|
+
encoding: "utf8",
|
|
64
|
+
stdio: "pipe",
|
|
65
|
+
timeout: DEFAULT_TIMEOUT
|
|
66
|
+
});
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
function containerLogs(id) {
|
|
70
|
+
try {
|
|
71
|
+
return execSync(`docker logs ${id}`, {
|
|
72
|
+
encoding: "utf8",
|
|
73
|
+
stdio: [
|
|
74
|
+
"ignore",
|
|
75
|
+
"pipe",
|
|
76
|
+
"pipe"
|
|
77
|
+
],
|
|
78
|
+
timeout: DEFAULT_TIMEOUT
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return streamOf(error, "stdout") ?? streamOf(error, "stderr") ?? "";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function readContainerFile(id, path) {
|
|
85
|
+
return execSync(`docker exec ${id} cat ${JSON.stringify(path)}`, {
|
|
86
|
+
encoding: "utf8",
|
|
87
|
+
timeout: DEFAULT_TIMEOUT
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function execInContainer(id, argv) {
|
|
91
|
+
try {
|
|
92
|
+
const quoted = argv.map((a) => JSON.stringify(a)).join(" ");
|
|
93
|
+
return {
|
|
94
|
+
exitCode: 0,
|
|
95
|
+
stderr: "",
|
|
96
|
+
stdout: execSync(`docker exec ${id} ${quoted}`, {
|
|
97
|
+
encoding: "utf8",
|
|
98
|
+
stdio: [
|
|
99
|
+
"ignore",
|
|
100
|
+
"pipe",
|
|
101
|
+
"pipe"
|
|
102
|
+
],
|
|
103
|
+
timeout: DEFAULT_TIMEOUT
|
|
104
|
+
})
|
|
105
|
+
};
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const status = fieldOf(error, "status");
|
|
108
|
+
return {
|
|
109
|
+
exitCode: typeof status === "number" ? status : 1,
|
|
110
|
+
stderr: streamOf(error, "stderr") ?? (error instanceof Error ? error.message : ""),
|
|
111
|
+
stdout: streamOf(error, "stdout") ?? ""
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
8
116
|
//#region src/services/containers/run-containers.ts
|
|
9
117
|
function canonical(value) {
|
|
10
118
|
return isRecord(value) ? Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, canonical(entry)])) : value;
|
|
@@ -95,17 +203,12 @@ async function serveContainers(containers) {
|
|
|
95
203
|
const server = createServer((request, response) => {
|
|
96
204
|
respond(containers, request, response);
|
|
97
205
|
});
|
|
98
|
-
server.listen(0, "127.0.0.1");
|
|
99
|
-
await once(server, "listening");
|
|
100
|
-
const address = server.address();
|
|
101
|
-
if (address === null || typeof address === "string") throw invariant("the run containers are served on no port");
|
|
102
|
-
const { port } = address;
|
|
103
206
|
return {
|
|
104
207
|
close: async () => {
|
|
105
208
|
server.close();
|
|
106
209
|
await once(server, "close");
|
|
107
210
|
},
|
|
108
|
-
url: `http://127.0.0.1:${
|
|
211
|
+
url: `http://127.0.0.1:${await listenedPort(server, { host: "127.0.0.1" })}`
|
|
109
212
|
};
|
|
110
213
|
}
|
|
111
214
|
//#endregion
|
|
@@ -198,6 +301,6 @@ var TestcontainersAdapter = class {
|
|
|
198
301
|
}
|
|
199
302
|
};
|
|
200
303
|
//#endregion
|
|
201
|
-
export { field as a, RunContainers as i, REUSED_LABEL as n, serveContainers as o, TestcontainersAdapter as r, OWNER_LABEL as t };
|
|
304
|
+
export { field as a, containerLogs as c, findContainersByLabel as d, inspectContainer as f, requireDockerDaemon as h, RunContainers as i, execInContainer as l, removeContainers as m, REUSED_LABEL as n, serveContainers as o, readContainerFile as p, TestcontainersAdapter as r, DOCKER_ABSENT as s, OWNER_LABEL as t, fieldOf as u };
|
|
202
305
|
|
|
203
306
|
//# sourceMappingURL=shared-testcontainers.adapter.js.map
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { TEMP_ROOTS_PARENT, setup as default, tempRootSetup };
|
|
1
|
+
export { t as TEMP_ROOTS_PARENT, n as default, r as tempRootSetup } from "./shared-temp-root.global-setup.js";
|
package/dist/vitest.d.ts
CHANGED
|
@@ -13,11 +13,18 @@ type SpecificationProjectOptions = {
|
|
|
13
13
|
/** Raise (or lower) the 30s budget for this project alone: each test's, and each hook's, which tears down what the test stood on. */
|
|
14
14
|
timeout?: number;
|
|
15
15
|
};
|
|
16
|
-
/** What a specification whose runner
|
|
17
|
-
type
|
|
18
|
-
/** The `*.specification.ts`
|
|
19
|
-
runner?: string;
|
|
16
|
+
/** What a specification whose spec files import their runner from its specification modules accepts on top. */
|
|
17
|
+
type RunnerOption = {
|
|
18
|
+
/** The `*.specification.ts` modules its spec files import their runner from: stated, a worker's files share one module graph and each module runs again before each file, so every file still boots its own subject. */
|
|
19
|
+
runner?: readonly string[] | string;
|
|
20
20
|
};
|
|
21
|
+
/** What a specification whose runner boots its subject in this process accepts on top. */
|
|
22
|
+
type InProcessProjectOptions = RunnerOption & SpecificationProjectOptions;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/specifications/api/api.project.d.ts
|
|
25
|
+
/** `api()`: the app met through HTTP. */
|
|
26
|
+
type ApiProjectOptions = InProcessProjectOptions;
|
|
27
|
+
export declare function api(options?: ApiProjectOptions): TestProjectInlineConfiguration;
|
|
21
28
|
//#endregion
|
|
22
29
|
//#region src/specifications/cli/documents/literate-plugin.d.ts
|
|
23
30
|
type LiterateOptions = {
|
|
@@ -50,23 +57,18 @@ type LiterateDoor = {
|
|
|
50
57
|
literate?: false | Partial<LiterateOptions>;
|
|
51
58
|
};
|
|
52
59
|
/** `cli()`: a binary, and the documents that specify it. */
|
|
53
|
-
type CliProjectOptions = LiterateDoor & SpecificationProjectOptions;
|
|
60
|
+
type CliProjectOptions = LiterateDoor & RunnerOption & SpecificationProjectOptions;
|
|
54
61
|
/** A cli project: the specification's `.spec.ts` files and its documents (docs/guides/spec-documents.md); the plugin's glob joins this project's include, never the root config. */
|
|
55
62
|
export declare function cli(options?: CliProjectOptions): TestProjectInlineConfiguration;
|
|
56
63
|
//#endregion
|
|
57
|
-
//#region src/specifications/api/api.project.d.ts
|
|
58
|
-
/** `api()`: the app met through HTTP. */
|
|
59
|
-
type ApiProjectOptions = InProcessProjectOptions;
|
|
60
|
-
export declare function api(options?: ApiProjectOptions): TestProjectInlineConfiguration;
|
|
61
|
-
//#endregion
|
|
62
64
|
//#region src/specifications/component/component.project.d.ts
|
|
63
65
|
type ComponentOptions = {
|
|
64
66
|
/** Freeze the page's `Date` for every render of the project. */
|
|
65
67
|
clock?: string;
|
|
66
68
|
/** `Accept-Language` and `Intl` locale of the page. Default `'en-US'`. */
|
|
67
69
|
locale?: string;
|
|
68
|
-
/** The directory `vite` and `wrap` resolve against;
|
|
69
|
-
root
|
|
70
|
+
/** The directory `vite` and `wrap` resolve against, absolute; `vitestConfig` hands it the project root, or the `root` a declared project states read from it. */
|
|
71
|
+
root: string;
|
|
70
72
|
/** IANA zone the page's `Date` reports in. Default `'UTC'`. */
|
|
71
73
|
timezone?: string;
|
|
72
74
|
/** The app's Vite pipeline: a config path, the object, or a framework's function. Only the pipeline is adopted. */
|
|
@@ -82,7 +84,7 @@ type ComponentOptions = {
|
|
|
82
84
|
/** `component()`: rendered units, in the `__tests__/` of the folder of the components they cover; under a specs tree, beside them. */
|
|
83
85
|
type ComponentProjectOptions = ComponentOptions & SpecificationProjectOptions;
|
|
84
86
|
/** A component project: renders in a real Chromium; the default `exclude` keeps it out of `specs/`. */
|
|
85
|
-
export declare function component(options
|
|
87
|
+
export declare function component(options: ComponentProjectOptions): Promise<TestProjectInlineConfiguration>;
|
|
86
88
|
//#endregion
|
|
87
89
|
//#region src/specifications/integration/integration.project.d.ts
|
|
88
90
|
/** `integration()`: a module against real services, or against a golden. */
|
|
@@ -108,12 +110,12 @@ type MobileProjectOptions = SpecificationProjectOptions & ({
|
|
|
108
110
|
device?: never;
|
|
109
111
|
root?: never;
|
|
110
112
|
});
|
|
111
|
-
/** A simulator cannot share a machine with itself: serial. A global setup holds
|
|
113
|
+
/** A simulator cannot share a machine with itself: serial, unless `serial: false` states a server that gives every session an app of its own. A global setup holds the run's driver sessions, one per worker. */
|
|
112
114
|
export declare function mobile(options?: MobileProjectOptions): TestProjectInlineConfiguration;
|
|
113
115
|
//#endregion
|
|
114
116
|
//#region src/specifications/website/website.project.d.ts
|
|
115
117
|
/** `website()`: the assembled product, met through a served page. */
|
|
116
|
-
type WebsiteProjectOptions = SpecificationProjectOptions;
|
|
118
|
+
type WebsiteProjectOptions = RunnerOption & SpecificationProjectOptions;
|
|
117
119
|
export declare function website(options?: WebsiteProjectOptions): TestProjectInlineConfiguration;
|
|
118
120
|
//#endregion
|
|
119
121
|
//#region src/vitest/unit.project.d.ts
|
|
@@ -128,13 +130,6 @@ type UnitProjectOptions = SpecificationProjectOptions & {
|
|
|
128
130
|
export declare function unit(options?: UnitProjectOptions): TestProjectInlineConfiguration;
|
|
129
131
|
//#endregion
|
|
130
132
|
//#region src/index.vitest.d.ts
|
|
131
|
-
/** A vite/vitest config, merged over the preset's defaults, plus `literate`. */
|
|
132
|
-
export type SpecConfig = ViteUserConfig & {
|
|
133
|
-
/** Turns matching `<case>.spec.yaml` files into tests; with `projects`, put the plugin in the collecting project instead. */
|
|
134
|
-
literate?: LiterateOptions;
|
|
135
|
-
};
|
|
136
|
-
/** The shared vitest config: what you pass is merged over the defaults. */
|
|
137
|
-
export declare function defineSpecConfig(config?: SpecConfig): ViteUserConfig;
|
|
138
133
|
export declare const vitestConfig: (configUrl: string) => Promise<ViteUserConfig>;
|
|
139
134
|
//#endregion
|
|
140
135
|
export type { ApiProjectOptions, CliProjectOptions, ComponentProjectOptions, InProcessProjectOptions, IntegrationProjectOptions, JobProjectOptions, LintProjectOptions, LiterateDoor, LiterateOptions, LiteratePlugin, MobileProjectOptions, SpecificationProjectOptions, UnitProjectOptions, WebsiteProjectOptions };
|