@jterrazz/test 15.3.0 → 16.1.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/appium.adapter.js +71 -15
- package/dist/artifacts.js +25 -1735
- package/dist/browser/index.js +304 -233
- package/dist/checker.d.ts +1 -1
- package/dist/checker.js +1103 -97
- package/dist/fixtures.js +110 -0
- package/dist/index.d.ts +956 -971
- package/dist/index.js +2159 -2281
- package/dist/literate.js +1747 -0
- package/dist/match.js +625 -84
- package/dist/oxlint.cjs +3585 -1480
- package/dist/oxlint.d.cts +60 -48
- package/dist/oxlint.d.ts +60 -48
- package/dist/oxlint.js +3585 -1480
- package/dist/playwright.adapter.js +51 -8
- package/dist/{intercept.js → server.js} +2 -2
- package/dist/stream.js +6 -6
- package/dist/{ambiguity.js → substring-warning.js} +106 -7
- package/dist/vitest.d.ts +67 -48
- package/dist/vitest.js +229 -93
- package/docs/01-architecture.md +95 -0
- package/docs/02-developing.md +351 -0
- package/docs/03-testing.md +495 -0
- package/docs/04-operating.md +101 -0
- package/docs/05-module-tests.md +152 -0
- package/docs/06-integration.md +180 -0
- package/docs/07-component.md +246 -0
- package/docs/08-website.md +404 -0
- package/docs/09-mobile.md +270 -0
- package/docs/10-api.md +264 -0
- package/docs/11-jobs.md +157 -0
- package/docs/12-cli.md +677 -0
- package/docs/13-elements.md +242 -0
- package/docs/14-assertions.md +314 -0
- package/docs/15-tokens.md +253 -0
- package/docs/16-contracts.md +438 -0
- package/docs/17-services.md +188 -0
- package/docs/18-conventions.md +305 -0
- package/docs/19-linting.md +510 -0
- package/docs/README.md +36 -0
- package/docs/decisions/001-v9-conventions-on-three-channels.md +50 -0
- package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +49 -0
- package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +84 -0
- package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +81 -0
- package/docs/decisions/005-the-doubles-ladder-is-closed.md +86 -0
- package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +245 -0
- package/docs/decisions/007-compose-mode-leaves-the-package.md +53 -0
- package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +75 -0
- package/docs/decisions/_template.md +22 -0
- package/docs/reference/classes/Matcher.md +120 -0
- package/docs/reference/classes/ProcessHandle.md +368 -0
- package/docs/reference/functions/defineContract.md +39 -0
- package/docs/reference/functions/defineContracts.md +36 -0
- package/docs/reference/functions/disabled.md +28 -0
- package/docs/reference/functions/enabled.md +20 -0
- package/docs/reference/functions/focused.md +26 -0
- package/docs/reference/functions/mockOf.md +79 -0
- package/docs/reference/functions/postgres.md +31 -0
- package/docs/reference/functions/process.md +33 -0
- package/docs/reference/functions/redis.md +28 -0
- package/docs/reference/functions/required.md +41 -0
- package/docs/reference/functions/selected.md +26 -0
- package/docs/reference/functions/sqlite.md +33 -0
- package/docs/reference/functions/testId.md +20 -0
- package/docs/reference/functions/text.md +40 -0
- package/docs/reference/functions/valued.md +28 -0
- package/docs/reference/functions/waitUntil.md +46 -0
- package/docs/reference/functions/within.md +29 -0
- package/docs/reference/index.md +196 -0
- package/docs/reference/interfaces/BaseResult.md +87 -0
- package/docs/reference/interfaces/CallResult.md +160 -0
- package/docs/reference/interfaces/CaptureScope.md +82 -0
- package/docs/reference/interfaces/CliResult.md +253 -0
- package/docs/reference/interfaces/ContainerAccessor.md +166 -0
- package/docs/reference/interfaces/DirectoryAccessor.md +33 -0
- package/docs/reference/interfaces/FetchResult.md +180 -0
- package/docs/reference/interfaces/FilesystemAccessor.md +46 -0
- package/docs/reference/interfaces/HttpResult.md +126 -0
- package/docs/reference/interfaces/JsonAccessor.md +46 -0
- package/docs/reference/interfaces/PageResult.md +356 -0
- package/docs/reference/interfaces/RenderResult.md +72 -0
- package/docs/reference/interfaces/RenderedText.md +110 -0
- package/docs/reference/interfaces/ResponseAccessor.md +47 -0
- package/docs/reference/interfaces/ScreenResult.md +128 -0
- package/docs/reference/interfaces/TableAccessor.md +26 -0
- package/docs/reference/interfaces/TextAccessor.md +96 -0
- package/docs/reference/type-aliases/ApiHandle.md +68 -0
- package/docs/reference/type-aliases/ApiSpecification.md +213 -0
- package/docs/reference/type-aliases/ApiSpecificationOptions.md +68 -0
- package/docs/reference/type-aliases/BrowserConsoleMessage.md +30 -0
- package/docs/reference/type-aliases/BrowserLinkElement.md +50 -0
- package/docs/reference/type-aliases/BrowserMetaElement.md +40 -0
- package/docs/reference/type-aliases/BrowserOpenOptions.md +87 -0
- package/docs/reference/type-aliases/BrowserPage.md +132 -0
- package/docs/reference/type-aliases/BrowserPort.md +51 -0
- package/docs/reference/type-aliases/CliEnv.md +11 -0
- package/docs/reference/type-aliases/CliHandle.md +80 -0
- package/docs/reference/type-aliases/CliInput.md +36 -0
- package/docs/reference/type-aliases/CliOutput.md +46 -0
- package/docs/reference/type-aliases/CliPort.md +58 -0
- package/docs/reference/type-aliases/CliSpecification.md +144 -0
- package/docs/reference/type-aliases/CliSpecificationOptions.md +134 -0
- package/docs/reference/type-aliases/ComponentChain.md +156 -0
- package/docs/reference/type-aliases/ComponentScenario.md +20 -0
- package/docs/reference/type-aliases/ComponentUi.md +47 -0
- package/docs/reference/type-aliases/ComponentVisitor.md +10 -0
- package/docs/reference/type-aliases/ContainerPort.md +113 -0
- package/docs/reference/type-aliases/Contract.md +68 -0
- package/docs/reference/type-aliases/ContractInput.md +13 -0
- package/docs/reference/type-aliases/ContractRequest.md +95 -0
- package/docs/reference/type-aliases/ContractResponder.md +23 -0
- package/docs/reference/type-aliases/ContractResponse.md +78 -0
- package/docs/reference/type-aliases/ContractResponseValue.md +13 -0
- package/docs/reference/type-aliases/Contracts.md +48 -0
- package/docs/reference/type-aliases/DatabaseKeys.md +16 -0
- package/docs/reference/type-aliases/DatabasePort.md +72 -0
- package/docs/reference/type-aliases/DeviceOpenOptions.md +46 -0
- package/docs/reference/type-aliases/DevicePort.md +51 -0
- package/docs/reference/type-aliases/DeviceScreen.md +32 -0
- package/docs/reference/type-aliases/DeviceTimeouts.md +39 -0
- package/docs/reference/type-aliases/DockerSpecConfig.md +43 -0
- package/docs/reference/type-aliases/DomMount.md +21 -0
- package/docs/reference/type-aliases/ElementKind.md +27 -0
- package/docs/reference/type-aliases/ElementMatch.md +74 -0
- package/docs/reference/type-aliases/ElementOptions.md +29 -0
- package/docs/reference/type-aliases/ElementRef.md +128 -0
- package/docs/reference/type-aliases/ExecOptions.md +37 -0
- package/docs/reference/type-aliases/FileAccessor.md +54 -0
- package/docs/reference/type-aliases/HonoApp.md +31 -0
- package/docs/reference/type-aliases/HttpContractFilter.md +49 -0
- package/docs/reference/type-aliases/HttpResponseInit.md +46 -0
- package/docs/reference/type-aliases/HttpStreamInit.md +58 -0
- package/docs/reference/type-aliases/IntegrationHandle.md +46 -0
- package/docs/reference/type-aliases/IntegrationSpecification.md +111 -0
- package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +42 -0
- package/docs/reference/type-aliases/Intercept.md +19 -0
- package/docs/reference/type-aliases/InterceptOptions.md +28 -0
- package/docs/reference/type-aliases/InterceptScope.md +11 -0
- package/docs/reference/type-aliases/IsolationStrategy.md +75 -0
- package/docs/reference/type-aliases/JobHandle.md +34 -0
- package/docs/reference/type-aliases/JobsHandle.md +57 -0
- package/docs/reference/type-aliases/JobsResult.md +16 -0
- package/docs/reference/type-aliases/JobsSpecification.md +94 -0
- package/docs/reference/type-aliases/JobsSpecificationOptions.md +56 -0
- package/docs/reference/type-aliases/LandmarkKind.md +20 -0
- package/docs/reference/type-aliases/LiterateRunFlags.md +27 -0
- package/docs/reference/type-aliases/LiterateServeRegistration.md +70 -0
- package/docs/reference/type-aliases/MatchFixtureOptions.md +27 -0
- package/docs/reference/type-aliases/MatchableRequest.md +61 -0
- package/docs/reference/type-aliases/MatcherKind.md +34 -0
- package/docs/reference/type-aliases/MobileBackendOptions.md +28 -0
- package/docs/reference/type-aliases/MobileElementKind.md +14 -0
- package/docs/reference/type-aliases/MobileElementMatch.md +60 -0
- package/docs/reference/type-aliases/MobileElementRef.md +20 -0
- package/docs/reference/type-aliases/MobileHandle.md +65 -0
- package/docs/reference/type-aliases/MobileScenario.md +20 -0
- package/docs/reference/type-aliases/MobileSpecification.md +51 -0
- package/docs/reference/type-aliases/MobileSpecificationOptions.md +126 -0
- package/docs/reference/type-aliases/MobileVisitor.md +81 -0
- package/docs/reference/type-aliases/MockOfOptions.md +26 -0
- package/docs/reference/type-aliases/MockPort.md +10 -0
- package/docs/reference/type-aliases/PinnedClock.md +10 -0
- package/docs/reference/type-aliases/PostgresOptions.md +32 -0
- package/docs/reference/type-aliases/ProcessOptions.md +112 -0
- package/docs/reference/type-aliases/RedisOptions.md +20 -0
- package/docs/reference/type-aliases/RenderSubject.md +12 -0
- package/docs/reference/type-aliases/ScreenNode.md +67 -0
- package/docs/reference/type-aliases/ServerPort.md +36 -0
- package/docs/reference/type-aliases/ServerResponse.md +46 -0
- package/docs/reference/type-aliases/ServerSpec.md +21 -0
- package/docs/reference/type-aliases/ServiceHandle.md +216 -0
- package/docs/reference/type-aliases/ServiceRecord.md +12 -0
- package/docs/reference/type-aliases/SpecDocument.md +84 -0
- package/docs/reference/type-aliases/SpecEnvToken.md +21 -0
- package/docs/reference/type-aliases/SpecFileAssertion.md +27 -0
- package/docs/reference/type-aliases/SpecFixture.md +30 -0
- package/docs/reference/type-aliases/SpecKind.md +8 -0
- package/docs/reference/type-aliases/SpecRun.md +108 -0
- package/docs/reference/type-aliases/SpecServeEntry.md +40 -0
- package/docs/reference/type-aliases/SpecStream.md +32 -0
- package/docs/reference/type-aliases/SpecificationConfig.md +290 -0
- package/docs/reference/type-aliases/SqliteOptions.md +36 -0
- package/docs/reference/type-aliases/SseEvent.md +58 -0
- package/docs/reference/type-aliases/StreamBody.md +61 -0
- package/docs/reference/type-aliases/TextFilter.md +22 -0
- package/docs/reference/type-aliases/VisitScenario.md +20 -0
- package/docs/reference/type-aliases/Visitor.md +215 -0
- package/docs/reference/type-aliases/WaitUntilOptions.md +46 -0
- package/docs/reference/type-aliases/WebsiteBackendOptions.md +37 -0
- package/docs/reference/type-aliases/WebsiteHandle.md +51 -0
- package/docs/reference/type-aliases/WebsiteSpecification.md +118 -0
- package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +40 -0
- package/docs/reference/variables/anthropic.md +19 -0
- package/docs/reference/variables/banner.md +10 -0
- package/docs/reference/variables/button.md +10 -0
- package/docs/reference/variables/clock.md +16 -0
- package/docs/reference/variables/complementary.md +10 -0
- package/docs/reference/variables/component.md +10 -0
- package/docs/reference/variables/content.md +10 -0
- package/docs/reference/variables/contentinfo.md +10 -0
- package/docs/reference/variables/dialog.md +10 -0
- package/docs/reference/variables/field.md +10 -0
- package/docs/reference/variables/form.md +10 -0
- package/docs/reference/variables/heading.md +10 -0
- package/docs/reference/variables/http.md +40 -0
- package/docs/reference/variables/intercept.md +11 -0
- package/docs/reference/variables/link.md +10 -0
- package/docs/reference/variables/listitem.md +10 -0
- package/docs/reference/variables/main.md +10 -0
- package/docs/reference/variables/match.md +8 -0
- package/docs/reference/variables/navigation.md +10 -0
- package/docs/reference/variables/openai.md +21 -0
- package/docs/reference/variables/option.md +17 -0
- package/docs/reference/variables/region.md +10 -0
- package/docs/reference/variables/row.md +10 -0
- package/docs/reference/variables/search.md +10 -0
- package/docs/reference/variables/specification.md +8 -0
- package/docs/reference/variables/status.md +10 -0
- package/docs/reference/variables/table.md +10 -0
- package/package.json +32 -11
- package/dist/spec-document.js +0 -651
|
@@ -1,14 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as formatElement, i as describeAmbiguity, n as widenedForWindow, r as AmbiguousElementError } from "./substring-warning.js";
|
|
2
2
|
import { mkdtempSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { setTimeout } from "node:timers/promises";
|
|
6
|
-
//#region src/
|
|
6
|
+
//#region src/seams/playwright/playwright.adapter.ts
|
|
7
7
|
/** How many candidates the ambiguity error enumerates before truncating. */
|
|
8
8
|
const MAX_REPORTED_MATCHES = 10;
|
|
9
9
|
const VALUE_TIMEOUT_MS = 3e4;
|
|
10
10
|
const VALUE_POLL_MS = 100;
|
|
11
11
|
/**
|
|
12
|
+
* The roles a FIELD can carry — what `field('Name')` designates.
|
|
13
|
+
*
|
|
14
|
+
* A field is named by its ACCESSIBLE name, not by the text of the label
|
|
15
|
+
* element that happens to wrap it: `<label><span>Channel</span><select>…` has
|
|
16
|
+
* a label whose text carries every option of the select, while the control's
|
|
17
|
+
* accessible name is `Channel` — which is what the ARIA tree shows and what a
|
|
18
|
+
* screen reader reads. Resolving by label text alone made the ordinary
|
|
19
|
+
* wrapping-label form unreachable under the 16.0 whole-name default.
|
|
20
|
+
*
|
|
21
|
+
* The label-text locator stays in the union for the controls ARIA gives no
|
|
22
|
+
* role at all (a date or colour input), which nothing else would find.
|
|
23
|
+
*/
|
|
24
|
+
const FIELD_ROLES = [
|
|
25
|
+
"checkbox",
|
|
26
|
+
"combobox",
|
|
27
|
+
"radio",
|
|
28
|
+
"searchbox",
|
|
29
|
+
"slider",
|
|
30
|
+
"spinbutton",
|
|
31
|
+
"switch",
|
|
32
|
+
"textbox"
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
12
35
|
* Translate a user-facing descriptor into a playwright locator, resolving
|
|
13
36
|
* `scope` outside-in so `within(navigation(), link('X'))` searches the nav
|
|
14
37
|
* subtree. No `.first()` anywhere: playwright's strict mode is the mechanism
|
|
@@ -17,9 +40,16 @@ const VALUE_POLL_MS = 100;
|
|
|
17
40
|
*/
|
|
18
41
|
function locate(root, element) {
|
|
19
42
|
const scope = element.scope ? locate(root, element.scope) : root;
|
|
20
|
-
const exact = element.exact ??
|
|
43
|
+
const exact = element.exact ?? true;
|
|
21
44
|
const name = element.name ?? "";
|
|
22
|
-
if (element.kind === "field")
|
|
45
|
+
if (element.kind === "field") {
|
|
46
|
+
let field = scope.getByLabel(name, { exact });
|
|
47
|
+
for (const role of FIELD_ROLES) field = field.or(scope.getByRole(role, {
|
|
48
|
+
exact,
|
|
49
|
+
name
|
|
50
|
+
}));
|
|
51
|
+
return field;
|
|
52
|
+
}
|
|
23
53
|
if (element.kind === "testId") return scope.getByTestId(name);
|
|
24
54
|
if (element.kind === "text") return scope.getByText(name, { exact });
|
|
25
55
|
return scope.getByRole(element.kind, element.name === void 0 ? { exact } : {
|
|
@@ -59,18 +89,30 @@ async function captureMatches(locator) {
|
|
|
59
89
|
* would send the author to fix the wrong descriptor.
|
|
60
90
|
*/
|
|
61
91
|
async function act(page, element, action) {
|
|
92
|
+
const target = await widenedForWindow(element, page.url(), probeOf(page)) ?? element;
|
|
62
93
|
try {
|
|
63
|
-
return await action(locate(page,
|
|
94
|
+
return await action(locate(page, target));
|
|
64
95
|
} catch (error) {
|
|
65
96
|
if (!isStrictViolation(error)) throw error;
|
|
66
|
-
const culprit = await findAmbiguousLevel(page,
|
|
97
|
+
const culprit = await findAmbiguousLevel(page, target);
|
|
98
|
+
const matches = await captureMatches(locate(page, culprit));
|
|
67
99
|
throw new AmbiguousElementError(describeAmbiguity({
|
|
68
100
|
element: culprit,
|
|
69
|
-
matches
|
|
101
|
+
matches,
|
|
70
102
|
url: page.url()
|
|
71
103
|
}));
|
|
72
104
|
}
|
|
73
105
|
}
|
|
106
|
+
/** What the transitional window may ask of the page. */
|
|
107
|
+
function probeOf(page) {
|
|
108
|
+
return {
|
|
109
|
+
count: async (element) => await locate(page, element).count(),
|
|
110
|
+
nameOf: async (element) => {
|
|
111
|
+
const [match] = await captureMatches(locate(page, element));
|
|
112
|
+
return match?.accessibleName ?? match?.text;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
74
116
|
/** Walk the scope chain outside-in; the outermost ambiguous level is the fault. */
|
|
75
117
|
async function findAmbiguousLevel(page, element) {
|
|
76
118
|
const chain = [];
|
|
@@ -279,7 +321,8 @@ var PlaywrightAdapter = class {
|
|
|
279
321
|
/** Screenshot the failing state into a temp file; never masks the original error. */
|
|
280
322
|
async captureEvidence(page) {
|
|
281
323
|
try {
|
|
282
|
-
const
|
|
324
|
+
const dir = mkdtempSync(resolve(tmpdir(), "spec-website-"));
|
|
325
|
+
const path = resolve(dir, "failure.png");
|
|
283
326
|
await page.screenshot({
|
|
284
327
|
fullPage: true,
|
|
285
328
|
path
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as isStreamBody, n as ContractQueue, t as toReadableStream } from "./stream.js";
|
|
2
|
-
//#region src/
|
|
2
|
+
//#region src/seams/msw/handlers.ts
|
|
3
3
|
/**
|
|
4
4
|
* What a chain with zero contracts gets where the facet's scope is KNOWN but
|
|
5
5
|
* not total — the node engine, which shares a process with everything else the
|
|
@@ -115,7 +115,7 @@ async function observe(request) {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
//#endregion
|
|
118
|
-
//#region src/
|
|
118
|
+
//#region src/seams/msw/server.ts
|
|
119
119
|
let mswModule = null;
|
|
120
120
|
let mswHttp = null;
|
|
121
121
|
async function loadMsw() {
|
package/dist/stream.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as CaptureScope } from "./match.js";
|
|
2
|
-
import {
|
|
3
|
-
//#region src/
|
|
2
|
+
import { A as placeholderPatternSource, D as hasPlaceholders, P as textEquals } from "./literate.js";
|
|
3
|
+
//#region src/model/contracts/contract.ts
|
|
4
4
|
/**
|
|
5
5
|
* Declare one contract. Identity function — its value is the enforced shape
|
|
6
6
|
* and the naming convention:
|
|
@@ -86,7 +86,7 @@ function contractsOf(input) {
|
|
|
86
86
|
return flatten([input]);
|
|
87
87
|
}
|
|
88
88
|
//#endregion
|
|
89
|
-
//#region src/
|
|
89
|
+
//#region src/model/contracts/types.ts
|
|
90
90
|
/**
|
|
91
91
|
* The marker that tells a STREAMED body from a value that happens to be an
|
|
92
92
|
* object. A body reaches three different engines (msw's server, msw's worker,
|
|
@@ -100,7 +100,7 @@ function isStreamBody(body) {
|
|
|
100
100
|
return typeof body === "object" && body !== null && "kind" in body && body.kind === "@jterrazz/test:stream";
|
|
101
101
|
}
|
|
102
102
|
//#endregion
|
|
103
|
-
//#region src/
|
|
103
|
+
//#region src/model/timing/pause.ts
|
|
104
104
|
/**
|
|
105
105
|
* One answer to "wait" — the only `setTimeout` the model owns.
|
|
106
106
|
*
|
|
@@ -115,7 +115,7 @@ async function pause(ms) {
|
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
117
|
//#endregion
|
|
118
|
-
//#region src/
|
|
118
|
+
//#region src/model/contracts/queue.ts
|
|
119
119
|
/**
|
|
120
120
|
* The ONE contract queue — pure, engine-agnostic (no MSW, no node:http).
|
|
121
121
|
* Both engines consume it: the MSW integration on api/jobs chains, and the
|
|
@@ -288,7 +288,7 @@ var ContractQueue = class {
|
|
|
288
288
|
}
|
|
289
289
|
};
|
|
290
290
|
//#endregion
|
|
291
|
-
//#region src/
|
|
291
|
+
//#region src/model/contracts/stream.ts
|
|
292
292
|
/**
|
|
293
293
|
* A declared stream, made real — ONE serialisation for all three engines.
|
|
294
294
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/model/elements/ambiguity.ts
|
|
2
2
|
/**
|
|
3
3
|
* The ambiguity refusal — CONVENTIONS W3.
|
|
4
4
|
*
|
|
@@ -52,8 +52,9 @@ const CONTEXT_LANDMARKS = {
|
|
|
52
52
|
};
|
|
53
53
|
/**
|
|
54
54
|
* Render a descriptor as the source that would build it — `link('Articles')`,
|
|
55
|
-
* `within(navigation(), link('Articles', { exact:
|
|
56
|
-
* the vocabulary the author wrote, never playwright's
|
|
55
|
+
* `within(navigation(), link('Articles', { exact: false }))`. The error speaks
|
|
56
|
+
* the vocabulary the author wrote, never playwright's — so the option appears
|
|
57
|
+
* only when the AUTHOR stated one, whichever way.
|
|
57
58
|
*/
|
|
58
59
|
function formatElement(element) {
|
|
59
60
|
const bare = formatBare(element);
|
|
@@ -62,7 +63,7 @@ function formatElement(element) {
|
|
|
62
63
|
function formatBare(element) {
|
|
63
64
|
const args = [];
|
|
64
65
|
if (element.name !== void 0) args.push(JSON.stringify(element.name));
|
|
65
|
-
if (element.exact) args.push(
|
|
66
|
+
if (element.exact !== void 0) args.push(`{ exact: ${String(element.exact)} }`);
|
|
66
67
|
return `${CONSTRUCTORS[element.kind]}(${args.join(", ")})`;
|
|
67
68
|
}
|
|
68
69
|
/** `1. <a href="/articles">Articles</a> in <nav>` — one evidence line per candidate. */
|
|
@@ -98,7 +99,7 @@ function formatFixes(element, matches) {
|
|
|
98
99
|
const alsoHere = rest.length > 0 ? ` [also here: ${rest.map(([name]) => name).join(", ")}]` : "";
|
|
99
100
|
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
101
|
}
|
|
101
|
-
if (
|
|
102
|
+
if (element.exact === false && element.name !== void 0) {
|
|
102
103
|
const remaining = matches.filter((match) => match.text === element.name).length;
|
|
103
104
|
if (remaining > 0 && remaining < matches.length) fixes.push(`exact name ${formatBare({
|
|
104
105
|
...element,
|
|
@@ -138,8 +139,106 @@ function describeAmbiguity(options) {
|
|
|
138
139
|
"Disambiguate with one of:",
|
|
139
140
|
...fixes,
|
|
140
141
|
"",
|
|
141
|
-
"Docs: docs/
|
|
142
|
+
"Docs: docs/13-elements.md#designating-exactly-one-element (CONVENTIONS W3)"
|
|
142
143
|
].join("\n");
|
|
143
144
|
}
|
|
144
145
|
//#endregion
|
|
145
|
-
|
|
146
|
+
//#region src/model/elements/substring-warning.ts
|
|
147
|
+
/**
|
|
148
|
+
* The transitional warning for a name that matches only as a SUBSTRING.
|
|
149
|
+
*
|
|
150
|
+
* Until 16.0 a descriptor's name matched any element whose accessible name
|
|
151
|
+
* CONTAINED it, so `link('Articles')` also designated "Read Articles" — and a
|
|
152
|
+
* test could pass for years against the wrong element without anyone noticing,
|
|
153
|
+
* because the wrong element was usually next to the right one. Exact is the
|
|
154
|
+
* default now.
|
|
155
|
+
*
|
|
156
|
+
* A lint rule cannot carry this: whether a name matched whole or in part is a
|
|
157
|
+
* fact about a RUN, and a verdict fed by a previous run's artefact is not
|
|
158
|
+
* deterministic. So it is a runtime warning, printed by the adapter that did
|
|
159
|
+
* the looking, at the moment it has both answers in hand — nothing found under
|
|
160
|
+
* exact, something found under substring.
|
|
161
|
+
*
|
|
162
|
+
* It is printed ONCE per descriptor per process: a scenario that clicks the
|
|
163
|
+
* same link in ten tests has one thing to fix, not ten lines to read.
|
|
164
|
+
*/
|
|
165
|
+
/** How long the window lasts, stated in the message itself. */
|
|
166
|
+
const WINDOW = "through 16.x";
|
|
167
|
+
/** Descriptors already reported in this process. */
|
|
168
|
+
const reported = /* @__PURE__ */ new Set();
|
|
169
|
+
/** The identity a descriptor is deduplicated by — kind, name, and its scope chain. */
|
|
170
|
+
function identityOf(element) {
|
|
171
|
+
const own = `${element.kind}:${element.name ?? ""}`;
|
|
172
|
+
return element.scope === void 0 ? own : `${identityOf(element.scope)}>${own}`;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Report that `element` designated nothing as a whole name, but would have
|
|
176
|
+
* designated something as a substring. Returns whether a line was printed,
|
|
177
|
+
* which is what the adapters' own tests assert on.
|
|
178
|
+
*/
|
|
179
|
+
function warnSubstringOnly(element, where, found) {
|
|
180
|
+
const identity = identityOf(element);
|
|
181
|
+
if (reported.has(identity)) return false;
|
|
182
|
+
reported.add(identity);
|
|
183
|
+
const spelling = found === void 0 ? "" : ` The name to write is '${found.replaceAll(/\s+/gu, " ").trim()}'.`;
|
|
184
|
+
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}).${spelling} Names are exact from 16.0: write the name in full, scope it with \`within(…)\`, or state \`{ exact: false }\` to keep the old behaviour. The substring is still resolved, with this warning, ${WINDOW}; gone in 17.0, where the same descriptor is simply not found.`);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The descriptor to retry with, LEVEL by level, or `undefined` when nothing
|
|
189
|
+
* would change.
|
|
190
|
+
*
|
|
191
|
+
* Every level of the chain is asked the same question the target is asked —
|
|
192
|
+
* `within(button('Proof of authorship'), content('Verified'))` against a
|
|
193
|
+
* button named "Proof of authorship Verified" failed on the SCOPE, and a
|
|
194
|
+
* warning that only ever widened the target printed nothing at all. The chain
|
|
195
|
+
* is walked outside-in and rebuilt as it goes, since an inner level's answer
|
|
196
|
+
* is meaningless under a scope that designates nothing.
|
|
197
|
+
*
|
|
198
|
+
* A level that states an `exact` of its own is left alone: an author who wrote
|
|
199
|
+
* `{ exact: false }` chose the substring, and one who wrote `{ exact: true }`
|
|
200
|
+
* was already exact.
|
|
201
|
+
*
|
|
202
|
+
* Asked BEFORE the action, not after it fails: a verb waits its whole
|
|
203
|
+
* actionability budget, which is the test's own budget, so a retry afterwards
|
|
204
|
+
* never runs — the test is already over. The probes are counts, which wait for
|
|
205
|
+
* nothing, and the common case (the name matches whole) costs one per level.
|
|
206
|
+
*/
|
|
207
|
+
async function widenedForWindow(element, where, probe) {
|
|
208
|
+
const chain = [];
|
|
209
|
+
for (let level = element; level; level = level.scope) chain.unshift(level);
|
|
210
|
+
let rebuilt;
|
|
211
|
+
let widened = false;
|
|
212
|
+
for (const level of chain) {
|
|
213
|
+
const candidate = {
|
|
214
|
+
...level,
|
|
215
|
+
...rebuilt === void 0 ? {} : { scope: rebuilt }
|
|
216
|
+
};
|
|
217
|
+
const loose = await widenedLevel(candidate, where, probe);
|
|
218
|
+
widened ||= loose !== void 0;
|
|
219
|
+
rebuilt = loose ?? candidate;
|
|
220
|
+
}
|
|
221
|
+
return widened ? rebuilt : void 0;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* The one level, widened and warned about, or `undefined` when it designates
|
|
225
|
+
* something already, states its own `exact`, carries no name, or would
|
|
226
|
+
* designate nothing either way.
|
|
227
|
+
*/
|
|
228
|
+
async function widenedLevel(candidate, where, probe) {
|
|
229
|
+
if (candidate.exact !== void 0 || candidate.name === void 0) return;
|
|
230
|
+
try {
|
|
231
|
+
if (await probe.count(candidate) > 0) return;
|
|
232
|
+
const loose = {
|
|
233
|
+
...candidate,
|
|
234
|
+
exact: false
|
|
235
|
+
};
|
|
236
|
+
if (await probe.count(loose) !== 1) return;
|
|
237
|
+
warnSubstringOnly(candidate, where, await probe.nameOf(loose));
|
|
238
|
+
return loose;
|
|
239
|
+
} catch {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
244
|
+
export { formatElement as a, describeAmbiguity as i, widenedForWindow as n, AmbiguousElementError as r, warnSubstringOnly as t };
|
package/dist/vitest.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TestProjectInlineConfiguration, ViteUserConfig } from "vitest/config";
|
|
2
2
|
import { UserConfig, UserConfigFn } from "vite";
|
|
3
|
-
//#region src/
|
|
3
|
+
//#region src/runner/literate-plugin.d.ts
|
|
4
4
|
type LiterateOptions = {
|
|
5
5
|
/**
|
|
6
6
|
* Globs added to the project's test include. Default: every `.spec.yaml`
|
|
@@ -40,7 +40,7 @@ type LiteratePlugin = {
|
|
|
40
40
|
};
|
|
41
41
|
declare function literate(options: LiterateOptions): LiteratePlugin;
|
|
42
42
|
//#endregion
|
|
43
|
-
//#region src/
|
|
43
|
+
//#region src/runner/preset.d.ts
|
|
44
44
|
/**
|
|
45
45
|
* Options are a vite/vitest config plus one key of the framework's own.
|
|
46
46
|
* Everything stated here wins over the preset's defaults.
|
|
@@ -58,17 +58,17 @@ type SpecConfig = {
|
|
|
58
58
|
} & ViteUserConfig;
|
|
59
59
|
declare function defineSpecConfig(config?: SpecConfig): ViteUserConfig;
|
|
60
60
|
//#endregion
|
|
61
|
-
//#region src/
|
|
61
|
+
//#region src/runner/facet-project.d.ts
|
|
62
62
|
/**
|
|
63
|
-
* The
|
|
64
|
-
* kind runs in.
|
|
63
|
+
* The node facets' canonical projects.
|
|
65
64
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
65
|
+
* Each one collects the facet's own tree — `specs/<facet>/**\/*.spec.ts`, the
|
|
66
|
+
* suffix that says "the assembled product" — carries the preset's budgets and
|
|
67
|
+
* artefact directory, and runs in group 0 — the browser facets follow, so two
|
|
68
|
+
* Chromiums never share a slot on a two-vCPU runner. `{ include, exclude,
|
|
69
|
+
* timeout, serial }` is what a repository states when its tree, its budget or
|
|
70
|
+
* its parallelism differs;
|
|
71
|
+
* everything else is the framework's.
|
|
72
72
|
*/
|
|
73
73
|
/** What every helper accepts on top of its canonical project. */
|
|
74
74
|
type FacetProjectOptions = {
|
|
@@ -91,16 +91,31 @@ type FacetProjectOptions = {
|
|
|
91
91
|
/** Raise (or lower) the 30s budget for this project alone. */
|
|
92
92
|
timeout?: number;
|
|
93
93
|
};
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/facets/api/api.project.d.ts
|
|
96
|
+
/** `api()` — the app met through HTTP. */
|
|
97
|
+
declare function api(options?: FacetProjectOptions): TestProjectInlineConfiguration;
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/facets/cli/cli.project.d.ts
|
|
100
|
+
/** `cli()` — a binary, and the documents that specify it. */
|
|
101
|
+
type CliProjectOptions = FacetProjectOptions & {
|
|
96
102
|
/**
|
|
97
|
-
* The
|
|
98
|
-
*
|
|
103
|
+
* The literate door, wired by default: every `specs/cli/**\/*.spec.yaml`
|
|
104
|
+
* becomes a test file bound to `specs/cli/cli.specification.ts`. State
|
|
105
|
+
* `specification` to name another runner, `include` to narrow the glob, or
|
|
106
|
+
* `false` to collect no documents at all.
|
|
99
107
|
*/
|
|
100
|
-
|
|
108
|
+
literate?: false | Partial<LiterateOptions>;
|
|
101
109
|
};
|
|
102
|
-
/**
|
|
103
|
-
|
|
110
|
+
/**
|
|
111
|
+
* A cli project collects the facet's `.test.ts` files AND its documents: a
|
|
112
|
+
* `<case>.spec.yaml` is a test file of its own (docs/07 § the literate door),
|
|
113
|
+
* so the plugin's glob has to join the include of the project that collects
|
|
114
|
+
* them — which is this one, never the root config.
|
|
115
|
+
*/
|
|
116
|
+
declare function cli(options?: CliProjectOptions): TestProjectInlineConfiguration;
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/facets/component/component.project.d.ts
|
|
104
119
|
/** `component()` — rendered units, beside the components they cover. */
|
|
105
120
|
type ComponentProjectOptions = {
|
|
106
121
|
/** Freeze the page's `Date` for every render of the project. */
|
|
@@ -133,20 +148,6 @@ type ComponentProjectOptions = {
|
|
|
133
148
|
*/
|
|
134
149
|
wrap?: string;
|
|
135
150
|
} & FacetProjectOptions;
|
|
136
|
-
/**
|
|
137
|
-
* A module test runs beside the module it covers: no DOM, no browser, no
|
|
138
|
-
* pipeline. `.test.tsx` is excluded because a rendered thing is a component
|
|
139
|
-
* test — a project's dependency scan reads every file its globs match, and a
|
|
140
|
-
* `.tsx` collected here would be optimised for a page that never opens.
|
|
141
|
-
*/
|
|
142
|
-
declare function unit(options?: UnitProjectOptions): TestProjectInlineConfiguration;
|
|
143
|
-
/**
|
|
144
|
-
* A website spec meets the assembled product through a served page, in the
|
|
145
|
-
* Chromium playwright drives. It opens a browser, so it is scheduled before the
|
|
146
|
-
* component project and never beside it: two Chromiums must not share a slot on
|
|
147
|
-
* a 2-vCPU runner.
|
|
148
|
-
*/
|
|
149
|
-
declare function website(options?: WebsiteProjectOptions): TestProjectInlineConfiguration;
|
|
150
151
|
/**
|
|
151
152
|
* A component test renders in a real Chromium — the browser the website facet
|
|
152
153
|
* already drives through the same `playwright` peer.
|
|
@@ -156,33 +157,51 @@ declare function website(options?: WebsiteProjectOptions): TestProjectInlineConf
|
|
|
156
157
|
* your own `include` and the exclusion is yours to state too.
|
|
157
158
|
*/
|
|
158
159
|
declare function component(options?: ComponentProjectOptions): Promise<TestProjectInlineConfiguration>;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
/** `jobs()` — what a name triggers in-process. */
|
|
162
|
-
declare function jobs(options?: FacetProjectOptions): TestProjectInlineConfiguration;
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/facets/integration/integration.project.d.ts
|
|
163
162
|
/** `integration()` — a module against real services, or against a golden. */
|
|
164
163
|
declare function integration(options?: FacetProjectOptions): TestProjectInlineConfiguration;
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/facets/jobs/jobs.project.d.ts
|
|
166
|
+
/** `jobs()` — what a name triggers in-process. */
|
|
167
|
+
declare function jobs(options?: FacetProjectOptions): TestProjectInlineConfiguration;
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/facets/mobile/mobile.project.d.ts
|
|
165
170
|
/**
|
|
166
171
|
* `mobile()` — the app on a simulator. A simulator is the one thing that
|
|
167
172
|
* cannot share a machine with itself, so it runs last and alone (group 3).
|
|
168
173
|
*/
|
|
169
174
|
declare function mobile(options?: FacetProjectOptions): TestProjectInlineConfiguration;
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/facets/website/website.project.d.ts
|
|
177
|
+
/** `website()` — the assembled product, met through a served page. */
|
|
178
|
+
type WebsiteProjectOptions = FacetProjectOptions;
|
|
179
|
+
/**
|
|
180
|
+
* A website spec meets the assembled product through a served page, in the
|
|
181
|
+
* Chromium playwright drives. It opens a browser, so it is scheduled before the
|
|
182
|
+
* component project and never beside it: two Chromiums must not share a slot on
|
|
183
|
+
* a 2-vCPU runner.
|
|
184
|
+
*/
|
|
185
|
+
declare function website(options?: WebsiteProjectOptions): TestProjectInlineConfiguration;
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/runner/projects.d.ts
|
|
188
|
+
/** `unit()` — module tests, beside the modules they cover (CONVENTIONS I2). */
|
|
189
|
+
type UnitProjectOptions = FacetProjectOptions & {
|
|
172
190
|
/**
|
|
173
|
-
* The
|
|
174
|
-
* becomes
|
|
175
|
-
* `specification` to name another runner, `include` to narrow the glob, or
|
|
176
|
-
* `false` to collect no documents at all.
|
|
191
|
+
* The trees module tests live in, when they do not live in `src/`. Each
|
|
192
|
+
* root becomes `<root>/**\/*.test.ts`; `include` states the globs outright.
|
|
177
193
|
*/
|
|
178
|
-
|
|
194
|
+
roots?: string[];
|
|
179
195
|
};
|
|
180
196
|
/**
|
|
181
|
-
* A
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
197
|
+
* A module test runs beside the module it covers: no DOM, no browser, no
|
|
198
|
+
* pipeline. Its suffix is `.test.ts` and its place is OUTSIDE `specs/`, where
|
|
199
|
+
* the assembled product is specified in `.spec.ts` — the two exclusions say
|
|
200
|
+
* exactly that. `.test.tsx` is the third exclusion because a rendered thing is
|
|
201
|
+
* a component test: a project's dependency scan reads every file its globs
|
|
202
|
+
* match, and a `.tsx` collected here would be optimised for a page that never
|
|
203
|
+
* opens.
|
|
185
204
|
*/
|
|
186
|
-
declare function
|
|
205
|
+
declare function unit(options?: UnitProjectOptions): TestProjectInlineConfiguration;
|
|
187
206
|
//#endregion
|
|
188
207
|
export { type CliProjectOptions, type ComponentProjectOptions, type FacetProjectOptions, type LiterateOptions, type LiteratePlugin, type SpecConfig, type UnitProjectOptions, type WebsiteProjectOptions, api, cli, component, defineSpecConfig, integration, jobs, literate, mobile, unit, website };
|