as-test 1.5.1 → 1.6.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/CHANGELOG.md +32 -0
- package/as-test.config.schema.json +40 -0
- package/bin/build-cache.js +278 -0
- package/bin/commands/build-core.js +84 -78
- package/bin/commands/clean-core.js +4 -0
- package/bin/commands/run-core.js +186 -66
- package/bin/commands/test.js +2 -0
- package/bin/index.js +257 -92
- package/bin/reporters/default.js +56 -5
- package/bin/selectors.js +208 -0
- package/bin/types.js +18 -0
- package/bin/util.js +94 -0
- package/package.json +2 -2
- package/transform/lib/index.js +2 -2
package/bin/selectors.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
// Selector resolution runs in several places per command (the orchestrator,
|
|
5
|
+
// then build/run cores per file); dedupe by text so a warning prints once per
|
|
6
|
+
// process regardless of how many resolvers see the same selector.
|
|
7
|
+
const reportedSelectorWarnings = new Set();
|
|
8
|
+
export function emitSelectorWarnings(warnings) {
|
|
9
|
+
for (const warning of warnings) {
|
|
10
|
+
if (reportedSelectorWarnings.has(warning)) continue;
|
|
11
|
+
reportedSelectorWarnings.add(warning);
|
|
12
|
+
process.stderr.write(`${chalk.yellow.bold("WARN")} ${warning}\n`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const GLOB_MAGIC = /[*?[\]{}]/;
|
|
16
|
+
function hasGlobMagic(selector) {
|
|
17
|
+
return GLOB_MAGIC.test(selector);
|
|
18
|
+
}
|
|
19
|
+
function endsWithSlash(selector) {
|
|
20
|
+
return /[\\/]$/.test(selector);
|
|
21
|
+
}
|
|
22
|
+
function stripTrailingSlash(selector) {
|
|
23
|
+
return selector.replace(/[\\/]+$/, "");
|
|
24
|
+
}
|
|
25
|
+
function stripSuiteSuffix(selector) {
|
|
26
|
+
return selector.replace(/\.spec\.ts$/, "").replace(/\.ts$/, "");
|
|
27
|
+
}
|
|
28
|
+
function isCwdRelative(selector) {
|
|
29
|
+
return (
|
|
30
|
+
selector.startsWith("./") ||
|
|
31
|
+
selector.startsWith("../") ||
|
|
32
|
+
selector.startsWith(".\\") ||
|
|
33
|
+
selector.startsWith("..\\") ||
|
|
34
|
+
selector.startsWith("/") ||
|
|
35
|
+
selector.startsWith("~") ||
|
|
36
|
+
path.isAbsolute(selector)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
// A selector with a path separator that is not merely a single trailing slash
|
|
40
|
+
// (e.g. `assembly/__tests__/foo.spec.ts`, passed verbatim by the orchestrator)
|
|
41
|
+
// is treated as a direct cwd-relative path rather than a test-folder alias.
|
|
42
|
+
function hasInternalSlash(selector) {
|
|
43
|
+
return /[\\/]/.test(stripTrailingSlash(selector));
|
|
44
|
+
}
|
|
45
|
+
// The longest leading run of path segments containing no glob magic — the
|
|
46
|
+
// static "test folder" of an input pattern (`assembly/__tests__/**/*.spec.ts`
|
|
47
|
+
// -> `assembly/__tests__`).
|
|
48
|
+
function globBase(pattern) {
|
|
49
|
+
const segments = pattern.split("/");
|
|
50
|
+
const base = [];
|
|
51
|
+
for (const segment of segments) {
|
|
52
|
+
if (hasGlobMagic(segment)) break;
|
|
53
|
+
base.push(segment);
|
|
54
|
+
}
|
|
55
|
+
return base.join("/") || ".";
|
|
56
|
+
}
|
|
57
|
+
function uniqueInputRoots(configuredInputs) {
|
|
58
|
+
const roots = new Set();
|
|
59
|
+
for (const pattern of configuredInputs) {
|
|
60
|
+
if (pattern.startsWith("!")) continue;
|
|
61
|
+
roots.add(globBase(pattern));
|
|
62
|
+
}
|
|
63
|
+
return [...roots];
|
|
64
|
+
}
|
|
65
|
+
// Turn a cwd-relative selector into the spec glob(s) it stands for.
|
|
66
|
+
function cwdPatterns(selector) {
|
|
67
|
+
if (endsWithSlash(selector)) {
|
|
68
|
+
return [`${stripTrailingSlash(selector)}/**/*.spec.ts`];
|
|
69
|
+
}
|
|
70
|
+
if (/\.ts$/.test(selector)) return [selector];
|
|
71
|
+
return [`${stripSuiteSuffix(selector)}.spec.ts`];
|
|
72
|
+
}
|
|
73
|
+
// Turn a bare selector into the spec glob(s) it stands for, anchored to a
|
|
74
|
+
// configured input root and searched recursively beneath it. A selector that
|
|
75
|
+
// already carries glob magic (`rfc/*.spec.ts`, `*.spec.ts`) is appended
|
|
76
|
+
// verbatim so the user's pattern controls the match; a plain folder/name has
|
|
77
|
+
// the spec suffix supplied.
|
|
78
|
+
function barePatterns(root, selector) {
|
|
79
|
+
if (hasGlobMagic(selector)) {
|
|
80
|
+
return [`${root}/**/${selector}`];
|
|
81
|
+
}
|
|
82
|
+
if (endsWithSlash(selector)) {
|
|
83
|
+
return [`${root}/**/${stripTrailingSlash(selector)}/**/*.spec.ts`];
|
|
84
|
+
}
|
|
85
|
+
return [`${root}/**/${stripSuiteSuffix(selector)}.spec.ts`];
|
|
86
|
+
}
|
|
87
|
+
// Split comma-joined bare selectors (`a,b,c`) while leaving paths and globs
|
|
88
|
+
// (which can legitimately contain commas, e.g. `{a,b}`) intact.
|
|
89
|
+
function expandSelectors(selectors) {
|
|
90
|
+
const expanded = [];
|
|
91
|
+
for (const selector of selectors) {
|
|
92
|
+
if (!selector) continue;
|
|
93
|
+
if (
|
|
94
|
+
selector.includes(",") &&
|
|
95
|
+
!hasInternalSlash(selector) &&
|
|
96
|
+
!endsWithSlash(selector) &&
|
|
97
|
+
!hasGlobMagic(selector)
|
|
98
|
+
) {
|
|
99
|
+
for (const token of selector.split(",")) {
|
|
100
|
+
const trimmed = token.trim();
|
|
101
|
+
if (trimmed.length) expanded.push(trimmed);
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
expanded.push(selector);
|
|
106
|
+
}
|
|
107
|
+
return expanded;
|
|
108
|
+
}
|
|
109
|
+
async function globFiles(patterns) {
|
|
110
|
+
return glob(patterns);
|
|
111
|
+
}
|
|
112
|
+
async function resolveSelector(selector, inputRoots) {
|
|
113
|
+
const warnings = [];
|
|
114
|
+
const isGlob = hasGlobMagic(selector);
|
|
115
|
+
// Explicit cwd-relative selector (`./`, `../`, absolute, `~`) — resolve from
|
|
116
|
+
// the cwd only. A glob is matched verbatim; a plain path gets the spec suffix.
|
|
117
|
+
if (isCwdRelative(selector)) {
|
|
118
|
+
const files = await globFiles(isGlob ? [selector] : cwdPatterns(selector));
|
|
119
|
+
if (!files.length) {
|
|
120
|
+
const bare = selector.replace(/^\.[\\/]/, "");
|
|
121
|
+
let suggestion = null;
|
|
122
|
+
for (const root of inputRoots) {
|
|
123
|
+
const inRoot = await globFiles(barePatterns(root, bare));
|
|
124
|
+
if (inRoot.length) {
|
|
125
|
+
suggestion = bare;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
warnings.push(
|
|
130
|
+
suggestion
|
|
131
|
+
? `"${selector}" not found relative to the current directory — did you mean "${suggestion}" (searches the configured test folder)?`
|
|
132
|
+
: `"${selector}" not found relative to the current directory`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return { files, warnings };
|
|
136
|
+
}
|
|
137
|
+
// A plain path with an internal separator (e.g. the orchestrator's own
|
|
138
|
+
// `assembly/__tests__/foo.spec.ts`) resolves from the cwd verbatim. Globs
|
|
139
|
+
// skip this and fall through to test-folder anchoring below.
|
|
140
|
+
if (!isGlob && hasInternalSlash(selector)) {
|
|
141
|
+
const direct = await globFiles(cwdPatterns(selector));
|
|
142
|
+
if (direct.length) return { files: direct, warnings };
|
|
143
|
+
// Fall through to test-folder resolution for user shorthands like
|
|
144
|
+
// `nested/array` that aren't a real cwd path.
|
|
145
|
+
}
|
|
146
|
+
// Bare name/folder or relative glob — configured input root(s) first.
|
|
147
|
+
const perRoot = [];
|
|
148
|
+
for (const root of inputRoots) {
|
|
149
|
+
const files = await globFiles(barePatterns(root, selector));
|
|
150
|
+
if (files.length) perRoot.push({ root, files });
|
|
151
|
+
}
|
|
152
|
+
if (perRoot.length) {
|
|
153
|
+
if (perRoot.length > 1) {
|
|
154
|
+
warnings.push(
|
|
155
|
+
`selector "${selector}" matched specs under ${perRoot.length} input roots (${perRoot
|
|
156
|
+
.map((entry) => entry.root)
|
|
157
|
+
.join(", ")}) — running all of them`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return { files: perRoot.flatMap((entry) => entry.files), warnings };
|
|
161
|
+
}
|
|
162
|
+
// Fall back to the cwd before giving up.
|
|
163
|
+
const cwdFiles = await globFiles(
|
|
164
|
+
isGlob ? [selector] : cwdPatterns(`./${selector}`),
|
|
165
|
+
);
|
|
166
|
+
if (cwdFiles.length) {
|
|
167
|
+
return { files: cwdFiles, warnings };
|
|
168
|
+
}
|
|
169
|
+
warnings.push(
|
|
170
|
+
inputRoots.length
|
|
171
|
+
? `no spec files matched "${selector}" in ${inputRoots.join(", ")} or the current directory`
|
|
172
|
+
: `no spec files matched "${selector}"`,
|
|
173
|
+
);
|
|
174
|
+
return { files: [], warnings };
|
|
175
|
+
}
|
|
176
|
+
// Resolve configured input patterns + positional selectors into the concrete
|
|
177
|
+
// set of spec files to act on, along with any human-readable warnings. With no
|
|
178
|
+
// selectors this is just the configured globs (honoring `!`-negations); with
|
|
179
|
+
// selectors the per-selector rules above apply and config negations are
|
|
180
|
+
// intentionally bypassed so an explicit pick always wins.
|
|
181
|
+
export async function resolveSpecFiles(configured, selectors) {
|
|
182
|
+
const configuredInputs = Array.isArray(configured)
|
|
183
|
+
? configured
|
|
184
|
+
: [configured];
|
|
185
|
+
if (!selectors.length) {
|
|
186
|
+
const include = configuredInputs.filter((p) => !p.startsWith("!"));
|
|
187
|
+
const ignore = configuredInputs
|
|
188
|
+
.filter((p) => p.startsWith("!"))
|
|
189
|
+
.map((p) => p.slice(1));
|
|
190
|
+
const files = (await glob(include, { ignore })).sort((a, b) =>
|
|
191
|
+
a.localeCompare(b),
|
|
192
|
+
);
|
|
193
|
+
return { files, warnings: [] };
|
|
194
|
+
}
|
|
195
|
+
const inputRoots = uniqueInputRoots(configuredInputs);
|
|
196
|
+
const files = new Set();
|
|
197
|
+
const warnings = [];
|
|
198
|
+
for (const selector of expandSelectors(selectors)) {
|
|
199
|
+
if (!selector) continue;
|
|
200
|
+
const resolved = await resolveSelector(selector, inputRoots);
|
|
201
|
+
for (const file of resolved.files) files.add(file);
|
|
202
|
+
warnings.push(...resolved.warnings);
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
files: [...files].sort((a, b) => a.localeCompare(b)),
|
|
206
|
+
warnings,
|
|
207
|
+
};
|
|
208
|
+
}
|
package/bin/types.js
CHANGED
|
@@ -7,6 +7,13 @@ export class Config {
|
|
|
7
7
|
this.coverageDir = "./.as-test/coverage";
|
|
8
8
|
this.snapshotDir = "./.as-test/snapshots";
|
|
9
9
|
this.config = "none";
|
|
10
|
+
// Incremental test cache (opt-in). false = off; "build" = skip recompiling
|
|
11
|
+
// unchanged specs; "full"/true = also replay passing run results. The object
|
|
12
|
+
// form adds `maxTime` (e.g. "1h", "30m", "7d") to expire entries older than
|
|
13
|
+
// that, forcing a rebuild+rerun. ("reachable" is accepted as a deprecated
|
|
14
|
+
// alias for "full" — reachability-based dep pruning was unsound for
|
|
15
|
+
// AssemblyScript's compile-time inlining.) Enable per run with --cache / --no-cache.
|
|
16
|
+
this.cache = false;
|
|
10
17
|
this.coverage = false;
|
|
11
18
|
this.features = [];
|
|
12
19
|
this.env = {};
|
|
@@ -16,6 +23,17 @@ export class Config {
|
|
|
16
23
|
this.modes = {};
|
|
17
24
|
}
|
|
18
25
|
}
|
|
26
|
+
export class CacheOptions {
|
|
27
|
+
constructor() {
|
|
28
|
+
// Cache tier: "build" (skip rebuilds) or "full" (also replay runs).
|
|
29
|
+
// "reachable" is accepted as a deprecated alias for "full".
|
|
30
|
+
this.type = "full";
|
|
31
|
+
// Optional expiry: a duration string (e.g. "1h", "30m", "90s", "7d"). Entries
|
|
32
|
+
// built longer ago than this are treated as stale and rebuilt+rerun. Empty =
|
|
33
|
+
// no expiry.
|
|
34
|
+
this.maxTime = "";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
19
37
|
export const INTERNAL_FEATURE_NAMES = new Set(["try-as"]);
|
|
20
38
|
export function normalizeFeatureName(value) {
|
|
21
39
|
const trimmed = value.trim().toLowerCase();
|
package/bin/util.js
CHANGED
|
@@ -217,6 +217,7 @@ const TOP_LEVEL_KEYS = new Set([
|
|
|
217
217
|
"coverageDir",
|
|
218
218
|
"snapshotDir",
|
|
219
219
|
"config",
|
|
220
|
+
"cache",
|
|
220
221
|
"coverage",
|
|
221
222
|
"features",
|
|
222
223
|
"env",
|
|
@@ -253,6 +254,7 @@ function validateConfig(raw, configPath) {
|
|
|
253
254
|
validateStringField(raw, "coverageDir", "$", issues);
|
|
254
255
|
validateStringField(raw, "snapshotDir", "$", issues);
|
|
255
256
|
validateStringField(raw, "config", "$", issues);
|
|
257
|
+
validateCacheField(raw, "cache", "$", issues);
|
|
256
258
|
validateCoverageField(raw, "coverage", "$", issues);
|
|
257
259
|
validateFeaturesField(raw, "features", "$", issues);
|
|
258
260
|
validateEnvField(raw, "env", "$", issues);
|
|
@@ -380,6 +382,46 @@ function validateCoverageField(raw, key, pathPrefix, issues) {
|
|
|
380
382
|
if (!(key in raw) || raw[key] == undefined) return;
|
|
381
383
|
validateCoverageValue(raw[key], `${pathPrefix}.${key}`, issues);
|
|
382
384
|
}
|
|
385
|
+
function validateCacheField(raw, key, pathPrefix, issues) {
|
|
386
|
+
if (!(key in raw) || raw[key] == undefined) return;
|
|
387
|
+
const value = raw[key];
|
|
388
|
+
const path = `${pathPrefix}.${key}`;
|
|
389
|
+
if (typeof value == "boolean") return;
|
|
390
|
+
if (value === "build" || value === "full" || value === "reachable") return;
|
|
391
|
+
if (value && typeof value == "object" && !Array.isArray(value)) {
|
|
392
|
+
const obj = value;
|
|
393
|
+
if (
|
|
394
|
+
"type" in obj &&
|
|
395
|
+
obj.type !== "build" &&
|
|
396
|
+
obj.type !== "full" &&
|
|
397
|
+
obj.type !== "reachable"
|
|
398
|
+
) {
|
|
399
|
+
issues.push({
|
|
400
|
+
path: `${path}.type`,
|
|
401
|
+
message: 'must be "build" or "full"',
|
|
402
|
+
fix: 'set "type" to "build" or "full"',
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
if ("maxTime" in obj && obj.maxTime != undefined) {
|
|
406
|
+
if (
|
|
407
|
+
typeof obj.maxTime != "string" ||
|
|
408
|
+
parseDurationMs(obj.maxTime) == null
|
|
409
|
+
) {
|
|
410
|
+
issues.push({
|
|
411
|
+
path: `${path}.maxTime`,
|
|
412
|
+
message: 'must be a duration like "1h", "30m", "90s", or "7d"',
|
|
413
|
+
fix: 'use a number followed by ms/s/m/h/d, e.g. "1h"',
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
issues.push({
|
|
420
|
+
path,
|
|
421
|
+
message: 'must be a boolean, "build"/"full", or { type, maxTime }',
|
|
422
|
+
fix: 'use false, true, "build", "full", or { "type": "full", "maxTime": "1h" }',
|
|
423
|
+
});
|
|
424
|
+
}
|
|
383
425
|
function validateCoverageValue(value, path, issues) {
|
|
384
426
|
if (typeof value == "boolean") return;
|
|
385
427
|
if (!value || typeof value != "object" || Array.isArray(value)) {
|
|
@@ -758,6 +800,7 @@ function validateModesField(raw, key, pathPrefix, issues) {
|
|
|
758
800
|
validateStringField(modeObj, "coverageDir", modePath, issues);
|
|
759
801
|
validateStringField(modeObj, "snapshotDir", modePath, issues);
|
|
760
802
|
validateStringField(modeObj, "config", modePath, issues);
|
|
803
|
+
validateCacheField(modeObj, "cache", modePath, issues);
|
|
761
804
|
validateCoverageField(modeObj, "coverage", modePath, issues);
|
|
762
805
|
validateFeaturesField(modeObj, "features", modePath, issues);
|
|
763
806
|
validateFuzzField(modeObj, "fuzz", modePath, issues);
|
|
@@ -1244,6 +1287,7 @@ function mergeRootConfig(base, override) {
|
|
|
1244
1287
|
merged.snapshotDir = override.snapshotDir;
|
|
1245
1288
|
}
|
|
1246
1289
|
if ("config" in raw) merged.config = override.config;
|
|
1290
|
+
if ("cache" in raw) merged.cache = override.cache;
|
|
1247
1291
|
if ("coverage" in raw) {
|
|
1248
1292
|
merged.coverage = mergeCoverageConfig(
|
|
1249
1293
|
merged.coverage,
|
|
@@ -1392,6 +1436,56 @@ export function applyMode(config, modeName) {
|
|
|
1392
1436
|
function appendPathSegment(basePath, segment) {
|
|
1393
1437
|
return join(basePath, segment);
|
|
1394
1438
|
}
|
|
1439
|
+
// Parses a duration string ("500ms", "90s", "30m", "1h", "7d", "1.5h") to
|
|
1440
|
+
// milliseconds. Returns null when the format is unrecognized.
|
|
1441
|
+
export function parseDurationMs(value) {
|
|
1442
|
+
const match = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)\s*$/.exec(value);
|
|
1443
|
+
if (!match) return null;
|
|
1444
|
+
const amount = Number(match[1]);
|
|
1445
|
+
const unit = match[2];
|
|
1446
|
+
const mult =
|
|
1447
|
+
unit === "ms"
|
|
1448
|
+
? 1
|
|
1449
|
+
: unit === "s"
|
|
1450
|
+
? 1000
|
|
1451
|
+
: unit === "m"
|
|
1452
|
+
? 60000
|
|
1453
|
+
: unit === "h"
|
|
1454
|
+
? 3600000
|
|
1455
|
+
: 86400000;
|
|
1456
|
+
return amount * mult;
|
|
1457
|
+
}
|
|
1458
|
+
// Resolves the effective cache mode + expiry for a run. CLI flags win over
|
|
1459
|
+
// config: --no-cache forces off, --cache forces full. The config value may be a
|
|
1460
|
+
// boolean, a mode string, or an object { type, maxTime }. Default is off
|
|
1461
|
+
// (opt-in). maxTime (entry expiry) comes from the object form regardless of the
|
|
1462
|
+
// resolved mode.
|
|
1463
|
+
export function resolveCacheSettings(configCache, flags) {
|
|
1464
|
+
if (flags.noCache) return { mode: "off", maxTimeMs: null };
|
|
1465
|
+
// "reachable" is accepted for back-compat but treated as "full": reachability
|
|
1466
|
+
// pruning was unsound for AssemblyScript (compile-time-inlined consts, static
|
|
1467
|
+
// fields, @inline bodies, and re-export barrels can change output without
|
|
1468
|
+
// being "reachable"), so it could serve stale results. The full dependency
|
|
1469
|
+
// set is always correct.
|
|
1470
|
+
let mode = "off";
|
|
1471
|
+
let maxTimeMs = null;
|
|
1472
|
+
if (configCache && typeof configCache === "object") {
|
|
1473
|
+
mode = configCache.type === "build" ? "build" : "full";
|
|
1474
|
+
maxTimeMs = configCache.maxTime
|
|
1475
|
+
? parseDurationMs(configCache.maxTime)
|
|
1476
|
+
: null;
|
|
1477
|
+
} else if (
|
|
1478
|
+
configCache === true ||
|
|
1479
|
+
configCache === "full" ||
|
|
1480
|
+
configCache === "reachable"
|
|
1481
|
+
) {
|
|
1482
|
+
mode = "full";
|
|
1483
|
+
} else if (configCache === "build") {
|
|
1484
|
+
mode = "build";
|
|
1485
|
+
}
|
|
1486
|
+
if (flags.cache) mode = "full"; // --cache forces full; maxTime (if any) kept
|
|
1487
|
+
return { mode, maxTimeMs };
|
|
1488
|
+
}
|
|
1395
1489
|
export function getCliVersion() {
|
|
1396
1490
|
const candidates = [
|
|
1397
1491
|
join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "as-test",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"author": "Jairus Tanaka",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"husky": "^9.1.7",
|
|
25
25
|
"playwright": "^1.60.0",
|
|
26
26
|
"prettier": "3.8.3",
|
|
27
|
-
"try-as": "
|
|
27
|
+
"try-as": "1.1.0",
|
|
28
28
|
"typescript": "^6.0.3",
|
|
29
29
|
"typescript-eslint": "^8.59.4",
|
|
30
30
|
"vitepress": "^1.6.4"
|
package/transform/lib/index.js
CHANGED
|
@@ -140,7 +140,7 @@ function analyzeSourceText(sourceText) {
|
|
|
140
140
|
? new RegExp(`\\b${escapeRegex(runAlias)}\\s*\\(`).test(text)
|
|
141
141
|
: false;
|
|
142
142
|
return {
|
|
143
|
-
hasSuiteCalls: /\b(?:describe|test|it|only|
|
|
143
|
+
hasSuiteCalls: /\b(?:x?describe|x?test|x?it|x?only|todo|x?fuzz)\s*\(/.test(text),
|
|
144
144
|
hasRunCall,
|
|
145
145
|
runImportPath,
|
|
146
146
|
hasMockCalls: /\b(?:mockFn|unmockFn|mockImport|unmockImport)\s*\(/.test(text),
|
|
@@ -174,7 +174,7 @@ function detectRunAlias(text) {
|
|
|
174
174
|
return null;
|
|
175
175
|
}
|
|
176
176
|
function looksLikeAsTestImport(specifiers) {
|
|
177
|
-
return /\b(?:describe|test|it|only|
|
|
177
|
+
return /\b(?:x?describe|x?test|x?it|x?only|todo|x?fuzz|expect|beforeAll|afterAll|beforeEach|afterEach|mockFn|unmockFn|mockImport|unmockImport|snapshotFn|log|run)\b/.test(specifiers);
|
|
178
178
|
}
|
|
179
179
|
function stripComments(sourceText) {
|
|
180
180
|
return sourceText.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|