@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
package/dist/browser/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { Matcher, ProcessHandle, anthropic, banner, button, clock, complementary, component, content, contentinfo, defineStub, defineStubs, dialog, directory, disabled, enabled, field, file, focused, form, heading, http, intercept, link, listitem, main, match, mockOf, navigation, openai, option, packed, postgres, process, redis, region, required, row, ruleTester, search, selected, specification, sqlite, status, table, testId, text, valued, waitUntil, within };
|
|
1
|
+
export { Y as Matcher, t as ProcessHandle, x as anthropic, w as banner, N as button, _ as clock, T as complementary, C as component, P as content, E as contentinfo, q as defineStub, J as defineStubs, F as dialog, G as directory, d as disabled, f as enabled, I as field, K as file, p as focused, D as form, L as heading, S as http, y as intercept, R as link, z as listitem, O as main, X as match, g as mockOf, k as navigation, b as openai, B as option, v as packed, n as postgres, r as process, i as redis, A as region, l as required, V as row, a as ruleTester, j as search, m as selected, o as specification, s as sqlite, H as status, U as table, W as testId, u as text, h as valued, c as waitUntil, M as within } from "../browser-shared-index.browser.js";
|
package/dist/browser/services.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { ProcessHandle, postgres, process, redis, sqlite };
|
|
1
|
+
export { t as ProcessHandle, n as postgres, r as process, i as redis, s as sqlite } from "../browser-shared-index.browser.js";
|
package/dist/browser/stubs.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { anthropic, defineStub, defineStubs, http, intercept, openai };
|
|
1
|
+
export { x as anthropic, q as defineStub, J as defineStubs, S as http, y as intercept, b as openai } from "../browser-shared-index.browser.js";
|
|
@@ -191,14 +191,6 @@ const clock$1 = defineCapability({
|
|
|
191
191
|
}
|
|
192
192
|
});
|
|
193
193
|
//#endregion
|
|
194
|
-
//#region src/chain/pause.ts
|
|
195
|
-
async function pause(ms) {
|
|
196
|
-
if (ms <= 0) return;
|
|
197
|
-
await new Promise((resolve) => {
|
|
198
|
-
setTimeout(resolve, ms);
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
//#endregion
|
|
202
194
|
//#region src/stubs/exchange.ts
|
|
203
195
|
/** Each adapter serialises an object as JSON, so a stream declared as a plain value would arrive as `{}`: the tag tells them apart. */
|
|
204
196
|
const STREAM_BODY = "@jterrazz/test:stream";
|
|
@@ -718,7 +710,36 @@ var StubQueue = class {
|
|
|
718
710
|
}
|
|
719
711
|
};
|
|
720
712
|
//#endregion
|
|
713
|
+
//#region src/chain/pause.ts
|
|
714
|
+
async function pause(ms) {
|
|
715
|
+
if (ms <= 0) return;
|
|
716
|
+
await new Promise((resolve) => {
|
|
717
|
+
setTimeout(resolve, ms);
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
/** Asks `probe` until it answers something other than `undefined`, which it hands back, or answers `undefined` once `timeout` has passed. Measured on `performance.now()`, so `clock.at()`, which fakes `Date`, cannot freeze it. */
|
|
721
|
+
async function untilDeadline(probe, { interval = 0, timeout, wait }) {
|
|
722
|
+
const deadline = performance.now() + timeout;
|
|
723
|
+
for (;;) {
|
|
724
|
+
const answer = await probe();
|
|
725
|
+
if (answer !== void 0) return answer;
|
|
726
|
+
const remaining = deadline - performance.now();
|
|
727
|
+
if (remaining <= 0) return;
|
|
728
|
+
await (wait === void 0 ? pause(interval) : wait(remaining));
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
//#endregion
|
|
721
732
|
//#region src/stubs/delivery/stream-body.ts
|
|
733
|
+
async function nextTask() {
|
|
734
|
+
const { port1, port2 } = new MessageChannel();
|
|
735
|
+
await new Promise((resolve) => {
|
|
736
|
+
port1.onmessage = () => {
|
|
737
|
+
port1.close();
|
|
738
|
+
resolve();
|
|
739
|
+
};
|
|
740
|
+
port2.postMessage(null);
|
|
741
|
+
});
|
|
742
|
+
}
|
|
722
743
|
/** One serialisation for all three deliveries (msw server, msw worker, `node:http` stub). */
|
|
723
744
|
function toReadableStream(body) {
|
|
724
745
|
const encoder = new TextEncoder();
|
|
@@ -729,12 +750,39 @@ function toReadableStream(body) {
|
|
|
729
750
|
controller.close();
|
|
730
751
|
return;
|
|
731
752
|
}
|
|
732
|
-
if (
|
|
753
|
+
if (index > 0) {
|
|
754
|
+
await pause(body.delayBetweenChunks);
|
|
755
|
+
await nextTask();
|
|
756
|
+
}
|
|
733
757
|
index += 1;
|
|
734
758
|
controller.enqueue(encoder.encode(chunk));
|
|
735
759
|
} });
|
|
736
760
|
}
|
|
737
761
|
//#endregion
|
|
762
|
+
//#region src/stubs/delivery/stub-reply.ts
|
|
763
|
+
/** 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. */
|
|
764
|
+
function requestBody(raw) {
|
|
765
|
+
if (raw.length === 0) return null;
|
|
766
|
+
try {
|
|
767
|
+
return JSON.parse(raw);
|
|
768
|
+
} catch {
|
|
769
|
+
return raw;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
/** The reply a matched stub states for this request, once its delay has passed. */
|
|
773
|
+
async function replyOf(response, observed) {
|
|
774
|
+
const reply = typeof response === "function" ? response(observed) : response;
|
|
775
|
+
await pause(reply.delay ?? 0);
|
|
776
|
+
return reply;
|
|
777
|
+
}
|
|
778
|
+
/** The `error` of the 501 every delivery answers a request it refuses, whichever process serves it. */
|
|
779
|
+
function refusedReply(summary) {
|
|
780
|
+
return `@jterrazz/test strict stubs: ${summary}`;
|
|
781
|
+
}
|
|
782
|
+
function unmatchedSummary(method, url) {
|
|
783
|
+
return `unmatched request ${method} ${url}`;
|
|
784
|
+
}
|
|
785
|
+
//#endregion
|
|
738
786
|
//#region src/stubs/delivery/msw-handlers.ts
|
|
739
787
|
const NEVER_CACHED = { "cache-control": "no-store" };
|
|
740
788
|
function toMswResponse(msw, response) {
|
|
@@ -786,13 +834,14 @@ function handlerOf(msw, method) {
|
|
|
786
834
|
function buildStubHandlers(msw, stubs, bypass) {
|
|
787
835
|
const queue = new StubQueue(stubs);
|
|
788
836
|
let violation = null;
|
|
789
|
-
const
|
|
790
|
-
violation ??=
|
|
791
|
-
return msw.HttpResponse.json({ error:
|
|
837
|
+
const refuse = (error, summary) => {
|
|
838
|
+
violation ??= error;
|
|
839
|
+
return msw.HttpResponse.json({ error: refusedReply(summary) }, {
|
|
792
840
|
headers: NEVER_CACHED,
|
|
793
841
|
status: 501
|
|
794
842
|
});
|
|
795
843
|
};
|
|
844
|
+
const recordViolation = (method, url) => refuse(queue.unmatchedError(method, url), unmatchedSummary(method, url));
|
|
796
845
|
const handlers = [];
|
|
797
846
|
for (const route of queue.routes) for (const method of route.methods) {
|
|
798
847
|
const handlerFn = handlerOf(msw, method);
|
|
@@ -801,27 +850,19 @@ function buildStubHandlers(msw, stubs, bypass) {
|
|
|
801
850
|
const observed = await observe(request);
|
|
802
851
|
const stub = queue.take(observed);
|
|
803
852
|
if (!stub) return recordViolation(request.method, request.url);
|
|
804
|
-
|
|
805
|
-
await pause(response.delay ?? 0);
|
|
806
|
-
return toMswResponse(msw, response);
|
|
853
|
+
return toMswResponse(msw, await replyOf(stub.response, observed));
|
|
807
854
|
}));
|
|
808
855
|
}
|
|
809
856
|
handlers.push(msw.http.all("*", ({ request }) => bypass?.(request.url) === true ? msw.passthrough() : recordViolation(request.method, request.url)));
|
|
810
857
|
return {
|
|
811
858
|
handlers,
|
|
859
|
+
refuse,
|
|
812
860
|
violation: () => queue.verdict(violation)
|
|
813
861
|
};
|
|
814
862
|
}
|
|
815
863
|
async function observe(request) {
|
|
816
|
-
let body = null;
|
|
817
|
-
const rawText = await request.clone().text();
|
|
818
|
-
if (rawText) try {
|
|
819
|
-
body = JSON.parse(rawText);
|
|
820
|
-
} catch {
|
|
821
|
-
body = rawText;
|
|
822
|
-
}
|
|
823
864
|
return {
|
|
824
|
-
body,
|
|
865
|
+
body: requestBody(await request.clone().text()),
|
|
825
866
|
headers: Object.fromEntries(request.headers),
|
|
826
867
|
method: request.method.toUpperCase(),
|
|
827
868
|
url: request.url
|
|
@@ -842,15 +883,19 @@ function isRunnerRequest(url) {
|
|
|
842
883
|
const { pathname } = new URL(url);
|
|
843
884
|
return RUNNER_PREFIXES.some((prefix) => pathname.startsWith(prefix));
|
|
844
885
|
}
|
|
886
|
+
/** msw's hook for a frame no handler took: the subject's HTTP request prints msw's error; the runner's own request and a WebSocket frame, which carries none, pass. */
|
|
887
|
+
function onUnhandledFrame({ defaults, frame }) {
|
|
888
|
+
const { data } = frame;
|
|
889
|
+
if (typeof data !== "object" || data === null || !("request" in data)) return;
|
|
890
|
+
if (data.request instanceof Request && !isRunnerRequest(data.request.url)) defaults.error();
|
|
891
|
+
}
|
|
845
892
|
async function ensureStubWorker() {
|
|
846
893
|
if (workerInstance !== null) return;
|
|
847
894
|
const { setupWorker } = await import("msw/browser");
|
|
848
895
|
const worker = setupWorker();
|
|
849
896
|
workerInstance = worker;
|
|
850
897
|
await worker.start({
|
|
851
|
-
|
|
852
|
-
if (!isRunnerRequest(request.url)) print.error();
|
|
853
|
-
},
|
|
898
|
+
onUnhandledFrame,
|
|
854
899
|
quiet: true,
|
|
855
900
|
serviceWorker: { url: "/mockServiceWorker.js" }
|
|
856
901
|
});
|
|
@@ -1260,7 +1305,7 @@ function recordConsole() {
|
|
|
1260
1305
|
const ANSI_RE = /\u001B\[[0-9;]*m/gu;
|
|
1261
1306
|
/** rule `no-transform-duplicating-tokens`: a golden compares the text, never its colour. */
|
|
1262
1307
|
function stripAnsiCodes(value) {
|
|
1263
|
-
return value.
|
|
1308
|
+
return value.replaceAll(ANSI_RE, "");
|
|
1264
1309
|
}
|
|
1265
1310
|
//#endregion
|
|
1266
1311
|
//#region src/expectations/text/grep.ts
|
|
@@ -3154,18 +3199,15 @@ const DEFAULT_INTERVAL_MS = 50;
|
|
|
3154
3199
|
/** Wait until a condition holds; measured on `performance.now()`, so `clock.at()` cannot freeze it. Under `clock.run()` it never wakes: use `clock.advance()`. */
|
|
3155
3200
|
async function waitUntil(predicate, options = {}) {
|
|
3156
3201
|
const timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
if
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
}));
|
|
3167
|
-
await pause(interval);
|
|
3168
|
-
}
|
|
3202
|
+
if (await untilDeadline(async () => await predicate() || void 0, {
|
|
3203
|
+
interval: options.interval ?? DEFAULT_INTERVAL_MS,
|
|
3204
|
+
timeout
|
|
3205
|
+
}) === void 0) throw new Error(refusal({
|
|
3206
|
+
fix: "raise the budget if the wait is legitimate, or wait on what actually changes",
|
|
3207
|
+
found: `waitUntil(): ${options.why ?? "the condition"} did not hold within ${timeout}ms`,
|
|
3208
|
+
read: "docs/reference/assertions.md#two-helpers-that-are-not-matchers",
|
|
3209
|
+
why: "`waitUntil()` waits on a condition, within a budget."
|
|
3210
|
+
}));
|
|
3169
3211
|
}
|
|
3170
3212
|
//#endregion
|
|
3171
3213
|
//#region src/index.browser.ts
|
|
@@ -3188,4 +3230,4 @@ const sqlite = () => refuse("sqlite()");
|
|
|
3188
3230
|
//#endregion
|
|
3189
3231
|
export { region as A, option as B, component as C, form as D, contentinfo as E, dialog as F, directory as G, status as H, field as I, defineStubs as J, file as K, heading as L, within as M, button as N, main as O, content as P, link as R, http as S, complementary as T, table as U, row as V, testId as W, match as X, Matcher as Y, clock as _, ruleTester as a, openai as b, waitUntil as c, disabled as d, enabled as f, mockOf as g, valued as h, redis as i, search as j, navigation as k, required as l, selected as m, postgres as n, specification as o, focused as p, defineStub as q, process$1 as r, sqlite as s, ProcessHandle as t, text as u, packed as v, banner as w, anthropic as x, intercept as y, listitem as z };
|
|
3190
3232
|
|
|
3191
|
-
//# sourceMappingURL=shared-index.browser.js.map
|
|
3233
|
+
//# sourceMappingURL=browser-shared-index.browser.js.map
|