@savvy-web/vitest 0.3.0 → 1.0.1
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 +67 -117
- package/index.d.ts +236 -93
- package/index.js +316 -45
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
# @savvy-web/vitest
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@savvy-web/vitest)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Automatic Vitest project configuration discovery for pnpm monorepo
|
|
7
|
+
workspaces. Scans workspace packages, classifies test files as unit,
|
|
8
|
+
e2e, or integration by filename convention, and generates multi-project
|
|
9
|
+
Vitest configs with coverage thresholds, `vitest-agent-reporter`
|
|
10
|
+
integration, and CI-aware reporters.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- Zero-config workspace discovery with automatic test classification
|
|
15
|
+
- Named coverage levels (`none`, `basic`, `standard`, `strict`, `full`)
|
|
16
|
+
- Per-kind and per-project override support with chainable mutation API
|
|
17
|
+
- Built-in `vitest-agent-reporter` integration for AI-assisted workflows
|
|
18
|
+
- CI-aware reporters with automatic `github-actions` reporter in CI
|
|
7
19
|
|
|
8
20
|
## Installation
|
|
9
21
|
|
|
@@ -11,140 +23,78 @@ automatically.
|
|
|
11
23
|
pnpm add @savvy-web/vitest
|
|
12
24
|
```
|
|
13
25
|
|
|
14
|
-
Peer dependencies: `vitest
|
|
26
|
+
Peer dependencies: `vitest` >=4.1.0, `@vitest/coverage-v8` >=4.1.0, `vitest-agent-reporter` >=0.2.0
|
|
15
27
|
|
|
16
28
|
## Quick Start
|
|
17
29
|
|
|
18
|
-
Create a single `vitest.config.ts` at your workspace root:
|
|
19
|
-
|
|
20
30
|
```typescript
|
|
21
31
|
import { VitestConfig } from "@savvy-web/vitest";
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
test: {
|
|
26
|
-
reporters,
|
|
27
|
-
projects: projects.map((p) => p.toConfig()),
|
|
28
|
-
coverage: { provider: "v8", ...coverage },
|
|
29
|
-
},
|
|
30
|
-
}),
|
|
31
|
-
);
|
|
33
|
+
// Zero config -- everything automatic
|
|
34
|
+
export default VitestConfig.create();
|
|
32
35
|
```
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
## Directory Structure
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
project-root/ # or monorepo leaf workspace
|
|
41
|
+
lib/ # Configs, scripts -- linted, typechecked, no tests
|
|
42
|
+
src/ # Module source -- may contain co-located tests
|
|
43
|
+
__test__/ # Dedicated test directory
|
|
44
|
+
utils/ # Shared test helpers (excluded from discovery)
|
|
45
|
+
fixtures/ # Test fixtures (excluded from lint/typecheck/discovery)
|
|
46
|
+
*.test.ts # Unit tests (no signifier)
|
|
47
|
+
*.unit.test.ts # Unit tests (explicit signifier)
|
|
48
|
+
unit/ # Optional unit subdirectory
|
|
49
|
+
utils/ # Excluded
|
|
50
|
+
fixtures/ # Excluded
|
|
51
|
+
e2e/
|
|
52
|
+
utils/ # Excluded
|
|
53
|
+
fixtures/ # Excluded
|
|
54
|
+
*.e2e.test.ts
|
|
55
|
+
integration/
|
|
56
|
+
utils/ # Excluded
|
|
57
|
+
fixtures/ # Excluded
|
|
58
|
+
*.int.test.ts
|
|
59
|
+
vitest.setup.ts # Optional -- auto-detected, added to all projects
|
|
60
|
+
```
|
|
38
61
|
|
|
39
|
-
|
|
62
|
+
## Examples
|
|
40
63
|
|
|
41
64
|
```typescript
|
|
42
|
-
|
|
65
|
+
// Named coverage level
|
|
66
|
+
export default VitestConfig.create({ coverage: "standard" });
|
|
43
67
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
68
|
+
// Per-kind overrides (object form)
|
|
69
|
+
export default VitestConfig.create({
|
|
70
|
+
unit: { environment: "jsdom" },
|
|
47
71
|
});
|
|
48
72
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
73
|
+
// Per-project overrides (callback form)
|
|
74
|
+
export default VitestConfig.create({
|
|
75
|
+
e2e: (projects) => {
|
|
76
|
+
projects.get("@savvy-web/auth:e2e")
|
|
77
|
+
?.override({ test: { testTimeout: 300_000 } })
|
|
78
|
+
.addCoverageExclude("src/generated/**");
|
|
54
79
|
},
|
|
55
80
|
});
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## API Reference
|
|
59
81
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
- **`isCI`** -- `true` when running in GitHub Actions
|
|
68
|
-
|
|
69
|
-
Returns whatever `ViteUserConfig` the callback produces (sync or async).
|
|
70
|
-
|
|
71
|
-
### `VitestProject.unit(options)` / `.e2e(options)` / `.custom(kind, options)`
|
|
72
|
-
|
|
73
|
-
Factory methods that create projects with preset defaults:
|
|
74
|
-
|
|
75
|
-
| Factory | Environment | Test Timeout | Hook Timeout | Max Concurrency |
|
|
76
|
-
| --- | --- | --- | --- | --- |
|
|
77
|
-
| `unit()` | `"node"` | vitest default | vitest default | vitest default |
|
|
78
|
-
| `e2e()` | `"node"` | 120 s | 60 s | `floor(cpus / 2)` clamped 1--8 |
|
|
79
|
-
| `custom(kind)` | none | none | none | none |
|
|
80
|
-
|
|
81
|
-
### `VitestProjectOptions`
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
interface VitestProjectOptions {
|
|
85
|
-
name: string;
|
|
86
|
-
include: string[];
|
|
87
|
-
kind?: VitestProjectKind;
|
|
88
|
-
overrides?: Partial<TestProjectInlineConfiguration>;
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
`name` and `include` always take precedence over values in `overrides`.
|
|
93
|
-
|
|
94
|
-
### `VitestConfigCreateOptions`
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
interface VitestConfigCreateOptions {
|
|
98
|
-
thresholds?: {
|
|
99
|
-
lines?: number; // default 80
|
|
100
|
-
functions?: number; // default 80
|
|
101
|
-
branches?: number; // default 80
|
|
102
|
-
statements?: number; // default 80
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### `CoverageConfig`
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
interface CoverageConfig {
|
|
111
|
-
include: string[]; // source globs (e.g., "pkgs/my-lib/src/**/*.ts")
|
|
112
|
-
exclude: string[]; // test file globs
|
|
113
|
-
thresholds: {
|
|
114
|
-
lines: number;
|
|
115
|
-
functions: number;
|
|
116
|
-
branches: number;
|
|
117
|
-
statements: number;
|
|
118
|
-
};
|
|
119
|
-
}
|
|
82
|
+
// Escape hatch for Vite-level config
|
|
83
|
+
export default VitestConfig.create(
|
|
84
|
+
{ coverage: "standard" },
|
|
85
|
+
(config) => {
|
|
86
|
+
config.resolve = { alias: { "@": "/src" } };
|
|
87
|
+
},
|
|
88
|
+
);
|
|
120
89
|
```
|
|
121
90
|
|
|
122
|
-
##
|
|
123
|
-
|
|
124
|
-
### Filename Patterns
|
|
125
|
-
|
|
126
|
-
| Pattern | Kind |
|
|
127
|
-
| --- | --- |
|
|
128
|
-
| `*.test.ts` / `*.spec.ts` | unit |
|
|
129
|
-
| `*.e2e.test.ts` / `*.e2e.spec.ts` | e2e |
|
|
130
|
-
|
|
131
|
-
### Directory Scanning
|
|
132
|
-
|
|
133
|
-
Each workspace package is scanned if it contains a `src/` directory. An
|
|
134
|
-
optional `__test__/` directory at the package root is also included when
|
|
135
|
-
present.
|
|
136
|
-
|
|
137
|
-
### Naming Suffixes
|
|
138
|
-
|
|
139
|
-
When a package has both unit and e2e test files, projects are automatically
|
|
140
|
-
suffixed with `:unit` and `:e2e` (e.g., `@savvy-web/my-lib:unit`). Packages
|
|
141
|
-
with only one kind use the bare package name.
|
|
91
|
+
## Documentation
|
|
142
92
|
|
|
143
|
-
|
|
93
|
+
For configuration, API reference, and advanced usage, see [docs/](./docs/).
|
|
144
94
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
95
|
+
- [API Reference](./docs/api.md) -- Complete reference for all exports
|
|
96
|
+
- [Test Discovery](./docs/discovery.md) -- Workspace scanning, test classification, and coverage scoping
|
|
97
|
+
- [Usage Guides](./docs/guides.md) -- Recipes for kind overrides, per-project mutation, coverage, agent reporter, and escape hatches
|
|
148
98
|
|
|
149
99
|
## License
|
|
150
100
|
|
package/index.d.ts
CHANGED
|
@@ -15,48 +15,70 @@
|
|
|
15
15
|
* ```typescript
|
|
16
16
|
* import { VitestConfig } from "@savvy-web/vitest";
|
|
17
17
|
*
|
|
18
|
-
* export default VitestConfig.create(
|
|
19
|
-
* ({ projects, coverage, reporters }) => ({
|
|
20
|
-
* test: {
|
|
21
|
-
* reporters,
|
|
22
|
-
* projects: projects.map((p) => p.toConfig()),
|
|
23
|
-
* coverage: { provider: "v8", ...coverage },
|
|
24
|
-
* },
|
|
25
|
-
* }),
|
|
26
|
-
* );
|
|
18
|
+
* export default VitestConfig.create();
|
|
27
19
|
* ```
|
|
28
20
|
*
|
|
29
21
|
* @packageDocumentation
|
|
30
22
|
*/
|
|
31
23
|
|
|
24
|
+
import type { AgentPluginOptions } from 'vitest-agent-reporter';
|
|
32
25
|
import { TestProjectInlineConfiguration } from 'vitest/config';
|
|
33
26
|
import type { ViteUserConfig } from 'vitest/config';
|
|
34
27
|
|
|
35
28
|
/**
|
|
36
|
-
*
|
|
29
|
+
* Pass-through configuration for `vitest-agent-reporter`'s `AgentPlugin`.
|
|
37
30
|
*
|
|
38
|
-
* @see {@link
|
|
31
|
+
* @see {@link VitestConfigOptions.agentReporter}
|
|
39
32
|
*
|
|
40
33
|
* @public
|
|
41
34
|
*/
|
|
42
|
-
export declare
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
35
|
+
export declare type AgentReporterConfig = AgentPluginOptions;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Named coverage level presets available on {@link VitestConfig.COVERAGE_LEVELS}.
|
|
39
|
+
*
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
export declare type CoverageLevelName = "none" | "basic" | "standard" | "strict" | "full";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Coverage thresholds with all four metrics required.
|
|
46
|
+
*
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
export declare interface CoverageThresholds {
|
|
50
|
+
/** Minimum line coverage percentage. */
|
|
51
|
+
lines: number;
|
|
52
|
+
/** Minimum function coverage percentage. */
|
|
53
|
+
functions: number;
|
|
54
|
+
/** Minimum branch coverage percentage. */
|
|
55
|
+
branches: number;
|
|
56
|
+
/** Minimum statement coverage percentage. */
|
|
57
|
+
statements: number;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Override for a specific test kind (unit, e2e, int).
|
|
62
|
+
*
|
|
63
|
+
* @remarks
|
|
64
|
+
* When an object is provided, it is merged into every project of that kind.
|
|
65
|
+
* When a callback is provided, it receives a Map of project name to
|
|
66
|
+
* {@link VitestProject} for fine-grained per-project mutation.
|
|
67
|
+
*
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
export declare type KindOverride = Partial<TestProjectInlineConfiguration["test"]> | ((projects: Map<string, VitestProject>) => void);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Post-process callback for escape-hatch customization of the assembled config.
|
|
74
|
+
*
|
|
75
|
+
* @param config - The assembled Vitest configuration
|
|
76
|
+
* @returns A replacement config, or void to use the mutated original
|
|
77
|
+
*
|
|
78
|
+
* @public
|
|
79
|
+
*/
|
|
80
|
+
export declare type PostProcessCallback = (config: ViteUserConfig) => ViteUserConfig | undefined;
|
|
81
|
+
|
|
60
82
|
export { TestProjectInlineConfiguration }
|
|
61
83
|
|
|
62
84
|
/**
|
|
@@ -82,47 +104,62 @@ export { TestProjectInlineConfiguration }
|
|
|
82
104
|
* ```typescript
|
|
83
105
|
* import { VitestConfig } from "@savvy-web/vitest";
|
|
84
106
|
*
|
|
85
|
-
* export default VitestConfig.create(
|
|
86
|
-
* ({ projects, coverage, reporters }) => ({
|
|
87
|
-
* test: {
|
|
88
|
-
* reporters,
|
|
89
|
-
* projects: projects.map((p) => p.toConfig()),
|
|
90
|
-
* coverage: { provider: "v8", ...coverage },
|
|
91
|
-
* },
|
|
92
|
-
* }),
|
|
93
|
-
* );
|
|
107
|
+
* export default VitestConfig.create();
|
|
94
108
|
* ```
|
|
95
109
|
*
|
|
96
110
|
* @public
|
|
97
111
|
*/
|
|
98
112
|
export declare class VitestConfig {
|
|
99
|
-
/** Default
|
|
100
|
-
static readonly
|
|
113
|
+
/** Default glob patterns excluded from coverage reporting. */
|
|
114
|
+
private static readonly DEFAULT_COVERAGE_EXCLUDE;
|
|
115
|
+
/**
|
|
116
|
+
* Named coverage level presets.
|
|
117
|
+
*
|
|
118
|
+
* @remarks
|
|
119
|
+
* Use a level name with the `coverage` option in {@link VitestConfig.create}
|
|
120
|
+
* to apply a preset. The object is frozen and cannot be mutated.
|
|
121
|
+
*
|
|
122
|
+
* | Level | lines | branches | functions | statements |
|
|
123
|
+
* | -------- | ----- | -------- | --------- | ---------- |
|
|
124
|
+
* | none | 0 | 0 | 0 | 0 |
|
|
125
|
+
* | basic | 50 | 50 | 50 | 50 |
|
|
126
|
+
* | standard | 70 | 65 | 70 | 70 |
|
|
127
|
+
* | strict | 80 | 75 | 80 | 80 |
|
|
128
|
+
* | full | 90 | 85 | 90 | 90 |
|
|
129
|
+
*/
|
|
130
|
+
static readonly COVERAGE_LEVELS: Readonly<Record<CoverageLevelName, CoverageThresholds>>;
|
|
101
131
|
private static cachedProjects;
|
|
102
132
|
private static cachedVitestProjects;
|
|
103
133
|
/**
|
|
104
134
|
* Creates a complete Vitest configuration by discovering workspace projects
|
|
105
135
|
* and generating appropriate settings.
|
|
106
136
|
*
|
|
107
|
-
* @param
|
|
108
|
-
*
|
|
109
|
-
* @
|
|
110
|
-
* @returns The Vitest configuration returned by the callback
|
|
137
|
+
* @param options - Optional declarative configuration
|
|
138
|
+
* @param postProcess - Optional escape-hatch callback for full config control
|
|
139
|
+
* @returns The assembled Vitest configuration
|
|
111
140
|
*
|
|
112
|
-
* @see {@link
|
|
113
|
-
* @see {@link
|
|
141
|
+
* @see {@link VitestConfigOptions} for available options
|
|
142
|
+
* @see {@link PostProcessCallback} for the post-process callback signature
|
|
143
|
+
*/
|
|
144
|
+
static create(options?: VitestConfigOptions, postProcess?: PostProcessCallback): Promise<ViteUserConfig>;
|
|
145
|
+
/**
|
|
146
|
+
* Applies kind-specific overrides to discovered projects.
|
|
147
|
+
*
|
|
148
|
+
* @privateRemarks
|
|
149
|
+
* When the override is an object, it is merged into every project of the
|
|
150
|
+
* matching kind. When it is a callback, it receives a Map of project name
|
|
151
|
+
* to {@link VitestProject} for fine-grained per-project mutation.
|
|
114
152
|
*/
|
|
115
|
-
static
|
|
153
|
+
private static applyKindOverrides;
|
|
116
154
|
/**
|
|
117
|
-
* Extracts
|
|
155
|
+
* Extracts all specific project names from command line arguments.
|
|
118
156
|
*
|
|
119
157
|
* @privateRemarks
|
|
120
158
|
* Supports both `--project=value` and `--project value` formats to match
|
|
121
|
-
* Vitest's own argument parsing behavior.
|
|
122
|
-
*
|
|
123
|
-
* ignored since multi-project coverage scoping is not supported.
|
|
159
|
+
* Vitest's own argument parsing behavior. All `--project` flags are
|
|
160
|
+
* collected to support multi-project coverage scoping.
|
|
124
161
|
*/
|
|
125
|
-
private static
|
|
162
|
+
private static getSpecificProjects;
|
|
126
163
|
/**
|
|
127
164
|
* Reads the `name` field from a package's `package.json`.
|
|
128
165
|
*
|
|
@@ -139,12 +176,22 @@ export declare class VitestConfig {
|
|
|
139
176
|
* pattern used throughout workspace discovery.
|
|
140
177
|
*/
|
|
141
178
|
private static isDirectory;
|
|
179
|
+
/** Extensions probed (in order) when detecting a setup file. */
|
|
180
|
+
private static readonly SETUP_FILE_EXTENSIONS;
|
|
181
|
+
/**
|
|
182
|
+
* Detects a `vitest.setup.{ts,tsx,js,jsx}` file at the package root.
|
|
183
|
+
*
|
|
184
|
+
* @privateRemarks
|
|
185
|
+
* First match wins. Returns just the filename (e.g. `"vitest.setup.ts"`)
|
|
186
|
+
* so the caller can prepend the relative prefix as needed.
|
|
187
|
+
*/
|
|
188
|
+
private static detectSetupFile;
|
|
142
189
|
/**
|
|
143
190
|
* Recursively scans a directory for test files and classifies them by kind.
|
|
144
191
|
*
|
|
145
192
|
* @privateRemarks
|
|
146
|
-
* Short-circuits as soon as
|
|
147
|
-
* unnecessary filesystem traversal.
|
|
193
|
+
* Short-circuits as soon as all three kinds (unit, e2e, and int) are
|
|
194
|
+
* found, avoiding unnecessary filesystem traversal.
|
|
148
195
|
*/
|
|
149
196
|
private static scanForTestFiles;
|
|
150
197
|
/**
|
|
@@ -152,6 +199,19 @@ export declare class VitestConfig {
|
|
|
152
199
|
* test directory.
|
|
153
200
|
*/
|
|
154
201
|
private static buildIncludes;
|
|
202
|
+
/**
|
|
203
|
+
* Conventional subdirectories under `__test__/` that hold helpers, not
|
|
204
|
+
* test files, and should be excluded from test discovery.
|
|
205
|
+
*/
|
|
206
|
+
private static readonly TEST_DIR_EXCLUSIONS;
|
|
207
|
+
/**
|
|
208
|
+
* Returns exclusion patterns for fixture/utils directories under
|
|
209
|
+
* `__test__/`, scoped to the given package prefix.
|
|
210
|
+
*
|
|
211
|
+
* @param prefix - Either `"<relativePath>/"` for non-root packages or
|
|
212
|
+
* `""` for the workspace root.
|
|
213
|
+
*/
|
|
214
|
+
private static buildTestDirExclusions;
|
|
155
215
|
/**
|
|
156
216
|
* Discovers all packages in the workspace that contain a `src/` directory
|
|
157
217
|
* and generates {@link VitestProject} instances based on filename conventions.
|
|
@@ -162,63 +222,68 @@ export declare class VitestConfig {
|
|
|
162
222
|
* test files still get a unit project entry as a forward-looking placeholder.
|
|
163
223
|
*/
|
|
164
224
|
private static discoverWorkspaceProjects;
|
|
225
|
+
/**
|
|
226
|
+
* Resolves coverage thresholds from options.
|
|
227
|
+
*
|
|
228
|
+
* @privateRemarks
|
|
229
|
+
* Priority: `options.coverage` (name or object) \> `COVERAGE_LEVELS.strict`.
|
|
230
|
+
*/
|
|
231
|
+
private static resolveThresholds;
|
|
165
232
|
/**
|
|
166
233
|
* Generates coverage configuration including thresholds.
|
|
167
234
|
*
|
|
168
235
|
* @privateRemarks
|
|
169
|
-
* Strips `:unit`/`:e2e` suffix when looking up project paths for
|
|
236
|
+
* Strips `:unit`/`:e2e`/`:int` suffix when looking up project paths for
|
|
170
237
|
* `--project` filtering, since coverage applies to the entire package
|
|
171
|
-
* regardless of test kind.
|
|
238
|
+
* regardless of test kind. When multiple `--project` flags are provided,
|
|
239
|
+
* coverage includes are unioned across all matched packages.
|
|
172
240
|
*/
|
|
173
241
|
private static getCoverageConfig;
|
|
174
242
|
}
|
|
175
243
|
|
|
176
|
-
/**
|
|
177
|
-
* Callback that receives discovered configuration and returns a Vitest config.
|
|
178
|
-
*
|
|
179
|
-
* @param config - Object containing discovered projects, coverage settings,
|
|
180
|
-
* reporters array, and CI detection flag
|
|
181
|
-
* @returns A Vitest user configuration, optionally async
|
|
182
|
-
*
|
|
183
|
-
* @see {@link VitestConfig.create} for the entry point that invokes this callback
|
|
184
|
-
*
|
|
185
|
-
* @public
|
|
186
|
-
*/
|
|
187
|
-
export declare type VitestConfigCallback = (config: {
|
|
188
|
-
/** Discovered {@link VitestProject} instances for the workspace. */
|
|
189
|
-
projects: VitestProject[];
|
|
190
|
-
/** Generated coverage configuration with thresholds. */
|
|
191
|
-
coverage: CoverageConfig;
|
|
192
|
-
/** Reporter names based on environment (adds `"github-actions"` in CI). */
|
|
193
|
-
reporters: string[];
|
|
194
|
-
/** Whether the current environment is GitHub Actions CI. */
|
|
195
|
-
isCI: boolean;
|
|
196
|
-
}) => ViteUserConfig | Promise<ViteUserConfig>;
|
|
197
|
-
|
|
198
244
|
/**
|
|
199
245
|
* Options for {@link VitestConfig.create}.
|
|
200
246
|
*
|
|
201
247
|
* @public
|
|
202
248
|
*/
|
|
203
|
-
export declare interface
|
|
249
|
+
export declare interface VitestConfigOptions {
|
|
204
250
|
/**
|
|
205
|
-
* Coverage thresholds
|
|
251
|
+
* Coverage level name or explicit thresholds object.
|
|
206
252
|
*
|
|
207
253
|
* @remarks
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* @
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
254
|
+
* When a {@link CoverageLevelName} string is provided, the corresponding
|
|
255
|
+
* preset from {@link VitestConfig.COVERAGE_LEVELS} is used. When a
|
|
256
|
+
* {@link CoverageThresholds} object is provided, it is used directly.
|
|
257
|
+
*
|
|
258
|
+
* @defaultValue `"strict"` (lines: 80, branches: 75, functions: 80, statements: 80)
|
|
259
|
+
*/
|
|
260
|
+
coverage?: CoverageLevelName | CoverageThresholds;
|
|
261
|
+
/** Additional glob patterns to exclude from coverage reporting. */
|
|
262
|
+
coverageExclude?: string[];
|
|
263
|
+
/**
|
|
264
|
+
* Whether to inject the vitest-agent-reporter plugin.
|
|
265
|
+
*
|
|
266
|
+
* @remarks
|
|
267
|
+
* When `true` or an {@link AgentReporterConfig} object, the plugin is
|
|
268
|
+
* injected with the given options (with `strategy` defaulting to `"own"`
|
|
269
|
+
* and `coverageThresholds` populated from the resolved coverage level).
|
|
270
|
+
* When `false`, the plugin is not injected.
|
|
271
|
+
*
|
|
272
|
+
* @defaultValue `true`
|
|
273
|
+
*/
|
|
274
|
+
agentReporter?: boolean | AgentReporterConfig;
|
|
275
|
+
/**
|
|
276
|
+
* Vitest pool mode.
|
|
277
|
+
*
|
|
278
|
+
* @defaultValue Uses Vitest's default (threads)
|
|
279
|
+
*/
|
|
280
|
+
pool?: "threads" | "forks" | "vmThreads" | "vmForks";
|
|
281
|
+
/** Override configuration for all unit test projects. */
|
|
282
|
+
unit?: KindOverride;
|
|
283
|
+
/** Override configuration for all e2e test projects. */
|
|
284
|
+
e2e?: KindOverride;
|
|
285
|
+
/** Override configuration for all integration test projects. */
|
|
286
|
+
int?: KindOverride;
|
|
222
287
|
}
|
|
223
288
|
|
|
224
289
|
/**
|
|
@@ -266,6 +331,14 @@ export declare class VitestProject {
|
|
|
266
331
|
* @see {@link VitestProjectKind}
|
|
267
332
|
*/
|
|
268
333
|
get kind(): VitestProjectKind;
|
|
334
|
+
/**
|
|
335
|
+
* Coverage exclusion patterns accumulated via {@link addCoverageExclude}.
|
|
336
|
+
*
|
|
337
|
+
* @remarks
|
|
338
|
+
* These patterns are not embedded in the inline project config but are
|
|
339
|
+
* made available for the workspace-level coverage configuration to consume.
|
|
340
|
+
*/
|
|
341
|
+
get coverageExcludes(): readonly string[];
|
|
269
342
|
/**
|
|
270
343
|
* Returns the vitest-native inline configuration object.
|
|
271
344
|
*
|
|
@@ -273,6 +346,54 @@ export declare class VitestProject {
|
|
|
273
346
|
* with all defaults and overrides merged
|
|
274
347
|
*/
|
|
275
348
|
toConfig(): TestProjectInlineConfiguration;
|
|
349
|
+
/**
|
|
350
|
+
* Creates a clone of this project with independent config state.
|
|
351
|
+
*
|
|
352
|
+
* @remarks
|
|
353
|
+
* The clone has its own config object so mutations via
|
|
354
|
+
* {@link override}, {@link addInclude}, {@link addExclude}, and
|
|
355
|
+
* {@link addCoverageExclude} do not affect the original.
|
|
356
|
+
*
|
|
357
|
+
* @returns A new {@link VitestProject} with the same configuration
|
|
358
|
+
*/
|
|
359
|
+
clone(): VitestProject;
|
|
360
|
+
/**
|
|
361
|
+
* Merges additional configuration over the current config.
|
|
362
|
+
*
|
|
363
|
+
* @remarks
|
|
364
|
+
* The {@link VitestProjectOptions.name | name} and
|
|
365
|
+
* {@link VitestProjectOptions.include | include} fields are preserved
|
|
366
|
+
* and cannot be overridden.
|
|
367
|
+
*
|
|
368
|
+
* @param config - Partial configuration to merge
|
|
369
|
+
* @returns `this` for chaining
|
|
370
|
+
*/
|
|
371
|
+
override(config: Partial<TestProjectInlineConfiguration>): this;
|
|
372
|
+
/**
|
|
373
|
+
* Appends glob patterns to the test include list.
|
|
374
|
+
*
|
|
375
|
+
* @param patterns - Glob patterns to add
|
|
376
|
+
* @returns `this` for chaining
|
|
377
|
+
*/
|
|
378
|
+
addInclude(...patterns: string[]): this;
|
|
379
|
+
/**
|
|
380
|
+
* Appends glob patterns to the test exclude list.
|
|
381
|
+
*
|
|
382
|
+
* @param patterns - Glob patterns to add
|
|
383
|
+
* @returns `this` for chaining
|
|
384
|
+
*/
|
|
385
|
+
addExclude(...patterns: string[]): this;
|
|
386
|
+
/**
|
|
387
|
+
* Appends glob patterns to the coverage exclusion list.
|
|
388
|
+
*
|
|
389
|
+
* @remarks
|
|
390
|
+
* These patterns are exposed via {@link coverageExcludes} for the
|
|
391
|
+
* workspace-level coverage configuration to consume.
|
|
392
|
+
*
|
|
393
|
+
* @param patterns - Glob patterns to exclude from coverage
|
|
394
|
+
* @returns `this` for chaining
|
|
395
|
+
*/
|
|
396
|
+
addCoverageExclude(...patterns: string[]): this;
|
|
276
397
|
/**
|
|
277
398
|
* Creates a unit test project with sensible defaults.
|
|
278
399
|
*
|
|
@@ -315,6 +436,28 @@ export declare class VitestProject {
|
|
|
315
436
|
* ```
|
|
316
437
|
*/
|
|
317
438
|
static e2e(options: VitestProjectOptions): VitestProject;
|
|
439
|
+
/**
|
|
440
|
+
* Creates an integration test project with sensible defaults.
|
|
441
|
+
*
|
|
442
|
+
* @remarks
|
|
443
|
+
* Defaults applied: `extends: true`, `environment: "node"`,
|
|
444
|
+
* `testTimeout: 60_000`, `hookTimeout: 30_000`,
|
|
445
|
+
* `maxConcurrency: clamp(floor(cpus / 2), 1, 8)`.
|
|
446
|
+
*
|
|
447
|
+
* @param options - Project options (the `kind` field is forced to `"int"`)
|
|
448
|
+
* @returns A new {@link VitestProject} configured for integration tests
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* ```typescript
|
|
452
|
+
* import { VitestProject } from "@savvy-web/vitest";
|
|
453
|
+
*
|
|
454
|
+
* const project = VitestProject.int({
|
|
455
|
+
* name: "@savvy-web/my-lib:int",
|
|
456
|
+
* include: ["__test__/integration/**\/*.int.test.ts"],
|
|
457
|
+
* });
|
|
458
|
+
* ```
|
|
459
|
+
*/
|
|
460
|
+
static int(options: VitestProjectOptions): VitestProject;
|
|
318
461
|
/**
|
|
319
462
|
* Creates a custom test project with no preset defaults beyond `extends: true`.
|
|
320
463
|
*
|
|
@@ -351,7 +494,7 @@ export declare class VitestProject {
|
|
|
351
494
|
*
|
|
352
495
|
* @public
|
|
353
496
|
*/
|
|
354
|
-
export declare type VitestProjectKind = "unit" | "e2e" | (string & {});
|
|
497
|
+
export declare type VitestProjectKind = "unit" | "e2e" | "int" | (string & {});
|
|
355
498
|
|
|
356
499
|
/**
|
|
357
500
|
* Options for constructing a {@link VitestProject}.
|
package/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { cpus } from "node:os";
|
|
3
3
|
import { join, relative } from "node:path";
|
|
4
|
+
import { AgentPlugin } from "vitest-agent-reporter";
|
|
4
5
|
import { getWorkspaceManagerRoot, getWorkspacePackagePaths } from "workspace-tools";
|
|
5
6
|
class VitestProject {
|
|
6
7
|
#name;
|
|
7
8
|
#kind;
|
|
8
9
|
#config;
|
|
10
|
+
#coverageExcludes = [];
|
|
9
11
|
constructor(options, defaults){
|
|
10
12
|
this.#name = options.name;
|
|
11
13
|
this.#kind = options.kind ?? "unit";
|
|
@@ -29,9 +31,75 @@ class VitestProject {
|
|
|
29
31
|
get kind() {
|
|
30
32
|
return this.#kind;
|
|
31
33
|
}
|
|
34
|
+
get coverageExcludes() {
|
|
35
|
+
return this.#coverageExcludes;
|
|
36
|
+
}
|
|
32
37
|
toConfig() {
|
|
33
38
|
return this.#config;
|
|
34
39
|
}
|
|
40
|
+
clone() {
|
|
41
|
+
const { test, ...rest } = this.#config;
|
|
42
|
+
const cloned = new VitestProject({
|
|
43
|
+
name: this.#name,
|
|
44
|
+
include: test?.include ?? [],
|
|
45
|
+
kind: this.#kind
|
|
46
|
+
}, {});
|
|
47
|
+
cloned.#config = {
|
|
48
|
+
...rest,
|
|
49
|
+
test: test ? {
|
|
50
|
+
...test
|
|
51
|
+
} : void 0
|
|
52
|
+
};
|
|
53
|
+
cloned.#coverageExcludes.push(...this.#coverageExcludes);
|
|
54
|
+
return cloned;
|
|
55
|
+
}
|
|
56
|
+
override(config) {
|
|
57
|
+
const { test: overrideTest, ...overrideRest } = config;
|
|
58
|
+
const { test: existingTest, ...existingRest } = this.#config;
|
|
59
|
+
this.#config = {
|
|
60
|
+
...existingRest,
|
|
61
|
+
...overrideRest,
|
|
62
|
+
test: {
|
|
63
|
+
...existingTest,
|
|
64
|
+
...overrideTest,
|
|
65
|
+
name: this.#name,
|
|
66
|
+
include: existingTest?.include
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
addInclude(...patterns) {
|
|
72
|
+
const { test: existingTest, ...rest } = this.#config;
|
|
73
|
+
this.#config = {
|
|
74
|
+
...rest,
|
|
75
|
+
test: {
|
|
76
|
+
...existingTest,
|
|
77
|
+
include: [
|
|
78
|
+
...existingTest?.include ?? [],
|
|
79
|
+
...patterns
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
addExclude(...patterns) {
|
|
86
|
+
const { test: existingTest, ...rest } = this.#config;
|
|
87
|
+
this.#config = {
|
|
88
|
+
...rest,
|
|
89
|
+
test: {
|
|
90
|
+
...existingTest,
|
|
91
|
+
exclude: [
|
|
92
|
+
...existingTest?.exclude ?? [],
|
|
93
|
+
...patterns
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
addCoverageExclude(...patterns) {
|
|
100
|
+
this.#coverageExcludes.push(...patterns);
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
35
103
|
static unit(options) {
|
|
36
104
|
return new VitestProject({
|
|
37
105
|
...options,
|
|
@@ -56,6 +124,20 @@ class VitestProject {
|
|
|
56
124
|
}
|
|
57
125
|
});
|
|
58
126
|
}
|
|
127
|
+
static int(options) {
|
|
128
|
+
const concurrency = Math.max(1, Math.min(8, Math.floor(cpus().length / 2)));
|
|
129
|
+
return new VitestProject({
|
|
130
|
+
...options,
|
|
131
|
+
kind: "int"
|
|
132
|
+
}, {
|
|
133
|
+
test: {
|
|
134
|
+
environment: "node",
|
|
135
|
+
testTimeout: 60000,
|
|
136
|
+
hookTimeout: 30000,
|
|
137
|
+
maxConcurrency: concurrency
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
59
141
|
static custom(kind, options) {
|
|
60
142
|
return new VitestProject({
|
|
61
143
|
...options,
|
|
@@ -64,13 +146,52 @@ class VitestProject {
|
|
|
64
146
|
}
|
|
65
147
|
}
|
|
66
148
|
class VitestConfig {
|
|
67
|
-
static
|
|
149
|
+
static DEFAULT_COVERAGE_EXCLUDE = [
|
|
150
|
+
"**/*.{test,spec}.{ts,tsx,js,jsx}",
|
|
151
|
+
"**/__test__/**",
|
|
152
|
+
"**/generated/**"
|
|
153
|
+
];
|
|
154
|
+
static COVERAGE_LEVELS = Object.freeze({
|
|
155
|
+
none: {
|
|
156
|
+
lines: 0,
|
|
157
|
+
branches: 0,
|
|
158
|
+
functions: 0,
|
|
159
|
+
statements: 0
|
|
160
|
+
},
|
|
161
|
+
basic: {
|
|
162
|
+
lines: 50,
|
|
163
|
+
branches: 50,
|
|
164
|
+
functions: 50,
|
|
165
|
+
statements: 50
|
|
166
|
+
},
|
|
167
|
+
standard: {
|
|
168
|
+
lines: 70,
|
|
169
|
+
branches: 65,
|
|
170
|
+
functions: 70,
|
|
171
|
+
statements: 70
|
|
172
|
+
},
|
|
173
|
+
strict: {
|
|
174
|
+
lines: 80,
|
|
175
|
+
branches: 75,
|
|
176
|
+
functions: 80,
|
|
177
|
+
statements: 80
|
|
178
|
+
},
|
|
179
|
+
full: {
|
|
180
|
+
lines: 90,
|
|
181
|
+
branches: 85,
|
|
182
|
+
functions: 90,
|
|
183
|
+
statements: 90
|
|
184
|
+
}
|
|
185
|
+
});
|
|
68
186
|
static cachedProjects = null;
|
|
69
187
|
static cachedVitestProjects = null;
|
|
70
|
-
static create(
|
|
71
|
-
const
|
|
188
|
+
static async create(options, postProcess) {
|
|
189
|
+
const specificProjects = VitestConfig.getSpecificProjects();
|
|
72
190
|
const { projects, vitestProjects } = VitestConfig.discoverWorkspaceProjects();
|
|
73
|
-
const
|
|
191
|
+
const thresholds = VitestConfig.resolveThresholds(options);
|
|
192
|
+
const coverageConfig = VitestConfig.getCoverageConfig(specificProjects, projects, options);
|
|
193
|
+
const workingProjects = vitestProjects.map((p)=>p.clone());
|
|
194
|
+
VitestConfig.applyKindOverrides(workingProjects, options);
|
|
74
195
|
const isCI = Boolean(process.env.GITHUB_ACTIONS);
|
|
75
196
|
const reporters = isCI ? [
|
|
76
197
|
"default",
|
|
@@ -78,20 +199,76 @@ class VitestConfig {
|
|
|
78
199
|
] : [
|
|
79
200
|
"default"
|
|
80
201
|
];
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
202
|
+
let config = {
|
|
203
|
+
test: {
|
|
204
|
+
reporters,
|
|
205
|
+
projects: workingProjects.map((p)=>p.toConfig()),
|
|
206
|
+
...options?.pool ? {
|
|
207
|
+
pool: options.pool
|
|
208
|
+
} : {},
|
|
209
|
+
coverage: {
|
|
210
|
+
provider: "v8",
|
|
211
|
+
...coverageConfig,
|
|
212
|
+
enabled: true
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
if (options?.agentReporter !== false) {
|
|
217
|
+
const agentOpts = "object" == typeof options?.agentReporter ? options.agentReporter : {};
|
|
218
|
+
const plugin = AgentPlugin({
|
|
219
|
+
strategy: "own",
|
|
220
|
+
...agentOpts,
|
|
221
|
+
reporter: {
|
|
222
|
+
coverageThresholds: {
|
|
223
|
+
...thresholds
|
|
224
|
+
},
|
|
225
|
+
...agentOpts.reporter
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
config.plugins = [
|
|
229
|
+
plugin
|
|
230
|
+
];
|
|
231
|
+
}
|
|
232
|
+
if (postProcess) {
|
|
233
|
+
const result = postProcess(config);
|
|
234
|
+
if (void 0 !== result) config = result;
|
|
235
|
+
}
|
|
236
|
+
return config;
|
|
237
|
+
}
|
|
238
|
+
static applyKindOverrides(vitestProjects, options) {
|
|
239
|
+
if (!options) return;
|
|
240
|
+
const kindOptions = {
|
|
241
|
+
unit: options.unit,
|
|
242
|
+
e2e: options.e2e,
|
|
243
|
+
int: options.int
|
|
244
|
+
};
|
|
245
|
+
for (const [kind, override] of Object.entries(kindOptions)){
|
|
246
|
+
if (void 0 === override) continue;
|
|
247
|
+
const projectsOfKind = vitestProjects.filter((p)=>p.kind === kind);
|
|
248
|
+
if ("function" == typeof override) {
|
|
249
|
+
const map = new Map();
|
|
250
|
+
for (const p of projectsOfKind)map.set(p.name, p);
|
|
251
|
+
override(map);
|
|
252
|
+
} else for (const p of projectsOfKind)p.override({
|
|
253
|
+
test: override
|
|
254
|
+
});
|
|
255
|
+
}
|
|
87
256
|
}
|
|
88
|
-
static
|
|
257
|
+
static getSpecificProjects() {
|
|
89
258
|
const args = process.argv;
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
259
|
+
const projects = [];
|
|
260
|
+
for(let i = 0; i < args.length; i++){
|
|
261
|
+
const arg = args[i];
|
|
262
|
+
if (arg.startsWith("--project=")) {
|
|
263
|
+
const value = arg.split("=")[1];
|
|
264
|
+
if (value) projects.push(value);
|
|
265
|
+
} else if ("--project" === arg && i + 1 < args.length) {
|
|
266
|
+
const value = args[i + 1];
|
|
267
|
+
if (value) projects.push(value);
|
|
268
|
+
i++;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return projects;
|
|
95
272
|
}
|
|
96
273
|
static getPackageNameFromPath(packagePath) {
|
|
97
274
|
try {
|
|
@@ -108,9 +285,26 @@ class VitestConfig {
|
|
|
108
285
|
return false;
|
|
109
286
|
}
|
|
110
287
|
}
|
|
288
|
+
static SETUP_FILE_EXTENSIONS = [
|
|
289
|
+
"ts",
|
|
290
|
+
"tsx",
|
|
291
|
+
"js",
|
|
292
|
+
"jsx"
|
|
293
|
+
];
|
|
294
|
+
static detectSetupFile(packagePath) {
|
|
295
|
+
for (const ext of VitestConfig.SETUP_FILE_EXTENSIONS){
|
|
296
|
+
const candidate = join(packagePath, `vitest.setup.${ext}`);
|
|
297
|
+
try {
|
|
298
|
+
const stat = statSync(candidate);
|
|
299
|
+
if (stat.isFile()) return `vitest.setup.${ext}`;
|
|
300
|
+
} catch {}
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
111
304
|
static scanForTestFiles(dirPath) {
|
|
112
305
|
let hasUnit = false;
|
|
113
306
|
let hasE2e = false;
|
|
307
|
+
let hasInt = false;
|
|
114
308
|
try {
|
|
115
309
|
const entries = readdirSync(dirPath, {
|
|
116
310
|
withFileTypes: true
|
|
@@ -120,16 +314,19 @@ class VitestConfig {
|
|
|
120
314
|
const sub = VitestConfig.scanForTestFiles(join(dirPath, entry.name));
|
|
121
315
|
hasUnit = hasUnit || sub.hasUnit;
|
|
122
316
|
hasE2e = hasE2e || sub.hasE2e;
|
|
317
|
+
hasInt = hasInt || sub.hasInt;
|
|
123
318
|
} else if (entry.isFile()) {
|
|
124
|
-
if (/\.e2e\.(test|spec)\.ts$/.test(entry.name)) hasE2e = true;
|
|
125
|
-
else if (/\.(test|spec)\.ts$/.test(entry.name))
|
|
319
|
+
if (/\.e2e\.(test|spec)\.(ts|tsx|js|jsx)$/.test(entry.name)) hasE2e = true;
|
|
320
|
+
else if (/\.int\.(test|spec)\.(ts|tsx|js|jsx)$/.test(entry.name)) hasInt = true;
|
|
321
|
+
else if (/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(entry.name)) hasUnit = true;
|
|
126
322
|
}
|
|
127
|
-
if (hasUnit && hasE2e) break;
|
|
323
|
+
if (hasUnit && hasE2e && hasInt) break;
|
|
128
324
|
}
|
|
129
325
|
} catch {}
|
|
130
326
|
return {
|
|
131
327
|
hasUnit,
|
|
132
|
-
hasE2e
|
|
328
|
+
hasE2e,
|
|
329
|
+
hasInt
|
|
133
330
|
};
|
|
134
331
|
}
|
|
135
332
|
static buildIncludes(srcGlob, testGlob, pattern) {
|
|
@@ -139,6 +336,21 @@ class VitestConfig {
|
|
|
139
336
|
if (testGlob) includes.push(`${testGlob}/**/${pattern}`);
|
|
140
337
|
return includes;
|
|
141
338
|
}
|
|
339
|
+
static TEST_DIR_EXCLUSIONS = [
|
|
340
|
+
"__test__/fixtures/**",
|
|
341
|
+
"__test__/utils/**",
|
|
342
|
+
"__test__/unit/fixtures/**",
|
|
343
|
+
"__test__/unit/utils/**",
|
|
344
|
+
"__test__/e2e/fixtures/**",
|
|
345
|
+
"__test__/e2e/utils/**",
|
|
346
|
+
"__test__/int/fixtures/**",
|
|
347
|
+
"__test__/int/utils/**",
|
|
348
|
+
"__test__/integration/fixtures/**",
|
|
349
|
+
"__test__/integration/utils/**"
|
|
350
|
+
];
|
|
351
|
+
static buildTestDirExclusions(prefix) {
|
|
352
|
+
return VitestConfig.TEST_DIR_EXCLUSIONS.map((pattern)=>`${prefix}${pattern}`);
|
|
353
|
+
}
|
|
142
354
|
static discoverWorkspaceProjects() {
|
|
143
355
|
if (VitestConfig.cachedProjects && VitestConfig.cachedVitestProjects) return {
|
|
144
356
|
projects: VitestConfig.cachedProjects,
|
|
@@ -161,32 +373,83 @@ class VitestConfig {
|
|
|
161
373
|
const srcScan = VitestConfig.scanForTestFiles(srcDirPath);
|
|
162
374
|
const testScan = hasTestDir ? VitestConfig.scanForTestFiles(testDirPath) : {
|
|
163
375
|
hasUnit: false,
|
|
164
|
-
hasE2e: false
|
|
376
|
+
hasE2e: false,
|
|
377
|
+
hasInt: false
|
|
165
378
|
};
|
|
166
379
|
const hasUnit = srcScan.hasUnit || testScan.hasUnit;
|
|
167
380
|
const hasE2e = srcScan.hasE2e || testScan.hasE2e;
|
|
168
|
-
const
|
|
381
|
+
const hasInt = srcScan.hasInt || testScan.hasInt;
|
|
382
|
+
const kindCount = [
|
|
383
|
+
hasUnit,
|
|
384
|
+
hasE2e,
|
|
385
|
+
hasInt
|
|
386
|
+
].filter(Boolean).length;
|
|
387
|
+
const shouldSuffix = kindCount >= 2;
|
|
169
388
|
const prefix = "." === relativePath ? "" : `${relativePath}/`;
|
|
170
389
|
const srcGlob = `${prefix}src`;
|
|
171
390
|
const testGlob = hasTestDir ? `${prefix}__test__` : null;
|
|
391
|
+
const testDirExcludes = hasTestDir ? VitestConfig.buildTestDirExclusions(prefix) : [];
|
|
392
|
+
const setupFile = VitestConfig.detectSetupFile(pkgPath);
|
|
393
|
+
const setupFiles = setupFile ? [
|
|
394
|
+
`${prefix}${setupFile}`
|
|
395
|
+
] : void 0;
|
|
172
396
|
if (hasUnit) vitestProjects.push(VitestProject.unit({
|
|
173
|
-
name:
|
|
174
|
-
include: VitestConfig.buildIncludes(srcGlob, testGlob, "*.{test,spec}.ts"),
|
|
397
|
+
name: shouldSuffix ? `${packageName}:unit` : packageName,
|
|
398
|
+
include: VitestConfig.buildIncludes(srcGlob, testGlob, "*.{test,spec}.{ts,tsx,js,jsx}"),
|
|
175
399
|
overrides: {
|
|
176
400
|
test: {
|
|
401
|
+
...setupFiles ? {
|
|
402
|
+
setupFiles
|
|
403
|
+
} : {},
|
|
177
404
|
exclude: [
|
|
178
|
-
"**/*.e2e.{test,spec}
|
|
405
|
+
"**/*.e2e.{test,spec}.*",
|
|
406
|
+
"**/*.int.{test,spec}.*",
|
|
407
|
+
...testDirExcludes
|
|
179
408
|
]
|
|
180
409
|
}
|
|
181
410
|
}
|
|
182
411
|
}));
|
|
183
412
|
if (hasE2e) vitestProjects.push(VitestProject.e2e({
|
|
184
|
-
name:
|
|
185
|
-
include: VitestConfig.buildIncludes(srcGlob, testGlob, "*.e2e.{test,spec}.ts")
|
|
413
|
+
name: shouldSuffix ? `${packageName}:e2e` : packageName,
|
|
414
|
+
include: VitestConfig.buildIncludes(srcGlob, testGlob, "*.e2e.{test,spec}.{ts,tsx,js,jsx}"),
|
|
415
|
+
overrides: {
|
|
416
|
+
test: {
|
|
417
|
+
...setupFiles ? {
|
|
418
|
+
setupFiles
|
|
419
|
+
} : {},
|
|
420
|
+
exclude: [
|
|
421
|
+
...testDirExcludes
|
|
422
|
+
]
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}));
|
|
426
|
+
if (hasInt) vitestProjects.push(VitestProject.int({
|
|
427
|
+
name: shouldSuffix ? `${packageName}:int` : packageName,
|
|
428
|
+
include: VitestConfig.buildIncludes(srcGlob, testGlob, "*.int.{test,spec}.{ts,tsx,js,jsx}"),
|
|
429
|
+
overrides: {
|
|
430
|
+
test: {
|
|
431
|
+
...setupFiles ? {
|
|
432
|
+
setupFiles
|
|
433
|
+
} : {},
|
|
434
|
+
exclude: [
|
|
435
|
+
...testDirExcludes
|
|
436
|
+
]
|
|
437
|
+
}
|
|
438
|
+
}
|
|
186
439
|
}));
|
|
187
|
-
if (!hasUnit && !hasE2e) vitestProjects.push(VitestProject.unit({
|
|
440
|
+
if (!hasUnit && !hasE2e && !hasInt) vitestProjects.push(VitestProject.unit({
|
|
188
441
|
name: packageName,
|
|
189
|
-
include: VitestConfig.buildIncludes(srcGlob, testGlob, "*.{test,spec}.ts")
|
|
442
|
+
include: VitestConfig.buildIncludes(srcGlob, testGlob, "*.{test,spec}.{ts,tsx,js,jsx}"),
|
|
443
|
+
overrides: {
|
|
444
|
+
test: {
|
|
445
|
+
...setupFiles ? {
|
|
446
|
+
setupFiles
|
|
447
|
+
} : {},
|
|
448
|
+
exclude: [
|
|
449
|
+
...testDirExcludes
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
}
|
|
190
453
|
}));
|
|
191
454
|
}
|
|
192
455
|
VitestConfig.cachedProjects = projects;
|
|
@@ -196,28 +459,36 @@ class VitestConfig {
|
|
|
196
459
|
vitestProjects
|
|
197
460
|
};
|
|
198
461
|
}
|
|
199
|
-
static
|
|
462
|
+
static resolveThresholds(options) {
|
|
463
|
+
if (options?.coverage === void 0) return {
|
|
464
|
+
...VitestConfig.COVERAGE_LEVELS.strict
|
|
465
|
+
};
|
|
466
|
+
if ("string" == typeof options.coverage) return {
|
|
467
|
+
...VitestConfig.COVERAGE_LEVELS[options.coverage]
|
|
468
|
+
};
|
|
469
|
+
return {
|
|
470
|
+
...options.coverage
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
static getCoverageConfig(specificProjects, projects, options) {
|
|
200
474
|
const exclude = [
|
|
201
|
-
|
|
475
|
+
...VitestConfig.DEFAULT_COVERAGE_EXCLUDE,
|
|
476
|
+
...options?.coverageExclude ?? []
|
|
202
477
|
];
|
|
203
|
-
const
|
|
204
|
-
const thresholds = {
|
|
205
|
-
lines: options?.thresholds?.lines ?? t,
|
|
206
|
-
functions: options?.thresholds?.functions ?? t,
|
|
207
|
-
branches: options?.thresholds?.branches ?? t,
|
|
208
|
-
statements: options?.thresholds?.statements ?? t
|
|
209
|
-
};
|
|
478
|
+
const thresholds = VitestConfig.resolveThresholds(options);
|
|
210
479
|
const toSrcGlob = (relPath)=>{
|
|
211
480
|
const prefix = "." === relPath ? "" : `${relPath}/`;
|
|
212
481
|
return `${prefix}src/**/*.ts`;
|
|
213
482
|
};
|
|
214
|
-
if (
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
483
|
+
if (specificProjects.length > 0) {
|
|
484
|
+
const includes = [];
|
|
485
|
+
for (const sp of specificProjects){
|
|
486
|
+
const baseName = sp.replace(/:(unit|e2e|int)$/, "");
|
|
487
|
+
const relPath = projects[baseName];
|
|
488
|
+
if (void 0 !== relPath) includes.push(toSrcGlob(relPath));
|
|
489
|
+
}
|
|
490
|
+
if (includes.length > 0) return {
|
|
491
|
+
include: includes,
|
|
221
492
|
exclude,
|
|
222
493
|
thresholds
|
|
223
494
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/vitest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Vitest utility functions for Silk Suite deployment system",
|
|
6
6
|
"homepage": "https://github.com/savvy-web/vitest#readme",
|
|
@@ -25,11 +25,12 @@
|
|
|
25
25
|
"workspace-tools": "^0.41.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@types/node": "^25.
|
|
28
|
+
"@types/node": "^25.5.0",
|
|
29
29
|
"@typescript/native-preview": "^7.0.0-dev.20260124.1",
|
|
30
30
|
"@vitest/coverage-v8": "^4.1.0",
|
|
31
31
|
"typescript": "^5.9.3",
|
|
32
|
-
"vitest": "^4.1.0"
|
|
32
|
+
"vitest": "^4.1.0",
|
|
33
|
+
"vitest-agent-reporter": "^1.1.0"
|
|
33
34
|
},
|
|
34
35
|
"peerDependenciesMeta": {
|
|
35
36
|
"@types/node": {
|
|
@@ -46,6 +47,9 @@
|
|
|
46
47
|
},
|
|
47
48
|
"vitest": {
|
|
48
49
|
"optional": false
|
|
50
|
+
},
|
|
51
|
+
"vitest-agent-reporter": {
|
|
52
|
+
"optional": false
|
|
49
53
|
}
|
|
50
54
|
},
|
|
51
55
|
"files": [
|