@jterrazz/test 15.3.0 → 16.0.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/README.md +120 -427
- package/dist/ambiguity.js +7 -6
- package/dist/appium.adapter.js +12 -11
- package/dist/artifacts.js +25 -1735
- package/dist/browser/index.js +256 -233
- package/dist/checker.d.ts +1 -1
- package/dist/checker.js +870 -73
- package/dist/fixtures.js +110 -0
- package/dist/index.d.ts +927 -971
- package/dist/index.js +2116 -2261
- package/dist/literate.js +1747 -0
- package/dist/match.js +625 -84
- package/dist/oxlint.cjs +3515 -1483
- package/dist/oxlint.d.cts +60 -48
- package/dist/oxlint.d.ts +60 -48
- package/dist/oxlint.js +3515 -1483
- package/dist/playwright.adapter.js +100 -6
- package/dist/{intercept.js → server.js} +2 -2
- package/dist/stream.js +6 -6
- package/dist/vitest.d.ts +67 -48
- package/dist/vitest.js +161 -85
- package/package.json +31 -11
- package/dist/spec-document.js +0 -651
package/dist/browser/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { TestRunner, expect, inject, onTestFinished, vi } from "vitest";
|
|
2
2
|
import { commands, page, userEvent } from "vitest/browser";
|
|
3
3
|
import { mock, mockDeep } from "vitest-mock-extended";
|
|
4
|
-
|
|
5
|
-
//#region src/specification/contracts/contract.ts
|
|
4
|
+
//#region src/model/contracts/contract.ts
|
|
6
5
|
/**
|
|
7
6
|
* Declare one contract. Identity function — its value is the enforced shape
|
|
8
7
|
* and the naming convention:
|
|
@@ -88,7 +87,7 @@ function contractsOf(input) {
|
|
|
88
87
|
return flatten([input]);
|
|
89
88
|
}
|
|
90
89
|
//#endregion
|
|
91
|
-
//#region src/
|
|
90
|
+
//#region src/model/contracts/intercept.ts
|
|
92
91
|
/**
|
|
93
92
|
* The network double in MODULE scope — the same contracts, the same queue and
|
|
94
93
|
* the same strictness as a chain's `.intercept()`, for the one kind of test
|
|
@@ -120,6 +119,30 @@ function declared(requestOrContracts, maybeResponse) {
|
|
|
120
119
|
response: maybeResponse
|
|
121
120
|
}];
|
|
122
121
|
}
|
|
122
|
+
/** Is this argument the options object rather than a response? */
|
|
123
|
+
function isOptions(value) {
|
|
124
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "origin" in value;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Resolve every relative request against `origin` for the life of the scope.
|
|
128
|
+
*
|
|
129
|
+
* The framework owns the seam so a test never has to: this is the one place
|
|
130
|
+
* `fetch` is wrapped, it is put back when the scope ends, and what it does is
|
|
131
|
+
* the one thing a page would have done for free — turn `/api/posts` into a URL.
|
|
132
|
+
*/
|
|
133
|
+
function resolveRelativeAgainst(origin) {
|
|
134
|
+
let base;
|
|
135
|
+
try {
|
|
136
|
+
base = new URL(origin);
|
|
137
|
+
} catch {
|
|
138
|
+
throw new Error(`intercept(): \`origin\` states where a relative request resolves — \`${origin}\` is not an absolute URL (e.g. 'http://console.test').`);
|
|
139
|
+
}
|
|
140
|
+
const original = globalThis.fetch;
|
|
141
|
+
globalThis.fetch = async (input, init) => typeof input === "string" && input.startsWith("/") ? await original(new URL(input, base).toString(), init) : await original(input, init);
|
|
142
|
+
return () => {
|
|
143
|
+
globalThis.fetch = original;
|
|
144
|
+
};
|
|
145
|
+
}
|
|
123
146
|
/**
|
|
124
147
|
* Build the module-scope double on one engine. The node entry hands it msw's
|
|
125
148
|
* server, the page's entry hands it msw's worker: one shape, two transports,
|
|
@@ -128,13 +151,22 @@ function declared(requestOrContracts, maybeResponse) {
|
|
|
128
151
|
* @internal
|
|
129
152
|
*/
|
|
130
153
|
function interceptThrough(register) {
|
|
131
|
-
async function intercept(requestOrContracts,
|
|
132
|
-
const
|
|
154
|
+
async function intercept(requestOrContracts, maybeResponseOrOptions, maybeOptions) {
|
|
155
|
+
const options = isOptions(maybeOptions) ? maybeOptions : isOptions(maybeResponseOrOptions) ? maybeResponseOrOptions : void 0;
|
|
156
|
+
const contracts = declared(requestOrContracts, options === maybeResponseOrOptions ? void 0 : maybeResponseOrOptions);
|
|
133
157
|
if (contracts.length === 0) throw new Error("intercept(): declare at least one contract — an empty list guards nothing, and a subject with no network needs no intercept at all.");
|
|
134
|
-
const
|
|
158
|
+
const restoreOrigin = options?.origin === void 0 ? void 0 : resolveRelativeAgainst(options.origin);
|
|
159
|
+
let registration;
|
|
160
|
+
try {
|
|
161
|
+
registration = await register(contracts);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
restoreOrigin?.();
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
135
166
|
return { [Symbol.asyncDispose]: async () => {
|
|
136
167
|
const violation = registration.violation();
|
|
137
168
|
registration.cleanup();
|
|
169
|
+
restoreOrigin?.();
|
|
138
170
|
await Promise.resolve();
|
|
139
171
|
if (violation) throw violation;
|
|
140
172
|
} };
|
|
@@ -142,7 +174,7 @@ function interceptThrough(register) {
|
|
|
142
174
|
return intercept;
|
|
143
175
|
}
|
|
144
176
|
//#endregion
|
|
145
|
-
//#region src/
|
|
177
|
+
//#region src/model/matching/match.ts
|
|
146
178
|
/** Every kind usable as a `{{token}}` in fixture files (all but ref/regex/includes). */
|
|
147
179
|
const TOKEN_KINDS = [
|
|
148
180
|
"any",
|
|
@@ -295,7 +327,94 @@ var CaptureScope = class {
|
|
|
295
327
|
}
|
|
296
328
|
};
|
|
297
329
|
//#endregion
|
|
298
|
-
//#region src/
|
|
330
|
+
//#region src/model/result/grep.ts
|
|
331
|
+
/**
|
|
332
|
+
* Extract text blocks from output that contain a pattern.
|
|
333
|
+
* Splits by blank lines (how linter/compiler output is structured),
|
|
334
|
+
* returns only blocks matching the pattern.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* expect(grep(result.stdout, "unused-var.ts")).toContain("no-unused-vars")
|
|
338
|
+
* expect(grep(result.stdout, "valid/sorted.ts")).not.toContain("sort-imports")
|
|
339
|
+
*/
|
|
340
|
+
function grep(output, pattern) {
|
|
341
|
+
return output.replaceAll(/\u001B\[[0-9;]*m/gu, "").split(/\n\s*\n/u).filter((block) => block.includes(pattern)).join("\n\n");
|
|
342
|
+
}
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/model/result/text.ts
|
|
345
|
+
const ANSI_RE = /\u001B\[[0-9;]*m/gu;
|
|
346
|
+
/** Strip ANSI escape sequences from a string. */
|
|
347
|
+
function stripAnsiCodes(value) {
|
|
348
|
+
return value.replace(ANSI_RE, "");
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Read-only accessor for a captured text handle — THE universal one: stdout,
|
|
352
|
+
* stderr, container logs, and file text all surface as a `TextAccessor`.
|
|
353
|
+
*
|
|
354
|
+
* Backed by a primitive string, exposed via {@link text}, but also provides
|
|
355
|
+
* `toString()` / `valueOf()` for string coercion, so common patterns like
|
|
356
|
+
* `String(result.stdout)` and template-literal interpolation still work.
|
|
357
|
+
*
|
|
358
|
+
* ANSI escape sequences are stripped by default before every comparison
|
|
359
|
+
* (CONVENTIONS D6) — `.text` stays raw. The runner `transform` option remains
|
|
360
|
+
* an escape hatch for application noise not covered by the `{{token}}`
|
|
361
|
+
* grammar.
|
|
362
|
+
*
|
|
363
|
+
* Text operations are **closed** over the type: `.grep(pattern)` returns a
|
|
364
|
+
* `TextAccessor` (not a bare string), preserving the `_expected/`-resolution
|
|
365
|
+
* context and the `transform`, so results are chainable
|
|
366
|
+
* (`result.stdout.grep(a).grep(b)`) and snapshot-able
|
|
367
|
+
* (`expect(result.stdout.grep('users.ts')).toMatch('block.txt')`).
|
|
368
|
+
*
|
|
369
|
+
* Assertions go through `expect()` matchers: `expect(result.stdout).toContain(...)`
|
|
370
|
+
* and `expect(result.stdout).toMatch('name.txt')` (resolved against
|
|
371
|
+
* `_expected/<name>`, flat — with `{{token}}` support, CONVENTIONS D4).
|
|
372
|
+
*/
|
|
373
|
+
var TextAccessor = class TextAccessor {
|
|
374
|
+
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
375
|
+
captures;
|
|
376
|
+
/** @internal Stream label used in failure messages ("stdout" / "stderr"). */
|
|
377
|
+
streamName;
|
|
378
|
+
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
379
|
+
testDir;
|
|
380
|
+
/** The raw captured text (never transformed, ANSI preserved). */
|
|
381
|
+
text;
|
|
382
|
+
/** @internal Normaliser applied before comparisons, never to fixtures. */
|
|
383
|
+
transform;
|
|
384
|
+
constructor(value, streamName, testDir, options = {}) {
|
|
385
|
+
this.text = value;
|
|
386
|
+
this.streamName = streamName;
|
|
387
|
+
this.testDir = testDir;
|
|
388
|
+
this.transform = options.transform;
|
|
389
|
+
this.captures = options.captures ?? new CaptureScope();
|
|
390
|
+
}
|
|
391
|
+
/** @internal The text as compared by matchers — ANSI stripped, transform applied. */
|
|
392
|
+
get comparableText() {
|
|
393
|
+
const stripped = stripAnsiCodes(this.text);
|
|
394
|
+
return this.transform ? this.transform(stripped) : stripped;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Keep only the blank-line-separated blocks of the text that contain
|
|
398
|
+
* `pattern` (how linter/compiler output is structured), returned as a new
|
|
399
|
+
* `TextAccessor` — the same subject type, so the result is chainable and
|
|
400
|
+
* snapshot-able. The scalpel for probing large tool outputs; snapshot the
|
|
401
|
+
* whole surface by default, reach for `.grep()` for targeted checks.
|
|
402
|
+
*/
|
|
403
|
+
grep(pattern) {
|
|
404
|
+
return new TextAccessor(grep(this.text, pattern), this.streamName, this.testDir, {
|
|
405
|
+
captures: this.captures,
|
|
406
|
+
transform: this.transform
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
toString() {
|
|
410
|
+
return this.text;
|
|
411
|
+
}
|
|
412
|
+
valueOf() {
|
|
413
|
+
return this.text;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/model/matching/structural.ts
|
|
299
418
|
/**
|
|
300
419
|
* Structural comparison engine shared by every fixture matcher.
|
|
301
420
|
*
|
|
@@ -528,7 +647,7 @@ function textEquals(expected, actual, scope) {
|
|
|
528
647
|
return placeholderStringMatches(expected, actual, scope);
|
|
529
648
|
}
|
|
530
649
|
//#endregion
|
|
531
|
-
//#region src/
|
|
650
|
+
//#region src/model/contracts/queue.ts
|
|
532
651
|
/**
|
|
533
652
|
* The ONE contract queue — pure, engine-agnostic (no MSW, no node:http).
|
|
534
653
|
* Both engines consume it: the MSW integration on api/jobs chains, and the
|
|
@@ -701,7 +820,7 @@ var ContractQueue = class {
|
|
|
701
820
|
}
|
|
702
821
|
};
|
|
703
822
|
//#endregion
|
|
704
|
-
//#region src/
|
|
823
|
+
//#region src/model/timing/pause.ts
|
|
705
824
|
/**
|
|
706
825
|
* One answer to "wait" — the only `setTimeout` the model owns.
|
|
707
826
|
*
|
|
@@ -716,7 +835,7 @@ async function pause(ms) {
|
|
|
716
835
|
});
|
|
717
836
|
}
|
|
718
837
|
//#endregion
|
|
719
|
-
//#region src/
|
|
838
|
+
//#region src/model/contracts/stream.ts
|
|
720
839
|
/**
|
|
721
840
|
* A declared stream, made real — ONE serialisation for all three engines.
|
|
722
841
|
*
|
|
@@ -746,7 +865,7 @@ function toReadableStream(body) {
|
|
|
746
865
|
} });
|
|
747
866
|
}
|
|
748
867
|
//#endregion
|
|
749
|
-
//#region src/
|
|
868
|
+
//#region src/model/contracts/types.ts
|
|
750
869
|
/**
|
|
751
870
|
* The marker that tells a STREAMED body from a value that happens to be an
|
|
752
871
|
* object. A body reaches three different engines (msw's server, msw's worker,
|
|
@@ -760,7 +879,7 @@ function isStreamBody(body) {
|
|
|
760
879
|
return typeof body === "object" && body !== null && "kind" in body && body.kind === "@jterrazz/test:stream";
|
|
761
880
|
}
|
|
762
881
|
//#endregion
|
|
763
|
-
//#region src/
|
|
882
|
+
//#region src/seams/msw/handlers.ts
|
|
764
883
|
/**
|
|
765
884
|
* A contract answers the request that was made, never a copy of an earlier one.
|
|
766
885
|
*
|
|
@@ -866,7 +985,7 @@ async function observe(request) {
|
|
|
866
985
|
};
|
|
867
986
|
}
|
|
868
987
|
//#endregion
|
|
869
|
-
//#region src/
|
|
988
|
+
//#region src/seams/msw/worker.ts
|
|
870
989
|
/**
|
|
871
990
|
* What the runner itself fetches. Everything else a page asks for is the
|
|
872
991
|
* subject's own traffic and answers to its contracts (D7).
|
|
@@ -935,7 +1054,7 @@ function resetWorkerContracts() {
|
|
|
935
1054
|
if (workerInstance !== null) workerInstance.resetHandlers();
|
|
936
1055
|
}
|
|
937
1056
|
//#endregion
|
|
938
|
-
//#region src/
|
|
1057
|
+
//#region src/model/chain/ground.ts
|
|
939
1058
|
/**
|
|
940
1059
|
* The GROUND a row of specs stands on — the naming law of a spec tree.
|
|
941
1060
|
*
|
|
@@ -958,7 +1077,7 @@ function resetWorkerContracts() {
|
|
|
958
1077
|
/** Goldens: every expected fixture, flat, extension included. */
|
|
959
1078
|
const GROUND_EXPECTED = "_expected";
|
|
960
1079
|
//#endregion
|
|
961
|
-
//#region src/
|
|
1080
|
+
//#region src/model/chain/reporter.ts
|
|
962
1081
|
const GREEN = "\x1B[32m";
|
|
963
1082
|
const RED = "\x1B[31m";
|
|
964
1083
|
const RESET = "\x1B[0m";
|
|
@@ -1009,7 +1128,7 @@ function formatStdoutDiff(file, expected, actual, options) {
|
|
|
1009
1128
|
return [...lines, ...rendered].join("\n");
|
|
1010
1129
|
}
|
|
1011
1130
|
//#endregion
|
|
1012
|
-
//#region src/
|
|
1131
|
+
//#region src/model/result/text-assertions.ts
|
|
1013
1132
|
/** The fixture name carries its extension — the name IS the file. */
|
|
1014
1133
|
function requireExtension(name, subject) {
|
|
1015
1134
|
if (!/\.[A-Za-z0-9]+$/u.test(name)) throw new Error(`toMatch("${name}"): the extension is part of the name and is required for ${subject} subjects (e.g. "help.txt").`);
|
|
@@ -1091,94 +1210,7 @@ function isIterable(value) {
|
|
|
1091
1210
|
return value !== null && value !== void 0 && typeof value[Symbol.iterator] === "function";
|
|
1092
1211
|
}
|
|
1093
1212
|
//#endregion
|
|
1094
|
-
//#region src/
|
|
1095
|
-
/**
|
|
1096
|
-
* Extract text blocks from output that contain a pattern.
|
|
1097
|
-
* Splits by blank lines (how linter/compiler output is structured),
|
|
1098
|
-
* returns only blocks matching the pattern.
|
|
1099
|
-
*
|
|
1100
|
-
* @example
|
|
1101
|
-
* expect(grep(result.stdout, "unused-var.ts")).toContain("no-unused-vars")
|
|
1102
|
-
* expect(grep(result.stdout, "valid/sorted.ts")).not.toContain("sort-imports")
|
|
1103
|
-
*/
|
|
1104
|
-
function grep(output, pattern) {
|
|
1105
|
-
return output.replaceAll(/\u001B\[[0-9;]*m/gu, "").split(/\n\s*\n/u).filter((block) => block.includes(pattern)).join("\n\n");
|
|
1106
|
-
}
|
|
1107
|
-
//#endregion
|
|
1108
|
-
//#region src/specification/facets/_common/result/text.ts
|
|
1109
|
-
const ANSI_RE = /\u001B\[[0-9;]*m/gu;
|
|
1110
|
-
/** Strip ANSI escape sequences from a string. */
|
|
1111
|
-
function stripAnsiCodes(value) {
|
|
1112
|
-
return value.replace(ANSI_RE, "");
|
|
1113
|
-
}
|
|
1114
|
-
/**
|
|
1115
|
-
* Read-only accessor for a captured text handle — THE universal one: stdout,
|
|
1116
|
-
* stderr, container logs, and file text all surface as a `TextAccessor`.
|
|
1117
|
-
*
|
|
1118
|
-
* Backed by a primitive string, exposed via {@link text}, but also provides
|
|
1119
|
-
* `toString()` / `valueOf()` for string coercion, so common patterns like
|
|
1120
|
-
* `String(result.stdout)` and template-literal interpolation still work.
|
|
1121
|
-
*
|
|
1122
|
-
* ANSI escape sequences are stripped by default before every comparison
|
|
1123
|
-
* (CONVENTIONS D6) — `.text` stays raw. The runner `transform` option remains
|
|
1124
|
-
* an escape hatch for application noise not covered by the `{{token}}`
|
|
1125
|
-
* grammar.
|
|
1126
|
-
*
|
|
1127
|
-
* Text operations are **closed** over the type: `.grep(pattern)` returns a
|
|
1128
|
-
* `TextAccessor` (not a bare string), preserving the `_expected/`-resolution
|
|
1129
|
-
* context and the `transform`, so results are chainable
|
|
1130
|
-
* (`result.stdout.grep(a).grep(b)`) and snapshot-able
|
|
1131
|
-
* (`expect(result.stdout.grep('users.ts')).toMatch('block.txt')`).
|
|
1132
|
-
*
|
|
1133
|
-
* Assertions go through `expect()` matchers: `expect(result.stdout).toContain(...)`
|
|
1134
|
-
* and `expect(result.stdout).toMatch('name.txt')` (resolved against
|
|
1135
|
-
* `_expected/<name>`, flat — with `{{token}}` support, CONVENTIONS D4).
|
|
1136
|
-
*/
|
|
1137
|
-
var TextAccessor = class TextAccessor {
|
|
1138
|
-
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
1139
|
-
captures;
|
|
1140
|
-
/** @internal Stream label used in failure messages ("stdout" / "stderr"). */
|
|
1141
|
-
streamName;
|
|
1142
|
-
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
1143
|
-
testDir;
|
|
1144
|
-
/** The raw captured text (never transformed, ANSI preserved). */
|
|
1145
|
-
text;
|
|
1146
|
-
/** @internal Normaliser applied before comparisons, never to fixtures. */
|
|
1147
|
-
transform;
|
|
1148
|
-
constructor(value, streamName, testDir, options = {}) {
|
|
1149
|
-
this.text = value;
|
|
1150
|
-
this.streamName = streamName;
|
|
1151
|
-
this.testDir = testDir;
|
|
1152
|
-
this.transform = options.transform;
|
|
1153
|
-
this.captures = options.captures ?? new CaptureScope();
|
|
1154
|
-
}
|
|
1155
|
-
/** @internal The text as compared by matchers — ANSI stripped, transform applied. */
|
|
1156
|
-
get comparableText() {
|
|
1157
|
-
const stripped = stripAnsiCodes(this.text);
|
|
1158
|
-
return this.transform ? this.transform(stripped) : stripped;
|
|
1159
|
-
}
|
|
1160
|
-
/**
|
|
1161
|
-
* Keep only the blank-line-separated blocks of the text that contain
|
|
1162
|
-
* `pattern` (how linter/compiler output is structured), returned as a new
|
|
1163
|
-
* `TextAccessor` — the same subject type, so the result is chainable and
|
|
1164
|
-
* snapshot-able. The scalpel for probing large tool outputs; snapshot the
|
|
1165
|
-
* whole surface by default, reach for `.grep()` for targeted checks.
|
|
1166
|
-
*/
|
|
1167
|
-
grep(pattern) {
|
|
1168
|
-
return new TextAccessor(grep(this.text, pattern), this.streamName, this.testDir, {
|
|
1169
|
-
captures: this.captures,
|
|
1170
|
-
transform: this.transform
|
|
1171
|
-
});
|
|
1172
|
-
}
|
|
1173
|
-
toString() {
|
|
1174
|
-
return this.text;
|
|
1175
|
-
}
|
|
1176
|
-
valueOf() {
|
|
1177
|
-
return this.text;
|
|
1178
|
-
}
|
|
1179
|
-
};
|
|
1180
|
-
//#endregion
|
|
1181
|
-
//#region src/specification/facets/website/ambiguity.ts
|
|
1213
|
+
//#region src/model/elements/ambiguity.ts
|
|
1182
1214
|
/**
|
|
1183
1215
|
* The ambiguity refusal — CONVENTIONS W3.
|
|
1184
1216
|
*
|
|
@@ -1232,8 +1264,9 @@ const CONTEXT_LANDMARKS = {
|
|
|
1232
1264
|
};
|
|
1233
1265
|
/**
|
|
1234
1266
|
* Render a descriptor as the source that would build it — `link('Articles')`,
|
|
1235
|
-
* `within(navigation(), link('Articles', { exact:
|
|
1236
|
-
* the vocabulary the author wrote, never playwright's
|
|
1267
|
+
* `within(navigation(), link('Articles', { exact: false }))`. The error speaks
|
|
1268
|
+
* the vocabulary the author wrote, never playwright's — so the option appears
|
|
1269
|
+
* only when the AUTHOR stated one, whichever way.
|
|
1237
1270
|
*/
|
|
1238
1271
|
function formatElement(element) {
|
|
1239
1272
|
const bare = formatBare(element);
|
|
@@ -1242,7 +1275,7 @@ function formatElement(element) {
|
|
|
1242
1275
|
function formatBare(element) {
|
|
1243
1276
|
const args = [];
|
|
1244
1277
|
if (element.name !== void 0) args.push(JSON.stringify(element.name));
|
|
1245
|
-
if (element.exact) args.push(
|
|
1278
|
+
if (element.exact !== void 0) args.push(`{ exact: ${String(element.exact)} }`);
|
|
1246
1279
|
return `${CONSTRUCTORS[element.kind]}(${args.join(", ")})`;
|
|
1247
1280
|
}
|
|
1248
1281
|
/** `1. <a href="/articles">Articles</a> in <nav>` — one evidence line per candidate. */
|
|
@@ -1278,7 +1311,7 @@ function formatFixes(element, matches) {
|
|
|
1278
1311
|
const alsoHere = rest.length > 0 ? ` [also here: ${rest.map(([name]) => name).join(", ")}]` : "";
|
|
1279
1312
|
fixes.push(first === void 0 ? `scope it within(<a container holding only this one>, ${formatBare(element)})` : `scope it within(${first[0]}, ${formatBare(element)}) [leaves ${first[1]} of ${matches.length}]${alsoHere}`);
|
|
1280
1313
|
}
|
|
1281
|
-
if (
|
|
1314
|
+
if (element.exact === false && element.name !== void 0) {
|
|
1282
1315
|
const remaining = matches.filter((match) => match.text === element.name).length;
|
|
1283
1316
|
if (remaining > 0 && remaining < matches.length) fixes.push(`exact name ${formatBare({
|
|
1284
1317
|
...element,
|
|
@@ -1318,11 +1351,52 @@ function describeAmbiguity(options) {
|
|
|
1318
1351
|
"Disambiguate with one of:",
|
|
1319
1352
|
...fixes,
|
|
1320
1353
|
"",
|
|
1321
|
-
"Docs: docs/
|
|
1354
|
+
"Docs: docs/13-elements.md#designating-exactly-one-element (CONVENTIONS W3)"
|
|
1322
1355
|
].join("\n");
|
|
1323
1356
|
}
|
|
1324
1357
|
//#endregion
|
|
1325
|
-
//#region src/
|
|
1358
|
+
//#region src/model/elements/substring-warning.ts
|
|
1359
|
+
/**
|
|
1360
|
+
* The transitional warning for a name that matches only as a SUBSTRING.
|
|
1361
|
+
*
|
|
1362
|
+
* Until 16.0 a descriptor's name matched any element whose accessible name
|
|
1363
|
+
* CONTAINED it, so `link('Articles')` also designated "Read Articles" — and a
|
|
1364
|
+
* test could pass for years against the wrong element without anyone noticing,
|
|
1365
|
+
* because the wrong element was usually next to the right one. Exact is the
|
|
1366
|
+
* default now.
|
|
1367
|
+
*
|
|
1368
|
+
* A lint rule cannot carry this: whether a name matched whole or in part is a
|
|
1369
|
+
* fact about a RUN, and a verdict fed by a previous run's artefact is not
|
|
1370
|
+
* deterministic. So it is a runtime warning, printed by the adapter that did
|
|
1371
|
+
* the looking, at the moment it has both answers in hand — nothing found under
|
|
1372
|
+
* exact, something found under substring.
|
|
1373
|
+
*
|
|
1374
|
+
* It is printed ONCE per descriptor per process: a scenario that clicks the
|
|
1375
|
+
* same link in ten tests has one thing to fix, not ten lines to read.
|
|
1376
|
+
*/
|
|
1377
|
+
/** How long the two-release window lasts, stated in the message itself. */
|
|
1378
|
+
const WINDOW = "16.0 and 16.1";
|
|
1379
|
+
/** Descriptors already reported in this process. */
|
|
1380
|
+
const reported = /* @__PURE__ */ new Set();
|
|
1381
|
+
/** The identity a descriptor is deduplicated by — kind, name, and its scope chain. */
|
|
1382
|
+
function identityOf(element) {
|
|
1383
|
+
const own = `${element.kind}:${element.name ?? ""}`;
|
|
1384
|
+
return element.scope === void 0 ? own : `${identityOf(element.scope)}>${own}`;
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Report that `element` designated nothing as a whole name, but would have
|
|
1388
|
+
* designated something as a substring. Returns whether a line was printed,
|
|
1389
|
+
* which is what the adapters' own tests assert on.
|
|
1390
|
+
*/
|
|
1391
|
+
function warnSubstringOnly(element, where) {
|
|
1392
|
+
const identity = identityOf(element);
|
|
1393
|
+
if (reported.has(identity)) return false;
|
|
1394
|
+
reported.add(identity);
|
|
1395
|
+
console.warn(`@jterrazz/test: ${element.kind}('${element.name ?? ""}') matched nothing as a whole accessible name, and matched only as a SUBSTRING of a longer one (${where}). Names are exact from 16.0: write the name in full, scope it with \`within(…)\`, or state \`{ exact: false }\` to keep the old behaviour. This warning ships for ${WINDOW}; after that the substring match is gone and this is simply not found.`);
|
|
1396
|
+
return true;
|
|
1397
|
+
}
|
|
1398
|
+
//#endregion
|
|
1399
|
+
//#region src/seams/vitest-browser/vitest-browser.adapter.ts
|
|
1326
1400
|
/**
|
|
1327
1401
|
* The browser-mode adapter — the component facet's half of the one element
|
|
1328
1402
|
* vocabulary.
|
|
@@ -1343,6 +1417,29 @@ const MAX_REPORTED_MATCHES = 10;
|
|
|
1343
1417
|
/** The landmark ancestors the ambiguity evidence names. */
|
|
1344
1418
|
const LANDMARKS = "nav, header, footer, main, aside, section, form, [role]";
|
|
1345
1419
|
/**
|
|
1420
|
+
* The roles a FIELD can carry — what `field('Name')` designates.
|
|
1421
|
+
*
|
|
1422
|
+
* A field is named by its ACCESSIBLE name, not by the text of the label
|
|
1423
|
+
* element that happens to wrap it: `<label><span>Channel</span><select>…` has
|
|
1424
|
+
* a label whose text carries every option of the select, while the control's
|
|
1425
|
+
* accessible name is `Channel` — which is what the ARIA tree shows and what a
|
|
1426
|
+
* screen reader reads. Resolving by label text alone made the ordinary
|
|
1427
|
+
* wrapping-label form unreachable under the 16.0 whole-name default.
|
|
1428
|
+
*
|
|
1429
|
+
* The label-text locator stays in the union for the controls ARIA gives no
|
|
1430
|
+
* role at all (a date or colour input), which nothing else would find.
|
|
1431
|
+
*/
|
|
1432
|
+
const FIELD_ROLES = [
|
|
1433
|
+
"checkbox",
|
|
1434
|
+
"combobox",
|
|
1435
|
+
"radio",
|
|
1436
|
+
"searchbox",
|
|
1437
|
+
"slider",
|
|
1438
|
+
"spinbutton",
|
|
1439
|
+
"switch",
|
|
1440
|
+
"textbox"
|
|
1441
|
+
];
|
|
1442
|
+
/**
|
|
1346
1443
|
* Translate a user-facing descriptor into a Browser Mode locator, resolving
|
|
1347
1444
|
* `scope` outside-in so `within(navigation(), link('X'))` searches the nav
|
|
1348
1445
|
* subtree. No `.first()` anywhere: designating exactly one element is
|
|
@@ -1351,9 +1448,16 @@ const LANDMARKS = "nav, header, footer, main, aside, section, form, [role]";
|
|
|
1351
1448
|
*/
|
|
1352
1449
|
function locate(root, element) {
|
|
1353
1450
|
const scope = element.scope ? locate(root, element.scope) : root;
|
|
1354
|
-
const exact = element.exact ??
|
|
1451
|
+
const exact = element.exact ?? true;
|
|
1355
1452
|
const name = element.name ?? "";
|
|
1356
|
-
if (element.kind === "field")
|
|
1453
|
+
if (element.kind === "field") {
|
|
1454
|
+
let field = scope.getByLabelText(name, { exact });
|
|
1455
|
+
for (const role of FIELD_ROLES) field = field.or(scope.getByRole(role, {
|
|
1456
|
+
exact,
|
|
1457
|
+
name
|
|
1458
|
+
}));
|
|
1459
|
+
return field;
|
|
1460
|
+
}
|
|
1357
1461
|
if (element.kind === "testId") return scope.getByTestId(name);
|
|
1358
1462
|
if (element.kind === "text") return scope.getByText(name, { exact });
|
|
1359
1463
|
return scope.getByRole(element.kind, element.name === void 0 ? { exact } : {
|
|
@@ -1396,7 +1500,10 @@ async function act(element, action) {
|
|
|
1396
1500
|
try {
|
|
1397
1501
|
return await action(locator);
|
|
1398
1502
|
} catch (error) {
|
|
1399
|
-
if (locator.elements().length <= 1)
|
|
1503
|
+
if (locator.elements().length <= 1) {
|
|
1504
|
+
reportSubstringOnly(element);
|
|
1505
|
+
throw error;
|
|
1506
|
+
}
|
|
1400
1507
|
const culprit = ambiguousLevel(element);
|
|
1401
1508
|
const matches = candidates(locate(page, culprit));
|
|
1402
1509
|
const url = globalThis.location.href;
|
|
@@ -1408,6 +1515,24 @@ async function act(element, action) {
|
|
|
1408
1515
|
}
|
|
1409
1516
|
}
|
|
1410
1517
|
/**
|
|
1518
|
+
* Say so when a descriptor found NOTHING as a whole name but would have found
|
|
1519
|
+
* something as a substring — the one shape the whole-name default changes.
|
|
1520
|
+
*
|
|
1521
|
+
* Asked only on the failure path, and only for a descriptor that stated no
|
|
1522
|
+
* `exact` of its own: an author who wrote `{ exact: false }` chose the
|
|
1523
|
+
* substring, and one who wrote `{ exact: true }` was already exact.
|
|
1524
|
+
*/
|
|
1525
|
+
function reportSubstringOnly(element) {
|
|
1526
|
+
if (element.exact !== void 0 || element.name === void 0) return;
|
|
1527
|
+
try {
|
|
1528
|
+
if (locate(page, element).elements().length > 0) return;
|
|
1529
|
+
if (locate(page, {
|
|
1530
|
+
...element,
|
|
1531
|
+
exact: false
|
|
1532
|
+
}).elements().length > 0) warnSubstringOnly(element, globalThis.location.href);
|
|
1533
|
+
} catch {}
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1411
1536
|
* Is this descriptor answered by PRESENCE rather than by visibility?
|
|
1412
1537
|
*
|
|
1413
1538
|
* The options of a collapsed `<select>` are in the document and in the
|
|
@@ -1584,7 +1709,7 @@ function mountDom(mount) {
|
|
|
1584
1709
|
};
|
|
1585
1710
|
}
|
|
1586
1711
|
//#endregion
|
|
1587
|
-
//#region src/
|
|
1712
|
+
//#region src/seams/vitest-browser/golden.ts
|
|
1588
1713
|
/** Update mode crosses the seam as a provided value, never as an env read. */
|
|
1589
1714
|
function updating() {
|
|
1590
1715
|
return inject("update") === true;
|
|
@@ -1679,7 +1804,7 @@ const nodeOnlyClass = (name) => function NodeOnly() {
|
|
|
1679
1804
|
return refuse(name);
|
|
1680
1805
|
};
|
|
1681
1806
|
//#endregion
|
|
1682
|
-
//#region src/
|
|
1807
|
+
//#region src/seams/vitest-browser/page-runtime.ts
|
|
1683
1808
|
/**
|
|
1684
1809
|
* The runner, as the page reaches it.
|
|
1685
1810
|
*
|
|
@@ -1728,7 +1853,7 @@ function projectWrapper() {
|
|
|
1728
1853
|
return typeof wrap === "function" ? wrap : void 0;
|
|
1729
1854
|
}
|
|
1730
1855
|
//#endregion
|
|
1731
|
-
//#region src/
|
|
1856
|
+
//#region src/facets/component/rendered-text.ts
|
|
1732
1857
|
/**
|
|
1733
1858
|
* A stream captured INSIDE the page.
|
|
1734
1859
|
*
|
|
@@ -1743,7 +1868,7 @@ var RenderedText = class extends TextAccessor {
|
|
|
1743
1868
|
capturedInPage = true;
|
|
1744
1869
|
};
|
|
1745
1870
|
//#endregion
|
|
1746
|
-
//#region src/
|
|
1871
|
+
//#region src/facets/component/component.result.ts
|
|
1747
1872
|
/**
|
|
1748
1873
|
* What a render resolves to — the component as the page saw it, after the
|
|
1749
1874
|
* scenario ran.
|
|
@@ -1773,7 +1898,7 @@ var RenderResult = class {
|
|
|
1773
1898
|
}
|
|
1774
1899
|
};
|
|
1775
1900
|
//#endregion
|
|
1776
|
-
//#region src/
|
|
1901
|
+
//#region src/facets/component/component.chain.ts
|
|
1777
1902
|
/** The console methods a render records; everything else is left alone. */
|
|
1778
1903
|
const CONSOLE_METHODS = [
|
|
1779
1904
|
"debug",
|
|
@@ -1936,91 +2061,11 @@ function chainOf(state) {
|
|
|
1936
2061
|
/** The handle a component test imports. */
|
|
1937
2062
|
const component = chainOf(EMPTY);
|
|
1938
2063
|
//#endregion
|
|
1939
|
-
//#region src/
|
|
1940
|
-
/**
|
|
1941
|
-
* Read-only accessor for a JSON payload parsed from a text stream (stdout).
|
|
1942
|
-
*
|
|
1943
|
-
* Lazily parses the JSON on first use; parse errors are deferred until the
|
|
1944
|
-
* value is read so that callers can still read the raw stream text without
|
|
1945
|
-
* triggering a throw.
|
|
1946
|
-
*
|
|
1947
|
-
* ANSI escape sequences are stripped by default before `JSON.parse`
|
|
1948
|
-
* (CONVENTIONS D6). When a `transform` is configured on the spec runner, it
|
|
1949
|
-
* runs on the stripped text — an escape hatch for application noise not
|
|
1950
|
-
* covered by the `{{token}}` grammar.
|
|
1951
|
-
*
|
|
1952
|
-
* Assertions go through `expect()`: `expect(result.json).toMatch('name.json')`
|
|
1953
|
-
* (deep-equal against `_expected/<name>`, with `{{placeholder}}` support).
|
|
1954
|
-
*/
|
|
1955
|
-
var JsonAccessor = class {
|
|
1956
|
-
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
1957
|
-
captures;
|
|
1958
|
-
/** @internal */
|
|
1959
|
-
rawText;
|
|
1960
|
-
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
1961
|
-
testDir;
|
|
1962
|
-
/** @internal */
|
|
1963
|
-
transform;
|
|
1964
|
-
constructor(rawText, testDir, transform, captures) {
|
|
1965
|
-
this.rawText = rawText;
|
|
1966
|
-
this.testDir = testDir;
|
|
1967
|
-
this.transform = transform;
|
|
1968
|
-
this.captures = captures ?? new CaptureScope();
|
|
1969
|
-
}
|
|
1970
|
-
/**
|
|
1971
|
-
* The parsed JSON value. Throws if the text is not valid JSON.
|
|
1972
|
-
*
|
|
1973
|
-
* `Value` is `unknown` unless the subject that produced this accessor
|
|
1974
|
-
* knows better — `.call<T>()` does, and hands its T straight through so a
|
|
1975
|
-
* one-field reading needs no cast in the test.
|
|
1976
|
-
*/
|
|
1977
|
-
get value() {
|
|
1978
|
-
const stripped = stripAnsiCodes(this.rawText);
|
|
1979
|
-
const source = this.transform ? this.transform(stripped) : stripped;
|
|
1980
|
-
try {
|
|
1981
|
-
return JSON.parse(source);
|
|
1982
|
-
} catch {
|
|
1983
|
-
const preview = source.slice(0, 200);
|
|
1984
|
-
throw new Error(`stdout is not valid JSON: ${preview}`);
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
};
|
|
1988
|
-
//#endregion
|
|
1989
|
-
//#region src/specification/facets/_common/result/table.ts
|
|
1990
|
-
/**
|
|
1991
|
-
* Read-only accessor for a database table after a specification run.
|
|
1992
|
-
*
|
|
1993
|
-
* Assertions go through `expect()` (async — they query the database):
|
|
1994
|
-
*
|
|
1995
|
-
* await expect(result.table('users', { database: 'db' })).toMatchRows({
|
|
1996
|
-
* columns: ['name'],
|
|
1997
|
-
* rows: [['Alice']],
|
|
1998
|
-
* });
|
|
1999
|
-
* await expect(result.table('users', { database: 'db' })).toBeEmpty();
|
|
2000
|
-
*/
|
|
2001
|
-
var TableAccessor = class {
|
|
2002
|
-
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
2003
|
-
captures;
|
|
2004
|
-
/** @internal */
|
|
2005
|
-
db;
|
|
2006
|
-
/** The table name this accessor reads. */
|
|
2007
|
-
name;
|
|
2008
|
-
constructor(name, db, captures) {
|
|
2009
|
-
this.name = name;
|
|
2010
|
-
this.db = db;
|
|
2011
|
-
this.captures = captures ?? new CaptureScope();
|
|
2012
|
-
}
|
|
2013
|
-
/** @internal Query the table — used by the toMatchRows / toBeEmpty matchers. */
|
|
2014
|
-
async query(columns) {
|
|
2015
|
-
return await this.db.query(this.name, columns);
|
|
2016
|
-
}
|
|
2017
|
-
};
|
|
2018
|
-
//#endregion
|
|
2019
|
-
//#region src/specification/facets/website/elements.ts
|
|
2064
|
+
//#region src/model/elements/elements.ts
|
|
2020
2065
|
const named = (kind) => (name, options) => ({
|
|
2021
2066
|
kind,
|
|
2022
2067
|
name,
|
|
2023
|
-
...options?.exact ? {
|
|
2068
|
+
...options?.exact === void 0 ? {} : { exact: options.exact }
|
|
2024
2069
|
});
|
|
2025
2070
|
/** A button (or element with the button role), by accessible name. */
|
|
2026
2071
|
const button = named("button");
|
|
@@ -2040,7 +2085,7 @@ const testId = (id) => ({
|
|
|
2040
2085
|
const landmark = (kind) => (name, options) => ({
|
|
2041
2086
|
kind,
|
|
2042
2087
|
...name === void 0 ? {} : { name },
|
|
2043
|
-
...options?.exact ? {
|
|
2088
|
+
...options?.exact === void 0 ? {} : { exact: options.exact }
|
|
2044
2089
|
});
|
|
2045
2090
|
/** The `banner` landmark — the page header. */
|
|
2046
2091
|
const banner = landmark("banner");
|
|
@@ -2158,7 +2203,7 @@ const valued = (element, value) => ({
|
|
|
2158
2203
|
value
|
|
2159
2204
|
});
|
|
2160
2205
|
//#endregion
|
|
2161
|
-
//#region src/
|
|
2206
|
+
//#region src/model/contracts/filters.ts
|
|
2162
2207
|
/** Does an observed text satisfy a declared {@link TextFilter}? */
|
|
2163
2208
|
function matchesText(filter, actual) {
|
|
2164
2209
|
if (filter === void 0) return true;
|
|
@@ -2167,7 +2212,7 @@ function matchesText(filter, actual) {
|
|
|
2167
2212
|
return structuralEquals(filter, actual, new CaptureScope());
|
|
2168
2213
|
}
|
|
2169
2214
|
//#endregion
|
|
2170
|
-
//#region src/
|
|
2215
|
+
//#region src/seams/anthropic/anthropic.ts
|
|
2171
2216
|
const ANTHROPIC_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
|
|
2172
2217
|
function matchesFilter(body, filter) {
|
|
2173
2218
|
if (!matchesText(filter.model, body?.model ?? "")) return false;
|
|
@@ -2258,7 +2303,7 @@ const anthropic = {
|
|
|
2258
2303
|
}
|
|
2259
2304
|
};
|
|
2260
2305
|
//#endregion
|
|
2261
|
-
//#region src/
|
|
2306
|
+
//#region src/model/contracts/http.ts
|
|
2262
2307
|
function wrapJson(data) {
|
|
2263
2308
|
return {
|
|
2264
2309
|
status: 200,
|
|
@@ -2447,7 +2492,7 @@ const http = {
|
|
|
2447
2492
|
}
|
|
2448
2493
|
};
|
|
2449
2494
|
//#endregion
|
|
2450
|
-
//#region src/
|
|
2495
|
+
//#region src/seams/openai/openai.ts
|
|
2451
2496
|
const OPENAI_CHAT_URL = "https://api.openai.com/v1/chat/completions";
|
|
2452
2497
|
const OPENAI_RESPONSES_URL = "https://api.openai.com/v1/responses";
|
|
2453
2498
|
function matchesChatFilter(body, filter) {
|
|
@@ -2604,19 +2649,12 @@ const openai = {
|
|
|
2604
2649
|
}
|
|
2605
2650
|
};
|
|
2606
2651
|
//#endregion
|
|
2607
|
-
//#region src/
|
|
2652
|
+
//#region src/model/doubles/mock-of.ts
|
|
2608
2653
|
function mockOf(options) {
|
|
2609
2654
|
return options?.deep === false ? mock() : mockDeep();
|
|
2610
2655
|
}
|
|
2611
2656
|
//#endregion
|
|
2612
|
-
//#region src/
|
|
2613
|
-
/**
|
|
2614
|
-
* Freeze or reset the global Date for deterministic time-dependent tests.
|
|
2615
|
-
* Wraps the `mockdate` package.
|
|
2616
|
-
*/
|
|
2617
|
-
const mockOfDate = MockDatePackage;
|
|
2618
|
-
//#endregion
|
|
2619
|
-
//#region src/vitest/clock.ts
|
|
2657
|
+
//#region src/model/clock/clock.ts
|
|
2620
2658
|
/** The instant `iso` names, or a refusal that says what an instant looks like. */
|
|
2621
2659
|
function instantOf(method, iso) {
|
|
2622
2660
|
const at = new Date(iso);
|
|
@@ -2683,7 +2721,7 @@ const clock = {
|
|
|
2683
2721
|
}
|
|
2684
2722
|
};
|
|
2685
2723
|
//#endregion
|
|
2686
|
-
//#region src/
|
|
2724
|
+
//#region src/model/assertions/required.ts
|
|
2687
2725
|
/**
|
|
2688
2726
|
* The value, or a failure that says what was missing and why it mattered.
|
|
2689
2727
|
*
|
|
@@ -2703,7 +2741,7 @@ function required(value, why) {
|
|
|
2703
2741
|
return value;
|
|
2704
2742
|
}
|
|
2705
2743
|
//#endregion
|
|
2706
|
-
//#region src/
|
|
2744
|
+
//#region src/model/assertions/wait-until.ts
|
|
2707
2745
|
const DEFAULT_TIMEOUT_MS = 5e3;
|
|
2708
2746
|
const DEFAULT_INTERVAL_MS = 50;
|
|
2709
2747
|
/**
|
|
@@ -2768,21 +2806,6 @@ const ProcessHandle = nodeOnlyClass("ProcessHandle");
|
|
|
2768
2806
|
const postgres = () => refuse("postgres()");
|
|
2769
2807
|
const redis = () => refuse("redis()");
|
|
2770
2808
|
const sqlite = () => refuse("sqlite()");
|
|
2771
|
-
const ContainerAccessor = nodeOnlyClass("ContainerAccessor");
|
|
2772
|
-
const findContainersByLabel = () => refuse("findContainersByLabel()");
|
|
2773
|
-
const inspectContainer = () => refuse("inspectContainer()");
|
|
2774
|
-
const removeContainers = () => refuse("removeContainers()");
|
|
2775
|
-
const Orchestrator = nodeOnlyClass("Orchestrator");
|
|
2776
|
-
const BaseResult = nodeOnlyClass("BaseResult");
|
|
2777
|
-
const CallResult = nodeOnlyClass("CallResult");
|
|
2778
|
-
const CliResult = nodeOnlyClass("CliResult");
|
|
2779
|
-
const DirectoryAccessor = nodeOnlyClass("DirectoryAccessor");
|
|
2780
|
-
const FetchResult = nodeOnlyClass("FetchResult");
|
|
2781
|
-
const FilesystemAccessor = nodeOnlyClass("FilesystemAccessor");
|
|
2782
|
-
const HttpResult = nodeOnlyClass("HttpResult");
|
|
2783
|
-
const PageResult = nodeOnlyClass("PageResult");
|
|
2784
|
-
const ResponseAccessor = nodeOnlyClass("ResponseAccessor");
|
|
2785
|
-
const ScreenResult = nodeOnlyClass("ScreenResult");
|
|
2786
2809
|
/**
|
|
2787
2810
|
* `text()` is real here: only its anchor differs. Under node the accessor
|
|
2788
2811
|
* remembers the caller's directory so a golden resolves against it; in a page
|
|
@@ -2793,4 +2816,4 @@ function text(value) {
|
|
|
2793
2816
|
return new TextAccessor(value, "text", "");
|
|
2794
2817
|
}
|
|
2795
2818
|
//#endregion
|
|
2796
|
-
export {
|
|
2819
|
+
export { Matcher, ProcessHandle, anthropic, banner, button, clock, complementary, component, content, contentinfo, defineContract, defineContracts, dialog, disabled, enabled, field, focused, form, heading, http, intercept, link, listitem, main, match, mockOf, navigation, openai, option, postgres, process, redis, region, required, row, search, selected, specification, sqlite, status, table, testId, text, valued, waitUntil, within };
|