@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/ambiguity.js
CHANGED
|
@@ -19,14 +19,20 @@ const CONSTRUCTORS = {
|
|
|
19
19
|
button: "button",
|
|
20
20
|
complementary: "complementary",
|
|
21
21
|
contentinfo: "contentinfo",
|
|
22
|
+
dialog: "dialog",
|
|
22
23
|
field: "field",
|
|
23
24
|
form: "form",
|
|
24
25
|
heading: "heading",
|
|
25
26
|
link: "link",
|
|
27
|
+
listitem: "listitem",
|
|
26
28
|
main: "main",
|
|
27
29
|
navigation: "navigation",
|
|
30
|
+
option: "option",
|
|
28
31
|
region: "region",
|
|
32
|
+
row: "row",
|
|
29
33
|
search: "search",
|
|
34
|
+
status: "status",
|
|
35
|
+
table: "table",
|
|
30
36
|
testId: "testId",
|
|
31
37
|
text: "content"
|
|
32
38
|
};
|
|
@@ -62,12 +68,22 @@ function formatBare(element) {
|
|
|
62
68
|
/** `1. <a href="/articles">Articles</a> in <nav>` — one evidence line per candidate. */
|
|
63
69
|
function formatMatch(match, index) {
|
|
64
70
|
const attribute = match.detail ? ` ${quoteDetail(match)}` : "";
|
|
71
|
+
const named = match.accessibleName ? ` named ${JSON.stringify(match.accessibleName)}` : "";
|
|
65
72
|
const context = match.context ? ` in <${match.context}>` : "";
|
|
66
|
-
return ` ${index + 1}. <${match.tag}${attribute}>${match.text}</${match.tag}>${context}`;
|
|
73
|
+
return ` ${index + 1}. <${match.tag}${attribute}>${match.text}</${match.tag}>${named}${context}`;
|
|
67
74
|
}
|
|
68
75
|
function quoteDetail(match) {
|
|
69
76
|
return `${match.tag === "a" ? "href" : "name"}=${JSON.stringify(match.detail)}`;
|
|
70
77
|
}
|
|
78
|
+
/** Each landmark the candidates sit in, and how many of them it holds. */
|
|
79
|
+
function landmarkCounts(matches) {
|
|
80
|
+
const counts = /* @__PURE__ */ new Map();
|
|
81
|
+
for (const match of matches) {
|
|
82
|
+
const landmark = match.context === void 0 ? void 0 : CONTEXT_LANDMARKS[match.context];
|
|
83
|
+
if (landmark !== void 0) counts.set(landmark, (counts.get(landmark) ?? 0) + 1);
|
|
84
|
+
}
|
|
85
|
+
return [...counts].toSorted(([, a], [, b]) => a - b);
|
|
86
|
+
}
|
|
71
87
|
/**
|
|
72
88
|
* The rewrites worth offering, in the order they should be tried. Scoping is
|
|
73
89
|
* always available; `exact` only when it would actually narrow the set, and
|
|
@@ -76,9 +92,12 @@ function quoteDetail(match) {
|
|
|
76
92
|
*/
|
|
77
93
|
function formatFixes(element, matches) {
|
|
78
94
|
const fixes = [];
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
81
|
-
|
|
95
|
+
const narrowing = landmarkCounts(matches).filter(([, count]) => count < matches.length);
|
|
96
|
+
if (!element.scope) {
|
|
97
|
+
const [first, ...rest] = narrowing;
|
|
98
|
+
const alsoHere = rest.length > 0 ? ` [also here: ${rest.map(([name]) => name).join(", ")}]` : "";
|
|
99
|
+
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}`);
|
|
100
|
+
}
|
|
82
101
|
if (!element.exact && element.name !== void 0) {
|
|
83
102
|
const remaining = matches.filter((match) => match.text === element.name).length;
|
|
84
103
|
if (remaining > 0 && remaining < matches.length) fixes.push(`exact name ${formatBare({
|
package/dist/artifacts.js
CHANGED
|
@@ -1,82 +1,10 @@
|
|
|
1
1
|
import { i as SpecSyntaxError, l as updateSpecFile, s as readSpecFile } from "./spec-document.js";
|
|
2
2
|
import { a as copyPlan, n as Matcher, r as TOKEN_KINDS, t as CaptureScope } from "./match.js";
|
|
3
3
|
import { cpSync, existsSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, isAbsolute,
|
|
4
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { execSync, spawn } from "node:child_process";
|
|
6
6
|
import { readdir } from "node:fs/promises";
|
|
7
7
|
import { createServer } from "node:net";
|
|
8
|
-
//#region src/specification/facets/_common/caller.ts
|
|
9
|
-
/** Sibling module tests are callers, not internals (CONVENTIONS I2). */
|
|
10
|
-
const TEST_FILE = /\.test\.[cm]?[jt]s$/u;
|
|
11
|
-
/** Where this very module sits inside the framework's SOURCE tree. */
|
|
12
|
-
const SOURCE_LOCATION = join("specification", "facets", "_common");
|
|
13
|
-
/** The real path of `path`, or `path` itself when it cannot be resolved. */
|
|
14
|
-
function realPath(path) {
|
|
15
|
-
try {
|
|
16
|
-
return realpathSync(path);
|
|
17
|
-
} catch {
|
|
18
|
-
return path;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Directory of the running framework module itself. From the built package
|
|
23
|
-
* this is `<package root>/dist`, whatever the resolution path — including a
|
|
24
|
-
* `file:` link, where the real path carries neither `node_modules` nor
|
|
25
|
-
* `/src/…` and would otherwise be mistaken for a caller frame.
|
|
26
|
-
*/
|
|
27
|
-
const FRAMEWORK_DIR = realPath(import.meta.dirname);
|
|
28
|
-
/**
|
|
29
|
-
* The directory holding the framework's OWN modules: `<package>/dist` for a
|
|
30
|
-
* built install (everything is bundled there), `<repo>/src` when the framework
|
|
31
|
-
* runs from source — this module's known location inside that tree is what
|
|
32
|
-
* tells the two apart.
|
|
33
|
-
*/
|
|
34
|
-
const FRAMEWORK_TREE = FRAMEWORK_DIR.endsWith(`${sep}${SOURCE_LOCATION}`) ? resolve(FRAMEWORK_DIR, "../../..") : FRAMEWORK_DIR;
|
|
35
|
-
/** Is `path` inside `directory` (strictly — a directory is not inside itself)? */
|
|
36
|
-
function isInside(directory, path) {
|
|
37
|
-
const rest = relative(directory, path);
|
|
38
|
-
return rest !== "" && !rest.startsWith("..") && !isAbsolute(rest);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Is this stack frame a module of the framework itself, rather than of the code
|
|
42
|
-
* calling it?
|
|
43
|
-
*
|
|
44
|
-
* Answered by IDENTITY: the frame is inside the framework's own directory.
|
|
45
|
-
* Answering it by substring — `/src/core/`, `/src/integrations/`, `/src/vitest/`
|
|
46
|
-
* — was answering a different question, "does this path look like the framework's
|
|
47
|
-
* layout", and a consumer that happens to have its own `src/core/` matched it.
|
|
48
|
-
* Its files were then skipped as framework internals, and fixture resolution
|
|
49
|
-
* anchored on the wrong directory (or fell through to the cwd) with nothing
|
|
50
|
-
* reported.
|
|
51
|
-
*
|
|
52
|
-
* @internal
|
|
53
|
-
*/
|
|
54
|
-
function isFrameworkFrame(filePath, frameworkTree = FRAMEWORK_TREE) {
|
|
55
|
-
if (TEST_FILE.test(filePath)) return false;
|
|
56
|
-
return isInside(realPath(frameworkTree), realPath(filePath));
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Detect the directory of the first stack frame that lives outside this
|
|
60
|
-
* package. Used to anchor fixture resolution (`_seeds/`, `_requests/`,
|
|
61
|
-
* `_expected/`, …) on the calling test / specification file.
|
|
62
|
-
*
|
|
63
|
-
* @internal
|
|
64
|
-
*/
|
|
65
|
-
function getCallerDir() {
|
|
66
|
-
const { stack } = /* @__PURE__ */ new Error("caller detection");
|
|
67
|
-
if (!stack) throw new Error("Cannot detect caller directory: no stack trace");
|
|
68
|
-
const lines = stack.split("\n");
|
|
69
|
-
for (const line of lines) {
|
|
70
|
-
const match = /at\s+(?:.*?\()?(?:file:\/\/)?(?<filePath>[^:)]+):\d+:\d+/u.exec(line);
|
|
71
|
-
if (!match?.groups?.filePath) continue;
|
|
72
|
-
const { filePath } = match.groups;
|
|
73
|
-
if (filePath.includes("node_modules")) continue;
|
|
74
|
-
if (isFrameworkFrame(filePath)) continue;
|
|
75
|
-
return resolve(filePath, "..");
|
|
76
|
-
}
|
|
77
|
-
return process.cwd();
|
|
78
|
-
}
|
|
79
|
-
//#endregion
|
|
80
8
|
//#region src/specification/facets/_common/result/grep.ts
|
|
81
9
|
/**
|
|
82
10
|
* Extract text blocks from output that contain a pattern.
|
|
@@ -163,26 +91,6 @@ var TextAccessor = class TextAccessor {
|
|
|
163
91
|
return this.text;
|
|
164
92
|
}
|
|
165
93
|
};
|
|
166
|
-
/**
|
|
167
|
-
* Wrap an arbitrary string into a {@link TextAccessor} anchored on the calling
|
|
168
|
-
* test's directory — the same caller-detection the builders use.
|
|
169
|
-
*
|
|
170
|
-
* The product surface of a test framework is its own error messages, checker
|
|
171
|
-
* output, and reports; those deserve the same goldening as any other output.
|
|
172
|
-
* `text()` makes an ad-hoc string a first-class snapshot subject:
|
|
173
|
-
*
|
|
174
|
-
* ```typescript
|
|
175
|
-
* const message = await catchMessage(() => expect(result.response).toMatch('wrong-body.http'));
|
|
176
|
-
* expect(text(message)).toMatch('wrong-body-error.txt'); // resolves to _expected/
|
|
177
|
-
* ```
|
|
178
|
-
*
|
|
179
|
-
* ANSI is stripped before every comparison (the raw form stays on `.text`),
|
|
180
|
-
* the `{{token}}` grammar applies to the fixture, and `.grep()` composition
|
|
181
|
-
* works exactly as on any stream accessor.
|
|
182
|
-
*/
|
|
183
|
-
function text(value) {
|
|
184
|
-
return new TextAccessor(value, "text", getCallerDir());
|
|
185
|
-
}
|
|
186
94
|
//#endregion
|
|
187
95
|
//#region src/specification/facets/_common/result/json.ts
|
|
188
96
|
/**
|
|
@@ -228,45 +136,35 @@ var JsonAccessor = class {
|
|
|
228
136
|
}
|
|
229
137
|
};
|
|
230
138
|
//#endregion
|
|
231
|
-
//#region src/
|
|
139
|
+
//#region src/specification/facets/_common/result/table.ts
|
|
232
140
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
141
|
+
* Read-only accessor for a database table after a specification run.
|
|
142
|
+
*
|
|
143
|
+
* Assertions go through `expect()` (async — they query the database):
|
|
144
|
+
*
|
|
145
|
+
* await expect(result.table('users', { database: 'db' })).toMatchRows({
|
|
146
|
+
* columns: ['name'],
|
|
147
|
+
* rows: [['Alice']],
|
|
148
|
+
* });
|
|
149
|
+
* await expect(result.table('users', { database: 'db' })).toBeEmpty();
|
|
237
150
|
*/
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
151
|
+
var TableAccessor = class {
|
|
152
|
+
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
153
|
+
captures;
|
|
154
|
+
/** @internal */
|
|
155
|
+
db;
|
|
156
|
+
/** The table name this accessor reads. */
|
|
157
|
+
name;
|
|
158
|
+
constructor(name, db, captures) {
|
|
159
|
+
this.name = name;
|
|
160
|
+
this.db = db;
|
|
161
|
+
this.captures = captures ?? new CaptureScope();
|
|
248
162
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
timeout: DEFAULT_TIMEOUT
|
|
255
|
-
});
|
|
256
|
-
const parsed = JSON.parse(raw);
|
|
257
|
-
return Array.isArray(parsed) ? parsed[0] : parsed;
|
|
258
|
-
}
|
|
259
|
-
/** Force-remove the given container IDs in a single call. Errors are swallowed. */
|
|
260
|
-
function removeContainers(ids) {
|
|
261
|
-
if (ids.length === 0) return;
|
|
262
|
-
try {
|
|
263
|
-
execSync(`docker rm -f ${ids.join(" ")}`, {
|
|
264
|
-
encoding: "utf8",
|
|
265
|
-
stdio: "pipe",
|
|
266
|
-
timeout: DEFAULT_TIMEOUT
|
|
267
|
-
});
|
|
268
|
-
} catch {}
|
|
269
|
-
}
|
|
163
|
+
/** @internal Query the table — used by the toMatchRows / toBeEmpty matchers. */
|
|
164
|
+
async query(columns) {
|
|
165
|
+
return await this.db.query(this.name, columns);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
270
168
|
//#endregion
|
|
271
169
|
//#region src/specification/matching/structural.ts
|
|
272
170
|
/**
|
|
@@ -605,6 +503,46 @@ function mergeTextPreservingPlaceholders(previous, actual, scope) {
|
|
|
605
503
|
return merged.join("\n");
|
|
606
504
|
}
|
|
607
505
|
//#endregion
|
|
506
|
+
//#region src/integrations/docker/docker-lookup.ts
|
|
507
|
+
/**
|
|
508
|
+
* Thin synchronous wrappers around the `docker` CLI used by the docker() spec
|
|
509
|
+
* mode. Sync is deliberate — the calls are fast, run only on the host, and
|
|
510
|
+
* keep the dispose path straightforward (`Symbol.asyncDispose` can still be
|
|
511
|
+
* async without needing these to be).
|
|
512
|
+
*/
|
|
513
|
+
const DEFAULT_TIMEOUT = 1e4;
|
|
514
|
+
/** Return all container IDs (running or stopped) that carry `key=value`. */
|
|
515
|
+
function findContainersByLabel(key, value) {
|
|
516
|
+
try {
|
|
517
|
+
return execSync(`docker ps -aq -f label=${key}=${value}`, {
|
|
518
|
+
encoding: "utf8",
|
|
519
|
+
timeout: DEFAULT_TIMEOUT
|
|
520
|
+
}).split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
521
|
+
} catch {
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/** Return the raw `docker inspect` payload (object, not array) for a container. */
|
|
526
|
+
function inspectContainer(id) {
|
|
527
|
+
const raw = execSync(`docker inspect ${id}`, {
|
|
528
|
+
encoding: "utf8",
|
|
529
|
+
timeout: DEFAULT_TIMEOUT
|
|
530
|
+
});
|
|
531
|
+
const parsed = JSON.parse(raw);
|
|
532
|
+
return Array.isArray(parsed) ? parsed[0] : parsed;
|
|
533
|
+
}
|
|
534
|
+
/** Force-remove the given container IDs in a single call. Errors are swallowed. */
|
|
535
|
+
function removeContainers(ids) {
|
|
536
|
+
if (ids.length === 0) return;
|
|
537
|
+
try {
|
|
538
|
+
execSync(`docker rm -f ${ids.join(" ")}`, {
|
|
539
|
+
encoding: "utf8",
|
|
540
|
+
stdio: "pipe",
|
|
541
|
+
timeout: DEFAULT_TIMEOUT
|
|
542
|
+
});
|
|
543
|
+
} catch {}
|
|
544
|
+
}
|
|
545
|
+
//#endregion
|
|
608
546
|
//#region src/specification/facets/_common/result/directory.ts
|
|
609
547
|
const DEFAULT_IGNORES = [
|
|
610
548
|
".git",
|
|
@@ -811,36 +749,6 @@ function expandWorkdir(env, workDir) {
|
|
|
811
749
|
return expanded;
|
|
812
750
|
}
|
|
813
751
|
//#endregion
|
|
814
|
-
//#region src/specification/facets/_common/result/table.ts
|
|
815
|
-
/**
|
|
816
|
-
* Read-only accessor for a database table after a specification run.
|
|
817
|
-
*
|
|
818
|
-
* Assertions go through `expect()` (async — they query the database):
|
|
819
|
-
*
|
|
820
|
-
* await expect(result.table('users', { database: 'db' })).toMatchRows({
|
|
821
|
-
* columns: ['name'],
|
|
822
|
-
* rows: [['Alice']],
|
|
823
|
-
* });
|
|
824
|
-
* await expect(result.table('users', { database: 'db' })).toBeEmpty();
|
|
825
|
-
*/
|
|
826
|
-
var TableAccessor = class {
|
|
827
|
-
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
828
|
-
captures;
|
|
829
|
-
/** @internal */
|
|
830
|
-
db;
|
|
831
|
-
/** The table name this accessor reads. */
|
|
832
|
-
name;
|
|
833
|
-
constructor(name, db, captures) {
|
|
834
|
-
this.name = name;
|
|
835
|
-
this.db = db;
|
|
836
|
-
this.captures = captures ?? new CaptureScope();
|
|
837
|
-
}
|
|
838
|
-
/** @internal Query the table — used by the toMatchRows / toBeEmpty matchers. */
|
|
839
|
-
async query(columns) {
|
|
840
|
-
return await this.db.query(this.name, columns);
|
|
841
|
-
}
|
|
842
|
-
};
|
|
843
|
-
//#endregion
|
|
844
752
|
//#region src/specification/facets/_common/result/result.ts
|
|
845
753
|
/**
|
|
846
754
|
* Base result - common accessors available after any action type.
|
|
@@ -1794,4 +1702,4 @@ const COVERAGE_DIR = `${VITEST_ARTIFACTS_DIR}/coverage`;
|
|
|
1794
1702
|
/** Where `sqlite()` caches the schema template it copies per worker. */
|
|
1795
1703
|
const SQLITE_TEMPLATE_DIR = `${VITEST_ARTIFACTS_DIR}/sqlite`;
|
|
1796
1704
|
//#endregion
|
|
1797
|
-
export {
|
|
1705
|
+
export { mergePreservingPlaceholders as A, DirectoryAccessor as C, inspectContainer as D, findContainersByLabel as E, structuralSubset as F, textEquals as I, TableAccessor as L, placeholderPatternSource as M, renderExpected as N, removeContainers as O, structuralEquals as P, JsonAccessor as R, FilesystemAccessor as S, walkDirectory as T, validateDatabaseOption as _, specDescription as a, resolveCommand as b, shouldUpdateSnapshots as c, formatStartupReport as d, formatStdoutDiff as f, BaseResult as g, CliResult as h, runSpecDocument as i, mergeTextPreservingPlaceholders as j, hasPlaceholders as k, formatDirectoryDiff as l, ContainerAccessor as m, SQLITE_TEMPLATE_DIR as n, ServeAdapter as o, formatTableDiff as p, VITEST_ARTIFACTS_DIR as r, UPDATE_HINT as s, COVERAGE_DIR as t, formatResponseDiff as u, discoverRoot as v, diffDirectories as w, resolveRoot as x, expandWorkdir as y, TextAccessor as z };
|