@jterrazz/test 16.0.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/dist/appium.adapter.js +59 -4
- package/dist/browser/index.js +88 -40
- package/dist/checker.js +272 -63
- package/dist/index.d.ts +33 -4
- package/dist/index.js +47 -24
- package/dist/oxlint.cjs +100 -27
- package/dist/oxlint.js +100 -27
- package/dist/playwright.adapter.js +14 -65
- package/dist/{ambiguity.js → substring-warning.js} +99 -1
- package/dist/vitest.js +71 -11
- 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 +2 -1
package/dist/index.js
CHANGED
|
@@ -66,6 +66,12 @@ function isOptions(value) {
|
|
|
66
66
|
* The framework owns the seam so a test never has to: this is the one place
|
|
67
67
|
* `fetch` is wrapped, it is put back when the scope ends, and what it does is
|
|
68
68
|
* the one thing a page would have done for free — turn `/api/posts` into a URL.
|
|
69
|
+
*
|
|
70
|
+
* Validating the origin and INSTALLING the wrapper are two steps because they
|
|
71
|
+
* happen at two moments: the refusal of a bad origin belongs before anything
|
|
72
|
+
* starts, while the wrapper may only go on once the engine is listening — it
|
|
73
|
+
* has to sit ON TOP of whatever `fetch` the engine installed, and be taken off
|
|
74
|
+
* before the engine takes its own back.
|
|
69
75
|
*/
|
|
70
76
|
function resolveRelativeAgainst(origin) {
|
|
71
77
|
let base;
|
|
@@ -74,10 +80,12 @@ function resolveRelativeAgainst(origin) {
|
|
|
74
80
|
} catch {
|
|
75
81
|
throw new Error(`intercept(): \`origin\` states where a relative request resolves — \`${origin}\` is not an absolute URL (e.g. 'http://console.test').`);
|
|
76
82
|
}
|
|
77
|
-
const original = globalThis.fetch;
|
|
78
|
-
globalThis.fetch = async (input, init) => typeof input === "string" && input.startsWith("/") ? await original(new URL(input, base).toString(), init) : await original(input, init);
|
|
79
83
|
return () => {
|
|
80
|
-
globalThis.fetch
|
|
84
|
+
const engine = globalThis.fetch;
|
|
85
|
+
globalThis.fetch = async (input, init) => typeof input === "string" && input.startsWith("/") ? await engine(new URL(input, base).toString(), init) : await engine(input, init);
|
|
86
|
+
return () => {
|
|
87
|
+
globalThis.fetch = engine;
|
|
88
|
+
};
|
|
81
89
|
};
|
|
82
90
|
}
|
|
83
91
|
/**
|
|
@@ -92,18 +100,13 @@ function interceptThrough(register) {
|
|
|
92
100
|
const options = isOptions(maybeOptions) ? maybeOptions : isOptions(maybeResponseOrOptions) ? maybeResponseOrOptions : void 0;
|
|
93
101
|
const contracts = declared(requestOrContracts, options === maybeResponseOrOptions ? void 0 : maybeResponseOrOptions);
|
|
94
102
|
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.");
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
registration = await register(contracts);
|
|
99
|
-
} catch (error) {
|
|
100
|
-
restoreOrigin?.();
|
|
101
|
-
throw error;
|
|
102
|
-
}
|
|
103
|
+
const installOrigin = options?.origin === void 0 ? void 0 : resolveRelativeAgainst(options.origin);
|
|
104
|
+
const registration = await register(contracts);
|
|
105
|
+
const restoreOrigin = installOrigin?.();
|
|
103
106
|
return { [Symbol.asyncDispose]: async () => {
|
|
107
|
+
restoreOrigin?.();
|
|
104
108
|
const violation = registration.violation();
|
|
105
109
|
registration.cleanup();
|
|
106
|
-
restoreOrigin?.();
|
|
107
110
|
await Promise.resolve();
|
|
108
111
|
if (violation) throw violation;
|
|
109
112
|
} };
|
|
@@ -2012,10 +2015,14 @@ var CallResult = class extends BaseResult {
|
|
|
2012
2015
|
/**
|
|
2013
2016
|
* What the call RETURNED. A string reads as a stream (`<name>.txt`),
|
|
2014
2017
|
* anything else as JSON (`<name>.json`) — the same two subjects every
|
|
2015
|
-
* other facet's results are built from
|
|
2018
|
+
* other facet's results are built from, chosen on the value in hand and
|
|
2019
|
+
* typed by {@link CallValue}, which answers for the three ways a type can
|
|
2020
|
+
* relate to `string`.
|
|
2016
2021
|
*
|
|
2017
|
-
* A call that threw
|
|
2018
|
-
*
|
|
2022
|
+
* A call that threw has no value to choose an accessor on, so `value` is
|
|
2023
|
+
* the JSON reading of `null` whatever the declared type promises: what a
|
|
2024
|
+
* refusal produced is {@link CallResult.error}, and that is where a spec
|
|
2025
|
+
* reads it.
|
|
2019
2026
|
*/
|
|
2020
2027
|
get value() {
|
|
2021
2028
|
const produced = this.outcome.threw ? null : this.outcome.value;
|
|
@@ -2063,7 +2070,10 @@ var FetchResult = class extends BaseResult {
|
|
|
2063
2070
|
}
|
|
2064
2071
|
/** The response body as a text accessor (`toMatch('robots.txt')`, `.grep()`). */
|
|
2065
2072
|
get body() {
|
|
2066
|
-
return new TextAccessor(this.exchange.body, "body", this.testDir, {
|
|
2073
|
+
return new TextAccessor(this.exchange.body, "body", this.testDir, {
|
|
2074
|
+
captures: this.captures,
|
|
2075
|
+
transform: this.config.transform
|
|
2076
|
+
});
|
|
2067
2077
|
}
|
|
2068
2078
|
/** Response headers as a flat, lower-cased key-value map. */
|
|
2069
2079
|
get headers() {
|
|
@@ -2071,7 +2081,7 @@ var FetchResult = class extends BaseResult {
|
|
|
2071
2081
|
}
|
|
2072
2082
|
/** The response body parsed as JSON. */
|
|
2073
2083
|
get json() {
|
|
2074
|
-
return new JsonAccessor(this.exchange.body, this.testDir,
|
|
2084
|
+
return new JsonAccessor(this.exchange.body, this.testDir, this.config.transform, this.captures);
|
|
2075
2085
|
}
|
|
2076
2086
|
/** The `location` header of a redirect response, or undefined. */
|
|
2077
2087
|
get location() {
|
|
@@ -2106,7 +2116,10 @@ var PageResult = class extends BaseResult {
|
|
|
2106
2116
|
*/
|
|
2107
2117
|
get console() {
|
|
2108
2118
|
const lines = this.page.consoleMessages.map((m) => `[${m.type}] ${m.text}`).join("\n");
|
|
2109
|
-
return new TextAccessor(lines, "console", this.testDir, {
|
|
2119
|
+
return new TextAccessor(lines, "console", this.testDir, {
|
|
2120
|
+
captures: this.captures,
|
|
2121
|
+
transform: this.config.transform
|
|
2122
|
+
});
|
|
2110
2123
|
}
|
|
2111
2124
|
/**
|
|
2112
2125
|
* Console messages of type `error` only — the stream you usually want
|
|
@@ -2115,7 +2128,10 @@ var PageResult = class extends BaseResult {
|
|
|
2115
2128
|
*/
|
|
2116
2129
|
get errors() {
|
|
2117
2130
|
const lines = this.page.consoleMessages.filter((m) => m.type === "error").map((m) => m.text).join("\n");
|
|
2118
|
-
return new TextAccessor(lines, "errors", this.testDir, {
|
|
2131
|
+
return new TextAccessor(lines, "errors", this.testDir, {
|
|
2132
|
+
captures: this.captures,
|
|
2133
|
+
transform: this.config.transform
|
|
2134
|
+
});
|
|
2119
2135
|
}
|
|
2120
2136
|
/**
|
|
2121
2137
|
* The document-head projection as a JSON accessor — title, canonical,
|
|
@@ -2129,11 +2145,14 @@ var PageResult = class extends BaseResult {
|
|
|
2129
2145
|
metas: namedMetas(this.page.metas),
|
|
2130
2146
|
title: this.page.title
|
|
2131
2147
|
};
|
|
2132
|
-
return new JsonAccessor(JSON.stringify(summary), this.testDir,
|
|
2148
|
+
return new JsonAccessor(JSON.stringify(summary), this.testDir, this.config.transform, this.captures);
|
|
2133
2149
|
}
|
|
2134
2150
|
/** The rendered DOM serialization as a text accessor. */
|
|
2135
2151
|
get html() {
|
|
2136
|
-
return new TextAccessor(this.page.html, "html", this.testDir, {
|
|
2152
|
+
return new TextAccessor(this.page.html, "html", this.testDir, {
|
|
2153
|
+
captures: this.captures,
|
|
2154
|
+
transform: this.config.transform
|
|
2155
|
+
});
|
|
2137
2156
|
}
|
|
2138
2157
|
/**
|
|
2139
2158
|
* Every `application/ld+json` block of the page, parsed, as one JSON
|
|
@@ -2141,7 +2160,7 @@ var PageResult = class extends BaseResult {
|
|
|
2141
2160
|
*/
|
|
2142
2161
|
get jsonLd() {
|
|
2143
2162
|
const combined = `[${this.page.jsonLdBlocks.join(",")}]`;
|
|
2144
|
-
return new JsonAccessor(combined, this.testDir,
|
|
2163
|
+
return new JsonAccessor(combined, this.testDir, this.config.transform, this.captures);
|
|
2145
2164
|
}
|
|
2146
2165
|
/** `<link>` elements of the head, in DOM order. */
|
|
2147
2166
|
get links() {
|
|
@@ -2157,7 +2176,10 @@ var PageResult = class extends BaseResult {
|
|
|
2157
2176
|
}
|
|
2158
2177
|
/** Rendered body text as a text accessor. */
|
|
2159
2178
|
get content() {
|
|
2160
|
-
return new TextAccessor(this.page.text, "text", this.testDir, {
|
|
2179
|
+
return new TextAccessor(this.page.text, "text", this.testDir, {
|
|
2180
|
+
captures: this.captures,
|
|
2181
|
+
transform: this.config.transform
|
|
2182
|
+
});
|
|
2161
2183
|
}
|
|
2162
2184
|
/**
|
|
2163
2185
|
* The ARIA snapshot of the rendered `<body>` — the page's outline, as the
|
|
@@ -3613,7 +3635,8 @@ async function startWebsite(options) {
|
|
|
3613
3635
|
backendUrl,
|
|
3614
3636
|
baseUrl,
|
|
3615
3637
|
browser: getBrowser,
|
|
3616
|
-
external: options.external ?? (options.server ? "block" : "allow")
|
|
3638
|
+
external: options.external ?? (options.server ? "block" : "allow"),
|
|
3639
|
+
transform: options.transform
|
|
3617
3640
|
})
|
|
3618
3641
|
};
|
|
3619
3642
|
}
|
package/dist/oxlint.cjs
CHANGED
|
@@ -441,7 +441,7 @@ const RULE_DOCS = {
|
|
|
441
441
|
},
|
|
442
442
|
"d8w-text-bypass": {
|
|
443
443
|
channel: "statique",
|
|
444
|
-
convention: "`expect(x.text).toContain/toMatch` short-circuits the typed accessor subject → warning.",
|
|
444
|
+
convention: "`expect(x.text).toContain/toMatch` short-circuits the typed accessor subject → warning. A RegExp argument is exempt: `toMatch` on an accessor takes a fixture NAME and throws on a regex, so reaching through to `.text` is the sanctioned form for a raw pattern (chapter 14).",
|
|
445
445
|
facet: "model",
|
|
446
446
|
family: "D",
|
|
447
447
|
fix: "Assert on the accessor itself (`expect(x)`), which carries the tokens and the fixture resolution.",
|
|
@@ -651,7 +651,7 @@ const RULE_DOCS = {
|
|
|
651
651
|
},
|
|
652
652
|
"i4-no-module-doubles": {
|
|
653
653
|
channel: "statique",
|
|
654
|
-
convention: "A test doubles a PORT, never a module: `vi.mock`/`vi.doMock` of a specifier outside the `modules` allow-list (a config comment states why each entry is there), a `__mocks__/` or `__fixtures__/` directory, and — in a `module`-role test — importing a data asset (`.json`, `.sql`, `.yaml`, …) are all errors. A dotted specifier (`./dashboard.post`) is still code. The `vi.stubGlobal` clause moved to M3 in 16.0: one owner per convention.",
|
|
654
|
+
convention: "A test doubles a PORT, never a module: `vi.mock`/`vi.doMock` of a specifier outside the `modules` allow-list (a config comment states why each entry is there), a `__mocks__/` or `__fixtures__/` directory, and — in a `module`-role test — importing a data asset (`.json`, `.sql`, `.yaml`, …) are all errors. The specifier is read in BOTH spellings — `vi.mock('<spec>')` and the `vi.mock(import('<spec>'))` form `vitest/prefer-import-in-mock` requires — so an allow-listed module has a green spelling under both rules. A dotted specifier (`./dashboard.post`) is still code. The `vi.stubGlobal` clause moved to M3 in 16.0: one owner per convention.",
|
|
655
655
|
facet: "model",
|
|
656
656
|
family: "I",
|
|
657
657
|
fix: "Double the port with `mockOf<Port>()`, keep a payload in a `*.fixtures.ts` neighbour, and allow-list a native module with its reason (chapter 05 § Doubles).",
|
|
@@ -661,7 +661,7 @@ const RULE_DOCS = {
|
|
|
661
661
|
},
|
|
662
662
|
"j2-no-sleep": {
|
|
663
663
|
channel: "statique",
|
|
664
|
-
convention: "No arbitrary sleep (`setTimeout`/`setInterval`/`Atomics.wait`, or a `node:timers/promises` import) in any test file — synchronisation is `see()`/`gone()` inside a scenario and `waitUntil(predicate)` everywhere else.",
|
|
664
|
+
convention: "No arbitrary sleep (`setTimeout`/`setInterval`/`Atomics.wait`, or a `node:timers/promises` import) in any test file — synchronisation is `see()`/`gone()` inside a scenario and `waitUntil(predicate)` everywhere else. A timer inside a test DOUBLE's implementation (`vi.fn(…)`, `mockImplementation(…)`, a `mockOf` handler) is out of reach: the double is staging the world, not the test waiting on it.",
|
|
665
665
|
facet: "model",
|
|
666
666
|
family: "J",
|
|
667
667
|
fix: "Wait for the condition, not for a duration.",
|
|
@@ -729,10 +729,10 @@ const CHECKER_PASSES = [
|
|
|
729
729
|
},
|
|
730
730
|
{
|
|
731
731
|
channel: "checker",
|
|
732
|
-
convention: "A test under `specs/<facet>/`, where the facet root holds a `*.specification.ts(x)`, imports that module or constructs a runner itself.",
|
|
732
|
+
convention: "A test under `specs/<facet>/`, where the facet root holds a `*.specification.ts(x)`, imports that module or constructs a runner itself. Ground is out of reach — nothing under a leading-underscore folder is a spec, a `.test.ts` under `_expected/` being the GOLDEN a fixer writes. So is any first-level folder that is not a facet: a specification with no module to sit beside and no runner to reach lives in a repository suite there.",
|
|
733
733
|
facet: "model",
|
|
734
734
|
family: "C",
|
|
735
|
-
fix: "Import `{ <facet> }` from `<facet>.specification.js`,
|
|
735
|
+
fix: "Import `{ <facet> }` from `<facet>.specification.js`, move a module test beside its module, or move a repository suite to a first-level folder of its own under `specs/`.",
|
|
736
736
|
id: "C18",
|
|
737
737
|
name: "c18-module-test-under-facet",
|
|
738
738
|
rationale: "A test reaching no runner under a facet is a module test paying the facet's budget and services to prove something that belongs beside its module.",
|
|
@@ -806,7 +806,7 @@ const CHECKER_PASSES = [
|
|
|
806
806
|
},
|
|
807
807
|
{
|
|
808
808
|
channel: "checker",
|
|
809
|
-
convention: "A member declares no dependency `@jterrazz/test` already carries (`msw`, `vitest-mock-extended`; `yaml` in devDependencies only — as a production dependency it is the product’s own library) and no retired seam (`happy-dom`, `jsdom`, `@testing-library/*`, `@playwright/test`, `mockdate`) — except `@testing-library/react-native`
|
|
809
|
+
convention: "A member declares no dependency `@jterrazz/test` already carries (`msw`, `vitest-mock-extended`; `yaml` in devDependencies only — as a production dependency it is the product’s own library) and no retired seam (`happy-dom`, `jsdom`, `@testing-library/*`, `@playwright/test`, `mockdate`) — except `@testing-library/react-native` where jest runs the tests of the member — the only vocabulary that runtime has until the React Native answer lands. Jest runs them when the member DECLARES it (a `jest`/`jest-expo` dependency, a `jest` field, a `test` script calling it) or when a `jest.config.*` elsewhere in the workspace ROOTS into the member, which is the shape a library co-locating its render tests takes when the app beside it owns the only working toolchain. The optional peers — `playwright`, `vite`, `react`, `better-sqlite3`, `pg`, `redis`, `testcontainers`… — are declared by the consumer, by design.",
|
|
810
810
|
facet: "model",
|
|
811
811
|
family: "F",
|
|
812
812
|
fix: "Remove the declaration: a transitive is already resolved by the package; a retired seam has a facet that replaces it (`component()`, `website()`, `clock`, `intercept()`). The diagnostic carries a sentence for each case.",
|
|
@@ -883,10 +883,10 @@ const CHECKER_PASSES = [
|
|
|
883
883
|
},
|
|
884
884
|
{
|
|
885
885
|
channel: "checker",
|
|
886
|
-
convention: "The suffix says the kind, and the tree agrees with it. A `.spec.ts` — the ASSEMBLED product — lives under `specs/<facet>/`; anywhere else is an error. A `.test.ts` — the UNIT — lives beside its module; under a facet folder (`api`, `cli`, `integration`, `jobs`, `mobile`, `website`) it is an error, fixable by `--fix` (a `git mv`). A `.test.ts` that reaches no runner is C18’s, whose fix is the opposite move, and the mover leaves it alone. A first level that is NOT a facet is a repository suite: it covers a tree, keeps `.test.ts`, and C1’s declared depth is what judges it. A document is named `<case>.spec.yaml`: `<case>` in kebab-case, without the words `test`/`spec`/`cli` the suffix already carries, and never the bare name of its directory.",
|
|
886
|
+
convention: "The suffix says the kind, and the tree agrees with it. A `.spec.ts` — the ASSEMBLED product — lives under `specs/<facet>/`; anywhere else is an error. A `.test.ts` — the UNIT — lives beside its module; under a facet folder (`api`, `cli`, `integration`, `jobs`, `mobile`, `website`) it is an error, fixable by `--fix` (a `git mv`). A `.test.ts` that reaches no runner is C18’s, whose fix is the opposite move, and the mover leaves it alone. A first level that is NOT a facet is a repository suite: it covers a tree, keeps `.test.ts`, and C1’s declared depth is what judges it — unless the tree carries its `*.specification.ts` at its own ROOT, which makes the whole tree one facet’s and every `.test.ts` below it a spec. A document is named `<case>.spec.yaml`: `<case>` in kebab-case, without the words `test`/`spec`/`cli` the suffix already carries, and never the bare name of its directory.",
|
|
887
887
|
facet: "model",
|
|
888
888
|
family: "C",
|
|
889
|
-
fix: "Move the `.spec.ts` under `specs/<facet>/` (or rename it `.test.ts` beside its module); rename the facet `.test.ts` to `.spec.ts` — `jterrazz-test-check <root> --fix` does it with `git mv`, and names every include
|
|
889
|
+
fix: "Move the `.spec.ts` under `specs/<facet>/` (or rename it `.test.ts` beside its module); rename the facet `.test.ts` to `.spec.ts` — `jterrazz-test-check <root> --fix` does it with `git mv`, and names every `include`/`exclude` entry of the member the rename broke.",
|
|
890
890
|
id: "C12",
|
|
891
891
|
name: "c12-spec-file-name",
|
|
892
892
|
rationale: "With no agreement between the suffix and the tree, a reader cannot say what a test proves without opening it; and the filename is the first sentence they read — `rm/rm.spec.yaml` says nothing twice.",
|
|
@@ -1404,7 +1404,7 @@ const RUNTIME_RULES = [
|
|
|
1404
1404
|
},
|
|
1405
1405
|
{
|
|
1406
1406
|
channel: "runtime",
|
|
1407
|
-
convention: "An element descriptor must designate exactly ONE element when a verb ACTS on it (`click`/`tap`/`fill`): if several match, the action is refused with an error enumerating the candidates and offering the rewrites (`within(...)`, another descriptor). The framework never acts on \"the first\". A name designates the WHOLE accessible name since 16.0 — `{ exact: false }` is the opt-out — and a descriptor that finds nothing whole but would have found something as a substring
|
|
1407
|
+
convention: "An element descriptor must designate exactly ONE element when a verb ACTS on it (`click`/`tap`/`fill`): if several match, the action is refused with an error enumerating the candidates and offering the rewrites (`within(...)`, another descriptor). The framework never acts on \"the first\". A name designates the WHOLE accessible name since 16.0 — `{ exact: false }` is the opt-out — and a descriptor that finds nothing whole but would have found something as a substring is still RESOLVED as a substring through 16.x, level by level, with a runtime warning naming the whole name to write, once per descriptor; in 17.0 it is simply not found. It holds on both scenario facets, website and mobile; on mobile, `see()` — which acts on nothing — is satisfied by any visible match (the XCUITest tree legitimately duplicates a label between a container and its child).",
|
|
1408
1408
|
facet: "model",
|
|
1409
1409
|
family: "W",
|
|
1410
1410
|
fix: "Scope it with `within(<landmark>, …)`, or name the element with a descriptor that designates one thing.",
|
|
@@ -1696,6 +1696,13 @@ function identifierName(node) {
|
|
|
1696
1696
|
const { name } = node;
|
|
1697
1697
|
return typeof name === "string" ? name : void 0;
|
|
1698
1698
|
}
|
|
1699
|
+
/** The source end offset of a node or comment (oxlint `end`, else `range[1]`). */
|
|
1700
|
+
function nodeEnd(node) {
|
|
1701
|
+
if (node === void 0) return -1;
|
|
1702
|
+
if (typeof node.end === "number") return node.end;
|
|
1703
|
+
const range = node.range;
|
|
1704
|
+
return Array.isArray(range) && typeof range[1] === "number" ? range[1] : -1;
|
|
1705
|
+
}
|
|
1699
1706
|
/** The three words a test narrates with, in the order they may appear. */
|
|
1700
1707
|
const MARKERS = [
|
|
1701
1708
|
"Given",
|
|
@@ -3149,8 +3156,8 @@ function testsItsNeighbour(filename) {
|
|
|
3149
3156
|
* product command/area with one or more `<aspect>.test.ts` files plus their
|
|
3150
3157
|
* shared asset dirs.
|
|
3151
3158
|
*
|
|
3152
|
-
* - a
|
|
3153
|
-
* `specs/<facet>/<domain>/<
|
|
3159
|
+
* - a spec sits at facet/domain depth — exactly
|
|
3160
|
+
* `specs/<facet>/<domain>/<aspect>.spec.ts`. A test directly at the facet root
|
|
3154
3161
|
* (or nested deeper than a domain) is rejected.
|
|
3155
3162
|
* - a `*.specification.ts` sits at the facet root — exactly
|
|
3156
3163
|
* `specs/<facet>/<file>.specification.ts`, never inside a domain.
|
|
@@ -3158,8 +3165,8 @@ function testsItsNeighbour(filename) {
|
|
|
3158
3165
|
* `depth: 'facet'`
|
|
3159
3166
|
*
|
|
3160
3167
|
* The same facet root, one degree looser: the folder follows the assets. A
|
|
3161
|
-
*
|
|
3162
|
-
* or one domain folder down (`specs/<facet>/<domain>/<aspect>.
|
|
3168
|
+
* spec sits EITHER at the facet root (`specs/<facet>/<aspect>.spec.ts`)
|
|
3169
|
+
* or one domain folder down (`specs/<facet>/<domain>/<aspect>.spec.ts`) — a
|
|
3163
3170
|
* test earns a domain folder when it carries assets of its own, and sits
|
|
3164
3171
|
* beside its siblings when it does not. Anything deeper is rejected, and so is
|
|
3165
3172
|
* a test loose at the specs root. A `*.specification.ts` sits at the facet
|
|
@@ -3168,9 +3175,10 @@ function testsItsNeighbour(filename) {
|
|
|
3168
3175
|
* `depth: 'mirror'`
|
|
3169
3176
|
*
|
|
3170
3177
|
* The tree mirrors something outside itself (a command tree, a source tree), so
|
|
3171
|
-
* the depth is that structure's and no fixed number can name it. A
|
|
3178
|
+
* the depth is that structure's and no fixed number can name it. A spec
|
|
3172
3179
|
* sits at ANY depth of at least one directory under `specs/`, and is NAMED
|
|
3173
|
-
* AFTER the directory holding it — `<dir>/<dir>.
|
|
3180
|
+
* AFTER the directory holding it — `<dir>/<dir>.spec.ts`, the suffix C12 gives
|
|
3181
|
+
* every file of a facet tree. That naming is what
|
|
3174
3182
|
* keeps the shape checkable: one test per mirrored node, no test loose at the
|
|
3175
3183
|
* specs root. Specification files are unconstrained: a mirror has no facet
|
|
3176
3184
|
* level to anchor them to.
|
|
@@ -3271,12 +3279,12 @@ const c1DomainStructure = {
|
|
|
3271
3279
|
messages: {
|
|
3272
3280
|
specInGround: "A spec must not live under \"{{ground}}/\" — a leading underscore marks GROUND (what the specs of a row stand on: _fixtures/, _expected/, _requests/, _seeds/), never a domain. Ground that is CODE keeps its own unit test: `<module>.test.ts` NEXT to the `<module>.ts` it tests (I2), and nothing else.",
|
|
3273
3281
|
specNotAtFacetRoot: "A `*.specification.ts` must sit at the facet root: `specs/<facet>/<name>.specification.ts`.",
|
|
3274
|
-
testAtFacetRoot: "A
|
|
3275
|
-
testAtSpecsRoot: "A
|
|
3276
|
-
testNotMirroringDirectory: "A
|
|
3277
|
-
testOutsideFacet: "A
|
|
3278
|
-
testTooDeep: "A
|
|
3279
|
-
testTooDeepForFacet: "A
|
|
3282
|
+
testAtFacetRoot: "A spec must live in a domain folder: `specs/<facet>/<domain>/<aspect>.spec.ts` — tests directly at the facet root are forbidden.",
|
|
3283
|
+
testAtSpecsRoot: "A spec must live in a directory under specs/ — with `depth: \"mirror\"` the tree mirrors a structure, and the specs root mirrors nothing.",
|
|
3284
|
+
testNotMirroringDirectory: "A spec must be named after the directory holding it — `{{directory}}/{{directory}}.spec.ts` — with `depth: \"mirror\"`.",
|
|
3285
|
+
testOutsideFacet: "A spec must live in a facet folder — `specs/<facet>/<aspect>.spec.ts`, or one domain deeper: with `depth: \"facet\"` the specs root holds facets, not tests.",
|
|
3286
|
+
testTooDeep: "A spec must sit at facet/domain depth: `specs/<facet>/<domain>/<aspect>.spec.ts` — no deeper nesting.",
|
|
3287
|
+
testTooDeepForFacet: "A spec must sit at the facet root or one domain folder down — `specs/<facet>/<aspect>.spec.ts` or `specs/<facet>/<domain>/<aspect>.spec.ts`: with `depth: \"facet\"` nothing nests deeper."
|
|
3280
3288
|
},
|
|
3281
3289
|
schema: [{
|
|
3282
3290
|
additionalProperties: false,
|
|
@@ -4015,11 +4023,22 @@ const d6wTransformTokenEquivalent = {
|
|
|
4015
4023
|
//#region src/lint/rules/model/d8w-text-bypass.ts
|
|
4016
4024
|
/** Text-oriented matchers that tempt a raw `.text` bypass (D8). */
|
|
4017
4025
|
const TEXT_MATCHERS = /* @__PURE__ */ new Set(["toContain", "toMatch"]);
|
|
4026
|
+
/** A regex literal, or `new RegExp(…)` — what an accessor subject refuses. */
|
|
4027
|
+
function isRegExp(node) {
|
|
4028
|
+
if (node === void 0) return false;
|
|
4029
|
+
if (node.type === "Literal") return "regex" in node;
|
|
4030
|
+
return node.type === "NewExpression" && identifierName(child(node, "callee")) === "RegExp";
|
|
4031
|
+
}
|
|
4018
4032
|
/**
|
|
4019
4033
|
* CONVENTIONS D8 (warning) — `.text` is the raw stream escape hatch. Asserting
|
|
4020
4034
|
* `expect(result.text).toContain(…)` throws away the typed subject (its token
|
|
4021
4035
|
* grammar, its `toMatch('file.txt')` fixture resolution) for a substring check.
|
|
4022
4036
|
* Prefer asserting on the accessor subject itself.
|
|
4037
|
+
*
|
|
4038
|
+
* A RegExp argument is exempt, because it is the one thing the typed subject
|
|
4039
|
+
* cannot take: `toMatch` on an accessor resolves a FIXTURE NAME and throws on a
|
|
4040
|
+
* regex, and chapter 14 names `expect(x.text).toMatch(/re/)` as the way through.
|
|
4041
|
+
* The rule warned on exactly the spelling its own manual prescribes.
|
|
4023
4042
|
*/
|
|
4024
4043
|
const d8wTextBypass = {
|
|
4025
4044
|
create(context) {
|
|
@@ -4031,6 +4050,7 @@ const d8wTextBypass = {
|
|
|
4031
4050
|
if (expectCall?.type !== "CallExpression") return;
|
|
4032
4051
|
const expectCallee = expectCall.callee;
|
|
4033
4052
|
if (expectCallee?.type !== "Identifier" || expectCallee.name !== "expect") return;
|
|
4053
|
+
if (isRegExp(childList(node, "arguments")[0])) return;
|
|
4034
4054
|
const subject = expectCall.arguments?.[0];
|
|
4035
4055
|
if (subject !== void 0 && memberPropertyName(subject) === "text") context.report({
|
|
4036
4056
|
data: { matcher },
|
|
@@ -4412,7 +4432,7 @@ const d16wAmbientValue = {
|
|
|
4412
4432
|
//#endregion
|
|
4413
4433
|
//#region src/lint/rules/model/d17w-double-only-oracle.ts
|
|
4414
4434
|
/** The three ways this vocabulary makes a double. */
|
|
4415
|
-
const DOUBLE_FACTORIES = /* @__PURE__ */ new Set([
|
|
4435
|
+
const DOUBLE_FACTORIES$1 = /* @__PURE__ */ new Set([
|
|
4416
4436
|
"mockOf",
|
|
4417
4437
|
"vi.fn",
|
|
4418
4438
|
"vi.spyOn"
|
|
@@ -4427,7 +4447,7 @@ function makesADouble(init) {
|
|
|
4427
4447
|
if (init.type === "AwaitExpression") return makesADouble(child(init, "argument"));
|
|
4428
4448
|
if (init.type !== "CallExpression") return false;
|
|
4429
4449
|
const path = memberPath(child(init, "callee"));
|
|
4430
|
-
return path !== void 0 && DOUBLE_FACTORIES.has(path);
|
|
4450
|
+
return path !== void 0 && DOUBLE_FACTORIES$1.has(path);
|
|
4431
4451
|
}
|
|
4432
4452
|
/** Every binding in the file whose value is a double this test built. */
|
|
4433
4453
|
function doubleBindings(root) {
|
|
@@ -5498,6 +5518,21 @@ function isModuleDouble(node) {
|
|
|
5498
5518
|
const property = child(callee, "property");
|
|
5499
5519
|
return identifierName(object) === "vi" && (identifierName(property) === "mock" || identifierName(property) === "doMock");
|
|
5500
5520
|
}
|
|
5521
|
+
/**
|
|
5522
|
+
* The specifier a module double names, in either spelling.
|
|
5523
|
+
*
|
|
5524
|
+
* `vi.mock('./x.js', …)` states it as a string; `vi.mock(import('./x.js'), …)`
|
|
5525
|
+
* states it as a dynamic import, which is the form `vitest/prefer-import-in-mock`
|
|
5526
|
+
* requires. Reading only the string left an allow-listed native module with no
|
|
5527
|
+
* green spelling: satisfy this rule and the upstream one fires, satisfy that one
|
|
5528
|
+
* and this fires.
|
|
5529
|
+
*/
|
|
5530
|
+
function mockedSpecifier(argument) {
|
|
5531
|
+
const literal = stringValue(argument);
|
|
5532
|
+
if (literal !== void 0) return literal;
|
|
5533
|
+
if (argument?.type !== "ImportExpression") return;
|
|
5534
|
+
return stringValue(child(argument, "source"));
|
|
5535
|
+
}
|
|
5501
5536
|
/** A bundler suffix (`./payload.json?raw`, `./doc.md#frag`) is not part of the extension. */
|
|
5502
5537
|
function withoutSuffix(source) {
|
|
5503
5538
|
return source.replace(/[?#].*$/u, "");
|
|
@@ -5540,7 +5575,7 @@ const i4NoModuleDoubles = {
|
|
|
5540
5575
|
const visitor = {
|
|
5541
5576
|
CallExpression(node) {
|
|
5542
5577
|
if (!isModuleDouble(node)) return;
|
|
5543
|
-
const named =
|
|
5578
|
+
const named = mockedSpecifier(childList(node, "arguments")[0]);
|
|
5544
5579
|
if (named === void 0 || !allowed.has(named)) context.report({
|
|
5545
5580
|
messageId: "viMock",
|
|
5546
5581
|
node
|
|
@@ -5583,6 +5618,26 @@ const i4NoModuleDoubles = {
|
|
|
5583
5618
|
};
|
|
5584
5619
|
//#endregion
|
|
5585
5620
|
//#region src/lint/rules/model/j2-no-sleep.ts
|
|
5621
|
+
/** The doubles whose implementation STAGES the world rather than waits on it. */
|
|
5622
|
+
const DOUBLE_FACTORIES = /* @__PURE__ */ new Set([
|
|
5623
|
+
"fn",
|
|
5624
|
+
"mockImplementation",
|
|
5625
|
+
"mockImplementationOnce",
|
|
5626
|
+
"mockOf"
|
|
5627
|
+
]);
|
|
5628
|
+
/**
|
|
5629
|
+
* Is this call a test double taking an implementation — `vi.fn(…)`,
|
|
5630
|
+
* `x.mockImplementation(…)`, `mockOf<Port>({ … })`?
|
|
5631
|
+
*
|
|
5632
|
+
* The member forms are read by their PROPERTY alone, because the object is
|
|
5633
|
+
* whatever the test called its double.
|
|
5634
|
+
*/
|
|
5635
|
+
function isDoubleImplementation(node) {
|
|
5636
|
+
const callee = child(node, "callee");
|
|
5637
|
+
if (callee === void 0) return false;
|
|
5638
|
+
const name = callee.type === "Identifier" ? identifierName(callee) : memberPropertyName(callee);
|
|
5639
|
+
return name !== void 0 && DOUBLE_FACTORIES.has(name);
|
|
5640
|
+
}
|
|
5586
5641
|
/**
|
|
5587
5642
|
* CONVENTIONS J2 — a test contains no arbitrary sleep, wherever it sits:
|
|
5588
5643
|
* synchronisation is `see()`/`gone()` inside a scenario and `waitUntil()`
|
|
@@ -5592,15 +5647,33 @@ const i4NoModuleDoubles = {
|
|
|
5592
5647
|
* The reach is every test file — a sleep in a module test beside `src/` waits
|
|
5593
5648
|
* exactly as blindly as one under `specs/`, and went unseen while the rule
|
|
5594
5649
|
* gated on the folder.
|
|
5650
|
+
*
|
|
5651
|
+
* What it does NOT reach is a timer inside a test DOUBLE's implementation. A
|
|
5652
|
+
* `vi.fn(() => setTimeout(…))` is the world being staged — a clone that settles
|
|
5653
|
+
* late, a handler that answers out of order — and the assertion that follows is
|
|
5654
|
+
* about the ORDER results come back in, which no predicate can state: a
|
|
5655
|
+
* `waitUntil` waits for something to become true, while what this test needs is
|
|
5656
|
+
* for something to happen late. The test is not the one waiting, so the rule is
|
|
5657
|
+
* not about it.
|
|
5595
5658
|
*/
|
|
5596
5659
|
const j2NoSleep = {
|
|
5597
5660
|
create(context) {
|
|
5598
5661
|
if (!isTestRole(roleOf(context.filename).role)) return {};
|
|
5662
|
+
const doubles = [];
|
|
5663
|
+
const insideADouble = (node) => doubles.some((span) => nodeStart(node) >= span.start && nodeEnd(node) <= span.end);
|
|
5599
5664
|
return {
|
|
5600
5665
|
CallExpression(node) {
|
|
5601
|
-
|
|
5666
|
+
if (isDoubleImplementation(node)) {
|
|
5667
|
+
doubles.push({
|
|
5668
|
+
end: nodeEnd(node),
|
|
5669
|
+
start: nodeStart(node)
|
|
5670
|
+
});
|
|
5671
|
+
return;
|
|
5672
|
+
}
|
|
5673
|
+
if (insideADouble(node)) return;
|
|
5674
|
+
const callee = child(node, "callee");
|
|
5602
5675
|
if (callee === void 0) return;
|
|
5603
|
-
const name = callee.type === "Identifier" ? callee
|
|
5676
|
+
const name = callee.type === "Identifier" ? identifierName(callee) : memberPropertyName(callee);
|
|
5604
5677
|
if (name === "setTimeout" || name === "setInterval") {
|
|
5605
5678
|
context.report({
|
|
5606
5679
|
messageId: "sleep",
|
|
@@ -5608,7 +5681,7 @@ const j2NoSleep = {
|
|
|
5608
5681
|
});
|
|
5609
5682
|
return;
|
|
5610
5683
|
}
|
|
5611
|
-
if (name === "wait" && callee.type === "MemberExpression" && callee
|
|
5684
|
+
if (name === "wait" && callee.type === "MemberExpression" && identifierName(child(callee, "object")) === "Atomics") context.report({
|
|
5612
5685
|
messageId: "sleep",
|
|
5613
5686
|
node
|
|
5614
5687
|
});
|