@jterrazz/test 15.1.1 → 15.3.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 +38 -29
- package/dist/ambiguity.js +23 -4
- package/dist/artifacts.js +108 -163
- package/dist/browser/index.js +2796 -0
- package/dist/checker.js +470 -29
- package/dist/index.d.ts +1434 -609
- package/dist/index.js +3696 -2745
- package/dist/intercept.js +117 -59
- package/dist/oxlint.cjs +481 -35
- package/dist/oxlint.d.cts +13 -1
- package/dist/oxlint.d.ts +13 -1
- package/dist/oxlint.js +481 -35
- package/dist/playwright.adapter.js +96 -22
- package/dist/{queue.js → stream.js} +61 -2
- package/dist/vitest.d.ts +130 -2
- package/dist/vitest.js +433 -3
- package/package.json +37 -5
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { n as describeAmbiguity, t as AmbiguousElementError } from "./ambiguity.js";
|
|
1
|
+
import { n as describeAmbiguity, r as formatElement, t as AmbiguousElementError } from "./ambiguity.js";
|
|
2
2
|
import { mkdtempSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
+
import { setTimeout } from "node:timers/promises";
|
|
5
6
|
//#region src/integrations/playwright/playwright.adapter.ts
|
|
6
7
|
/** How many candidates the ambiguity error enumerates before truncating. */
|
|
7
8
|
const MAX_REPORTED_MATCHES = 10;
|
|
9
|
+
const VALUE_TIMEOUT_MS = 3e4;
|
|
10
|
+
const VALUE_POLL_MS = 100;
|
|
8
11
|
/**
|
|
9
12
|
* Translate a user-facing descriptor into a playwright locator, resolving
|
|
10
13
|
* `scope` outside-in so `within(navigation(), link('X'))` searches the nav
|
|
@@ -14,26 +17,15 @@ const MAX_REPORTED_MATCHES = 10;
|
|
|
14
17
|
*/
|
|
15
18
|
function locate(root, element) {
|
|
16
19
|
const scope = element.scope ? locate(root, element.scope) : root;
|
|
17
|
-
const
|
|
18
|
-
|
|
20
|
+
const exact = element.exact ?? false;
|
|
21
|
+
const name = element.name ?? "";
|
|
22
|
+
if (element.kind === "field") return scope.getByLabel(name, { exact });
|
|
23
|
+
if (element.kind === "testId") return scope.getByTestId(name);
|
|
24
|
+
if (element.kind === "text") return scope.getByText(name, { exact });
|
|
25
|
+
return scope.getByRole(element.kind, element.name === void 0 ? { exact } : {
|
|
26
|
+
exact,
|
|
19
27
|
name: element.name
|
|
20
|
-
};
|
|
21
|
-
switch (element.kind) {
|
|
22
|
-
case "banner":
|
|
23
|
-
case "complementary":
|
|
24
|
-
case "contentinfo":
|
|
25
|
-
case "form":
|
|
26
|
-
case "main":
|
|
27
|
-
case "navigation":
|
|
28
|
-
case "region":
|
|
29
|
-
case "search": return scope.getByRole(element.kind, options);
|
|
30
|
-
case "button":
|
|
31
|
-
case "heading":
|
|
32
|
-
case "link": return scope.getByRole(element.kind, options);
|
|
33
|
-
case "field": return scope.getByLabel(element.name ?? "", { exact: element.exact ?? false });
|
|
34
|
-
case "testId": return scope.getByTestId(element.name ?? "");
|
|
35
|
-
case "text": return scope.getByText(element.name ?? "", { exact: element.exact ?? false });
|
|
36
|
-
}
|
|
28
|
+
});
|
|
37
29
|
}
|
|
38
30
|
/** Playwright signals "more than one match" through this error text. */
|
|
39
31
|
function isStrictViolation(error) {
|
|
@@ -48,11 +40,14 @@ async function captureMatches(locator) {
|
|
|
48
40
|
const landmark = element.parentElement?.closest(LANDMARKS);
|
|
49
41
|
const context = landmark ? landmark.getAttribute("role") ?? landmark.tagName.toLowerCase() : null;
|
|
50
42
|
const detail = element.getAttribute("href") ?? element.getAttribute("name");
|
|
43
|
+
const text = (element.textContent ?? "").replaceAll(/\s+/gu, " ").trim().slice(0, 80);
|
|
44
|
+
const label = element.getAttribute("aria-label")?.trim();
|
|
51
45
|
return {
|
|
46
|
+
accessibleName: label && label !== text ? label : void 0,
|
|
52
47
|
context: context ?? void 0,
|
|
53
48
|
detail: detail ?? void 0,
|
|
54
49
|
tag: element.tagName.toLowerCase(),
|
|
55
|
-
text
|
|
50
|
+
text
|
|
56
51
|
};
|
|
57
52
|
});
|
|
58
53
|
}, MAX_REPORTED_MATCHES);
|
|
@@ -83,6 +78,59 @@ async function findAmbiguousLevel(page, element) {
|
|
|
83
78
|
for (const level of chain.slice(0, -1)) if (await locate(page, level).count() > 1) return level;
|
|
84
79
|
return element;
|
|
85
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* The same element, narrowed by whatever STATE the descriptor carries — focus,
|
|
83
|
+
* or accepting input — or `null` when it carries none.
|
|
84
|
+
*
|
|
85
|
+
* A second condition on the locator rather than a one-shot probe: the locator
|
|
86
|
+
* keeps playwright's own retry, so `see(focused(x))` waits for the keyboard to
|
|
87
|
+
* arrive instead of asking once and failing on a frame of animation. It also
|
|
88
|
+
* needs nothing but `playwright` — `expect(...).toBeFocused()` lives in
|
|
89
|
+
* `@playwright/test`, which this package deliberately does not carry.
|
|
90
|
+
*/
|
|
91
|
+
function stateOnly(page, locator, element) {
|
|
92
|
+
if (element.focused === true) return locator.and(page.locator(":focus"));
|
|
93
|
+
if (element.disabled !== void 0) return locator.and(page.locator(element.disabled ? ":disabled" : ":enabled"));
|
|
94
|
+
if (element.selected !== void 0) return locator.and(page.locator(element.selected ? ":checked" : ":not(:checked)"));
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Is this descriptor answered by PRESENCE rather than by visibility? The
|
|
99
|
+
* options of a collapsed `<select>` are in the document and in the tree, and
|
|
100
|
+
* none of them has a box on the screen until a visitor opens it.
|
|
101
|
+
*/
|
|
102
|
+
function byPresence(element) {
|
|
103
|
+
return element.kind === "option";
|
|
104
|
+
}
|
|
105
|
+
/** The field's live value, or `null` when nothing (unambiguous) is there yet. */
|
|
106
|
+
async function currentValue(locator) {
|
|
107
|
+
try {
|
|
108
|
+
return await locator.inputValue({ timeout: VALUE_POLL_MS });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (isStrictViolation(error)) throw error;
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Wait until the field holds the value the descriptor carries — or, for
|
|
116
|
+
* `gone()`, until it no longer does.
|
|
117
|
+
*
|
|
118
|
+
* A poll rather than a narrowed locator: a value is a PROPERTY of the node,
|
|
119
|
+
* and the CSS a locator narrows with reads ATTRIBUTES — a controlled input
|
|
120
|
+
* whose `value` attribute never moves would answer for a value it no longer
|
|
121
|
+
* holds. The locator is re-read every round, so a re-rendered field is read
|
|
122
|
+
* again rather than held as a stale handle.
|
|
123
|
+
*/
|
|
124
|
+
async function waitForValue(locator, element, wanted) {
|
|
125
|
+
const value = element.value ?? "";
|
|
126
|
+
const deadline = Date.now() + VALUE_TIMEOUT_MS;
|
|
127
|
+
for (;;) {
|
|
128
|
+
const actual = await currentValue(locator);
|
|
129
|
+
if (actual === null ? !wanted : actual === value === wanted) return;
|
|
130
|
+
if (Date.now() >= deadline) throw new Error(`${formatElement(element)} ${wanted ? "never held" : "still holds"} ${JSON.stringify(value)}${actual === null ? " (it is not on the page)" : `, it holds ${JSON.stringify(actual)}`}`);
|
|
131
|
+
await setTimeout(VALUE_POLL_MS);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
86
134
|
/** The visitor implementation — every action auto-waits via playwright actionability. */
|
|
87
135
|
function createVisitor(page, baseUrl) {
|
|
88
136
|
return {
|
|
@@ -112,9 +160,32 @@ function createVisitor(page, baseUrl) {
|
|
|
112
160
|
press: async (key) => {
|
|
113
161
|
await page.keyboard.press(key);
|
|
114
162
|
},
|
|
163
|
+
gone: async (element) => {
|
|
164
|
+
await act(page, element, async (locator) => {
|
|
165
|
+
if (element.value !== void 0) {
|
|
166
|
+
await waitForValue(locator, element, false);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const narrowed = stateOnly(page, locator, element);
|
|
170
|
+
if (narrowed !== null) {
|
|
171
|
+
await narrowed.waitFor({ state: "detached" });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
await locator.waitFor({ state: byPresence(element) ? "detached" : "hidden" });
|
|
175
|
+
});
|
|
176
|
+
},
|
|
115
177
|
see: async (element) => {
|
|
116
178
|
await act(page, element, async (locator) => {
|
|
117
|
-
|
|
179
|
+
if (element.value !== void 0) {
|
|
180
|
+
await waitForValue(locator, element, true);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const narrowed = stateOnly(page, locator, element);
|
|
184
|
+
if (narrowed !== null) {
|
|
185
|
+
await narrowed.waitFor({ state: "attached" });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
await locator.waitFor({ state: byPresence(element) ? "attached" : "visible" });
|
|
118
189
|
});
|
|
119
190
|
},
|
|
120
191
|
select: async (element, option) => {
|
|
@@ -151,6 +222,7 @@ var PlaywrightAdapter = class {
|
|
|
151
222
|
}
|
|
152
223
|
try {
|
|
153
224
|
const page = await context.newPage();
|
|
225
|
+
if (options.clock !== void 0) await page.clock.setFixedTime(new Date(options.clock));
|
|
154
226
|
const consoleMessages = [];
|
|
155
227
|
page.on("console", (message) => {
|
|
156
228
|
consoleMessages.push({
|
|
@@ -192,9 +264,11 @@ var PlaywrightAdapter = class {
|
|
|
192
264
|
title: document.title
|
|
193
265
|
};
|
|
194
266
|
});
|
|
267
|
+
const tree = await page.locator("body").ariaSnapshot();
|
|
195
268
|
return {
|
|
196
269
|
consoleMessages,
|
|
197
270
|
status: response?.status() ?? 0,
|
|
271
|
+
tree,
|
|
198
272
|
url: page.url(),
|
|
199
273
|
...extraction
|
|
200
274
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as CaptureScope } from "./match.js";
|
|
2
|
-
import {
|
|
2
|
+
import { I as textEquals, M as placeholderPatternSource, k as hasPlaceholders } from "./artifacts.js";
|
|
3
3
|
//#region src/specification/contracts/contract.ts
|
|
4
4
|
/**
|
|
5
5
|
* Declare one contract. Identity function — its value is the enforced shape
|
|
@@ -86,6 +86,35 @@ function contractsOf(input) {
|
|
|
86
86
|
return flatten([input]);
|
|
87
87
|
}
|
|
88
88
|
//#endregion
|
|
89
|
+
//#region src/specification/contracts/types.ts
|
|
90
|
+
/**
|
|
91
|
+
* The marker that tells a STREAMED body from a value that happens to be an
|
|
92
|
+
* object. A body reaches three different engines (msw's server, msw's worker,
|
|
93
|
+
* the `node:http` stub) and each of them serialises "an object" as JSON, so a
|
|
94
|
+
* stream declared as a plain value arrives as `{}`. The tag is what each
|
|
95
|
+
* engine recognises before it reaches for `JSON.stringify`.
|
|
96
|
+
*/
|
|
97
|
+
const STREAM_BODY = "@jterrazz/test:stream";
|
|
98
|
+
/** Is this body a declared stream rather than a value to serialise? */
|
|
99
|
+
function isStreamBody(body) {
|
|
100
|
+
return typeof body === "object" && body !== null && "kind" in body && body.kind === "@jterrazz/test:stream";
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/specification/timing/pause.ts
|
|
104
|
+
/**
|
|
105
|
+
* One answer to "wait" — the only `setTimeout` the model owns.
|
|
106
|
+
*
|
|
107
|
+
* Two things in the framework genuinely have to wait: the spacing between the
|
|
108
|
+
* pieces of a declared stream, and the poll behind `waitUntil`. Everything
|
|
109
|
+
* else that waits is a spec doing it by hand, which is what J2 refuses. Stated
|
|
110
|
+
* once so there is one place to look when time stops behaving.
|
|
111
|
+
*/
|
|
112
|
+
async function pause(ms) {
|
|
113
|
+
await new Promise((resolve) => {
|
|
114
|
+
setTimeout(resolve, ms);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
89
118
|
//#region src/specification/contracts/queue.ts
|
|
90
119
|
/**
|
|
91
120
|
* The ONE contract queue — pure, engine-agnostic (no MSW, no node:http).
|
|
@@ -259,4 +288,34 @@ var ContractQueue = class {
|
|
|
259
288
|
}
|
|
260
289
|
};
|
|
261
290
|
//#endregion
|
|
262
|
-
|
|
291
|
+
//#region src/specification/contracts/stream.ts
|
|
292
|
+
/**
|
|
293
|
+
* A declared stream, made real — ONE serialisation for all three engines.
|
|
294
|
+
*
|
|
295
|
+
* msw's server and msw's worker take a `ReadableStream` body verbatim
|
|
296
|
+
* (https://mswjs.io/docs/http/mocking-responses/streaming) and the `node:http`
|
|
297
|
+
* stub pipes one into its response, so what "arriving in pieces" MEANS —
|
|
298
|
+
* the order, the spacing, the close after the last chunk — is stated here and
|
|
299
|
+
* nowhere else. A second copy of it would be a second answer to "what does the
|
|
300
|
+
* subject read", and the facets would drift.
|
|
301
|
+
*/
|
|
302
|
+
/**
|
|
303
|
+
* The pieces of `body`, in order, `delayBetweenChunks` milliseconds apart,
|
|
304
|
+
* closing after the last one.
|
|
305
|
+
*/
|
|
306
|
+
function toReadableStream(body) {
|
|
307
|
+
const encoder = new TextEncoder();
|
|
308
|
+
let index = 0;
|
|
309
|
+
return new ReadableStream({ async pull(controller) {
|
|
310
|
+
const chunk = body.chunks[index];
|
|
311
|
+
if (chunk === void 0) {
|
|
312
|
+
controller.close();
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (body.delayBetweenChunks > 0 && index > 0) await pause(body.delayBetweenChunks);
|
|
316
|
+
index += 1;
|
|
317
|
+
controller.enqueue(encoder.encode(chunk));
|
|
318
|
+
} });
|
|
319
|
+
}
|
|
320
|
+
//#endregion
|
|
321
|
+
export { isStreamBody as a, defineContracts as c, STREAM_BODY as i, isContract as l, ContractQueue as n, contractsOf as o, pause as r, defineContract as s, toReadableStream as t, isContracts as u };
|
package/dist/vitest.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ViteUserConfig } from "vitest/config";
|
|
1
|
+
import { TestProjectInlineConfiguration, ViteUserConfig } from "vitest/config";
|
|
2
|
+
import { UserConfig, UserConfigFn } from "vite";
|
|
2
3
|
//#region src/vitest/literate-plugin.d.ts
|
|
3
4
|
type LiterateOptions = {
|
|
4
5
|
/**
|
|
@@ -57,4 +58,131 @@ type SpecConfig = {
|
|
|
57
58
|
} & ViteUserConfig;
|
|
58
59
|
declare function defineSpecConfig(config?: SpecConfig): ViteUserConfig;
|
|
59
60
|
//#endregion
|
|
60
|
-
|
|
61
|
+
//#region src/vitest/projects.d.ts
|
|
62
|
+
/**
|
|
63
|
+
* The project helpers — one per kind of test, each the canonical project that
|
|
64
|
+
* kind runs in.
|
|
65
|
+
*
|
|
66
|
+
* A project is not a matter of taste: `--project component` has to mean the
|
|
67
|
+
* same thing in every repository of the ecosystem, and what a browser project
|
|
68
|
+
* needs (a provider pinned to the runner, a service worker, a Vite pipeline,
|
|
69
|
+
* an artefact directory that is not the repository root) is the framework's to
|
|
70
|
+
* know, not the consumer's to rediscover. So the consumer names the kind and
|
|
71
|
+
* states only what is theirs.
|
|
72
|
+
*/
|
|
73
|
+
/** What every helper accepts on top of its canonical project. */
|
|
74
|
+
type FacetProjectOptions = {
|
|
75
|
+
/**
|
|
76
|
+
* Globs this project stays out of. On a facet helper they ADD to the
|
|
77
|
+
* preset's (`_fixtures/` and vitest's own), because vite concatenates. On
|
|
78
|
+
* `unit()` and `component()`, whose canonical `exclude` exists only to
|
|
79
|
+
* guard their canonical `include`, stating one replaces it — so state
|
|
80
|
+
* both or neither there.
|
|
81
|
+
*/
|
|
82
|
+
exclude?: string[];
|
|
83
|
+
/** Replace the canonical `include` globs. */
|
|
84
|
+
include?: string[];
|
|
85
|
+
/**
|
|
86
|
+
* Run this project's files ONE AT A TIME (`fileParallelism: false`) — for
|
|
87
|
+
* the facets whose files share something a second worker would collide
|
|
88
|
+
* with: one served app, one database file, one browser profile.
|
|
89
|
+
*/
|
|
90
|
+
serial?: boolean;
|
|
91
|
+
/** Raise (or lower) the 30s budget for this project alone. */
|
|
92
|
+
timeout?: number;
|
|
93
|
+
};
|
|
94
|
+
/** `unit()` — module tests, beside the modules they cover (CONVENTIONS I2). */
|
|
95
|
+
type UnitProjectOptions = FacetProjectOptions & {
|
|
96
|
+
/**
|
|
97
|
+
* The trees module tests live in, when they do not live in `src/`. Each
|
|
98
|
+
* root becomes `<root>/**\/*.test.ts`; `include` states the globs outright.
|
|
99
|
+
*/
|
|
100
|
+
roots?: string[];
|
|
101
|
+
};
|
|
102
|
+
/** `website()` — the assembled product, met through a served page. */
|
|
103
|
+
type WebsiteProjectOptions = FacetProjectOptions;
|
|
104
|
+
/** `component()` — rendered units, beside the components they cover. */
|
|
105
|
+
type ComponentProjectOptions = {
|
|
106
|
+
/** Freeze the page's `Date` for every render of the project. */
|
|
107
|
+
clock?: string;
|
|
108
|
+
/** `Accept-Language` and `Intl` locale of the page. Default `'en-US'`. */
|
|
109
|
+
locale?: string;
|
|
110
|
+
/**
|
|
111
|
+
* The directory `vite` and `wrap` are resolved against. Default: the
|
|
112
|
+
* directory of the file that CALLED the helper — see
|
|
113
|
+
* {@link callerDirectory}. State it when the call is not written in the
|
|
114
|
+
* config it configures (a shared config factory).
|
|
115
|
+
*/
|
|
116
|
+
root?: string;
|
|
117
|
+
/** IANA zone the page's `Date` reports in. Default `'UTC'`. */
|
|
118
|
+
timezone?: string;
|
|
119
|
+
/**
|
|
120
|
+
* The app's own Vite pipeline: a config path, the object, or the function
|
|
121
|
+
* a framework exports. Only the PIPELINE is adopted (see {@link reroot}).
|
|
122
|
+
*/
|
|
123
|
+
vite?: Promise<UserConfig> | string | UserConfig | UserConfigFn;
|
|
124
|
+
/** The page size every render gets. Default 1280×720. */
|
|
125
|
+
viewport?: {
|
|
126
|
+
height: number;
|
|
127
|
+
width: number;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* A module whose default export is `(ui) => ReactNode` — the providers
|
|
131
|
+
* every render of this project is dressed in (a theme, a store, an intl
|
|
132
|
+
* provider). A router belongs on the chain instead: it is a test's Given.
|
|
133
|
+
*/
|
|
134
|
+
wrap?: string;
|
|
135
|
+
} & 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
|
+
* A component test renders in a real Chromium — the browser the website facet
|
|
152
|
+
* already drives through the same `playwright` peer.
|
|
153
|
+
*
|
|
154
|
+
* The default `include` reaches the whole project, so the default `exclude`
|
|
155
|
+
* keeps it out of `specs/`, where the assembled product is specified. State
|
|
156
|
+
* your own `include` and the exclusion is yours to state too.
|
|
157
|
+
*/
|
|
158
|
+
declare function component(options?: ComponentProjectOptions): Promise<TestProjectInlineConfiguration>;
|
|
159
|
+
/** `api()` — the app met through HTTP. */
|
|
160
|
+
declare function api(options?: FacetProjectOptions): TestProjectInlineConfiguration;
|
|
161
|
+
/** `jobs()` — what a name triggers in-process. */
|
|
162
|
+
declare function jobs(options?: FacetProjectOptions): TestProjectInlineConfiguration;
|
|
163
|
+
/** `integration()` — a module against real services, or against a golden. */
|
|
164
|
+
declare function integration(options?: FacetProjectOptions): TestProjectInlineConfiguration;
|
|
165
|
+
/**
|
|
166
|
+
* `mobile()` — the app on a simulator. A simulator is the one thing that
|
|
167
|
+
* cannot share a machine with itself, so it runs last and alone (group 3).
|
|
168
|
+
*/
|
|
169
|
+
declare function mobile(options?: FacetProjectOptions): TestProjectInlineConfiguration;
|
|
170
|
+
/** `cli()` — a binary, and the documents that specify it. */
|
|
171
|
+
type CliProjectOptions = FacetProjectOptions & {
|
|
172
|
+
/**
|
|
173
|
+
* The literate door, wired by default: every `specs/cli/**\/*.spec.yaml`
|
|
174
|
+
* becomes a test file bound to `specs/cli/cli.specification.ts`. State
|
|
175
|
+
* `specification` to name another runner, `include` to narrow the glob, or
|
|
176
|
+
* `false` to collect no documents at all.
|
|
177
|
+
*/
|
|
178
|
+
literate?: false | Partial<LiterateOptions>;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* A cli project collects the facet's `.test.ts` files AND its documents: a
|
|
182
|
+
* `<case>.spec.yaml` is a test file of its own (docs/07 § the literate door),
|
|
183
|
+
* so the plugin's glob has to join the include of the project that collects
|
|
184
|
+
* them — which is this one, never the root config.
|
|
185
|
+
*/
|
|
186
|
+
declare function cli(options?: CliProjectOptions): TestProjectInlineConfiguration;
|
|
187
|
+
//#endregion
|
|
188
|
+
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 };
|