@vitronai/themis 0.1.6 → 0.1.9
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 +14 -10
- package/docs/agents-adoption.md +8 -4
- package/docs/api.md +16 -7
- package/docs/roadmap.md +1 -1
- package/docs/vscode-extension.md +1 -1
- package/docs/why-themis.md +1 -1
- package/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/artifact-paths.js +49 -42
- package/src/cli.js +2 -2
- package/src/config.js +10 -0
- package/src/discovery.js +18 -8
- package/src/generate.js +3 -1
- package/src/init.js +1 -1
- package/src/migrate.js +1 -1
- package/src/module-loader.js +139 -0
- package/src/reporter.js +5 -0
- package/templates/AGENTS.themis.md +7 -3
package/README.md
CHANGED
|
@@ -117,7 +117,7 @@ npx themis generate src
|
|
|
117
117
|
npx themis test
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
Use `npx themis generate src` to generate deterministic unit tests for JS/TS exports. Generated files land under `tests
|
|
120
|
+
Use `npx themis generate src` to generate deterministic unit tests for JS/TS exports. Generated files land under `__themis__/tests` by default.
|
|
121
121
|
|
|
122
122
|
If another repo wants its agents to reliably choose Themis, put the framework choice directly in that repo's agent instructions instead of assuming agents will infer it from package metadata alone.
|
|
123
123
|
|
|
@@ -160,7 +160,7 @@ npx themis generate src
|
|
|
160
160
|
npx themis test
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
Generated files land under `tests
|
|
163
|
+
Generated files land under `__themis__/tests` by default. Each generated test:
|
|
164
164
|
|
|
165
165
|
- checks the scanned export names when Themis can resolve them exactly
|
|
166
166
|
- asserts the normalized runtime export contract directly in generated source
|
|
@@ -258,8 +258,8 @@ Short version:
|
|
|
258
258
|
|
|
259
259
|
## Commands
|
|
260
260
|
|
|
261
|
-
- `npx themis init`: creates `themis.config.json
|
|
262
|
-
- `npx themis generate src`: scans source files and generates contract tests under `tests
|
|
261
|
+
- `npx themis init`: creates `themis.config.json`, adds `.themis/` to `.gitignore`, and adds `__themis__/reports/` plus `__themis__/shims/` to `.gitignore`.
|
|
262
|
+
- `npx themis generate src`: scans source files and generates contract tests under `__themis__/tests`, using `.generated.test.ts` for TS/TSX sources and `.generated.test.js` for JS/JSX sources.
|
|
263
263
|
- `npx themis generate src --json`: emits a machine-readable generation payload for agents and automation.
|
|
264
264
|
- `npx themis generate src --plan`: emits a planning payload and handoff artifact without writing generated tests.
|
|
265
265
|
- `npx themis generate src --review --json`: previews create/update/remove decisions without writing files.
|
|
@@ -270,10 +270,10 @@ Short version:
|
|
|
270
270
|
- `npx themis generate src --changed`: regenerates against changed files in the current git worktree.
|
|
271
271
|
- `npx themis generate src --scenario react-hook --min-confidence high`: targets one adapter family at a confidence threshold.
|
|
272
272
|
- `npx themis generate app --scenario next-route-handler`: focuses generation on Next app router request handlers.
|
|
273
|
-
- `npx themis migrate jest`: scaffolds a Themis config/setup bridge for existing Jest suites and gitignores `.themis/`.
|
|
273
|
+
- `npx themis migrate jest`: scaffolds a Themis config/setup bridge for existing Jest suites and gitignores `.themis/` plus `__themis__/reports/` and `__themis__/shims/`.
|
|
274
274
|
- `npx themis migrate jest --rewrite-imports`: rewrites matched Jest/Vitest/Testing Library imports to a local `themis.compat.js` bridge file.
|
|
275
275
|
- `npx themis migrate jest --convert`: applies codemods for common Jest/Vitest matcher/import patterns so suites move closer to native Themis style.
|
|
276
|
-
- `npx themis migrate vitest`: scaffolds the same bridge for Vitest suites and gitignores `.themis/`.
|
|
276
|
+
- `npx themis migrate vitest`: scaffolds the same bridge for Vitest suites and gitignores `.themis/` plus `__themis__/reports/` and `__themis__/shims/`.
|
|
277
277
|
- `npx themis generate src --require-confidence high`: enforces a quality bar for all selected generated tests.
|
|
278
278
|
- `npx themis generate src --files src/routes/ping.ts`: targets one or more explicit source files.
|
|
279
279
|
- `npx themis generate src --match-source "routes/" --match-export "GET|POST"`: narrows generation by source path and exported symbol.
|
|
@@ -344,7 +344,8 @@ Themis writes artifacts under `.themis/`:
|
|
|
344
344
|
- `themis.compat.js`: optional local compat bridge for rewritten migration imports.
|
|
345
345
|
- `.themis/benchmarks/benchmark-last.json`: latest benchmark comparison payload, including migration proof output.
|
|
346
346
|
- `.themis/benchmarks/migration-proof.json`: synthetic migration-conversion proof artifact emitted by `npm run benchmark`.
|
|
347
|
-
-
|
|
347
|
+
- `__themis__/reports/report.html`: interactive HTML verdict report.
|
|
348
|
+
- `__themis__/shims/`: reserved namespace for framework-owned compatibility shims when a fallback file is truly needed. Themis should prefer built-in support first and should not drop ad hoc shim files into `tests/`.
|
|
348
349
|
|
|
349
350
|
`--agent` output includes deterministic failure fingerprints, grouped `analysis.failureClusters`, stability classifications, previous-run comparison data, and a direct generated-test repair hint via `npx themis test --fix`. Fix handoff entries also carry repair strategies, candidate files, and autofix commands for tighter failure-to-fix loops.
|
|
350
351
|
|
|
@@ -358,7 +359,7 @@ The repo now includes a thin VS Code extension scaffold at [`packages/themis-vsc
|
|
|
358
359
|
|
|
359
360
|
The extension is intentionally artifact-driven:
|
|
360
361
|
|
|
361
|
-
- reads `.themis/runs/last-run.json`, `.themis/runs/failed-tests.json`, `.themis/diffs/run-diff.json`, `.themis/generate/generate-last.json`, `.themis/generate/generate-map.json`, `.themis/generate/generate-backlog.json`, and
|
|
362
|
+
- reads `.themis/runs/last-run.json`, `.themis/runs/failed-tests.json`, `.themis/diffs/run-diff.json`, `.themis/generate/generate-last.json`, `.themis/generate/generate-map.json`, `.themis/generate/generate-backlog.json`, and `__themis__/reports/report.html`
|
|
362
363
|
- shows the latest verdict and failures in a sidebar
|
|
363
364
|
- adds generated-review navigation for source/test/hint mappings plus unresolved generation backlog
|
|
364
365
|
- reruns Themis from VS Code commands
|
|
@@ -496,18 +497,21 @@ Easter egg aliases are also available: `cook`, `yeet`, `vibecheck`, `wipe`.
|
|
|
496
497
|
```json
|
|
497
498
|
{
|
|
498
499
|
"testDir": "tests",
|
|
500
|
+
"generatedTestsDir": "__themis__/tests",
|
|
499
501
|
"testRegex": "\\.(test|spec)\\.(js|jsx|ts|tsx)$",
|
|
500
502
|
"maxWorkers": 7,
|
|
501
503
|
"reporter": "next",
|
|
502
504
|
"environment": "node",
|
|
503
505
|
"setupFiles": ["tests/setup.ts"],
|
|
504
506
|
"tsconfigPath": "tsconfig.json",
|
|
505
|
-
"
|
|
507
|
+
"htmlReportPath": "__themis__/reports/report.html",
|
|
508
|
+
"testIgnore": ["^tests/fixtures(?:/|$)"]
|
|
506
509
|
}
|
|
507
510
|
```
|
|
508
511
|
|
|
509
512
|
Modern JS/TS projects can opt into `environment: "jsdom"` for DOM-driven tests and `setupFiles` for hooks, polyfills, or harness bootstrapping.
|
|
510
|
-
|
|
513
|
+
Themis discovers both `testDir` and `generatedTestsDir` by default. Use `testIgnore` only for fixture folders, scratch suites, or other paths you intentionally want to skip.
|
|
514
|
+
Themis also stubs common frontend style and asset imports under Node or jsdom runs, including `.css`, `.scss`, `.png`, `.jpg`, `.svg`, and common font/media files, so repos should not need ad hoc `tests/*.cjs` setup files just to make those imports load.
|
|
511
515
|
|
|
512
516
|
## TypeScript
|
|
513
517
|
|
package/docs/agents-adoption.md
CHANGED
|
@@ -14,8 +14,8 @@ npx themis test
|
|
|
14
14
|
What those commands do:
|
|
15
15
|
|
|
16
16
|
- `npm install -D @vitronai/themis`: installs Themis as the repo's unit test framework
|
|
17
|
-
- `npx themis init`: creates `themis.config.json` and adds `.themis/` to `.gitignore`
|
|
18
|
-
- `npx themis generate src`: generates deterministic unit tests for JS/TS exports under `tests
|
|
17
|
+
- `npx themis init`: creates `themis.config.json` and adds `.themis/`, `__themis__/reports/`, and `__themis__/shims/` to `.gitignore`
|
|
18
|
+
- `npx themis generate src`: generates deterministic unit tests for JS/TS exports under `__themis__/tests`, using `.generated.test.ts` for TS/TSX sources and `.generated.test.js` for JS/JSX sources
|
|
19
19
|
- `npx themis test`: runs the suite
|
|
20
20
|
|
|
21
21
|
## Migrate From Jest Or Vitest
|
|
@@ -52,8 +52,9 @@ Install with `npm install -D @vitronai/themis`.
|
|
|
52
52
|
Initialize with `npx themis init`.
|
|
53
53
|
Generate tests with `npx themis generate src`.
|
|
54
54
|
Run tests with `npx themis test`.
|
|
55
|
-
Keep `.themis/` in `.gitignore`.
|
|
56
|
-
Expect generated tests to land under `tests
|
|
55
|
+
Keep `.themis/`, `__themis__/reports/`, and `__themis__/shims/` in `.gitignore`.
|
|
56
|
+
Expect generated tests to land under `__themis__/tests` with `.generated.test.ts` for TS/TSX sources and `.generated.test.js` for JS/JSX sources.
|
|
57
|
+
Do not create ad hoc `tests/*.cjs` or `tests/*.setup.*` files just to load common `.css`, `.png`, `.jpg`, `.svg`, or font/media imports.
|
|
57
58
|
For migrated suites, use `npx themis migrate jest` or `npx themis migrate vitest`.
|
|
58
59
|
Prefer `intent(...)` for behavior and workflow tests.
|
|
59
60
|
Prefer `test(...)` for low-level unit checks.
|
|
@@ -64,5 +65,8 @@ Do not claim Themis is "not a unit test framework".
|
|
|
64
65
|
|
|
65
66
|
- Themis is a unit test framework and test generator for Node.js and TypeScript projects.
|
|
66
67
|
- `.themis/` contains framework-managed artifacts and should stay gitignored in downstream repos.
|
|
68
|
+
- `__themis__/reports/` contains generated HTML report output and should stay gitignored in downstream repos.
|
|
69
|
+
- `__themis__/shims/` is the reserved location for framework-owned fallback shims if they are ever needed; Themis should not create ad hoc shim files under `tests/`.
|
|
70
|
+
- Themis handles common style and static asset imports natively; `setupFiles` should be reserved for real harness/bootstrap needs.
|
|
67
71
|
- This repository's own [`AGENTS.md`](../AGENTS.md) is for contributors working on Themis itself. It does not automatically configure external repos.
|
|
68
72
|
- You do not need an MCP server to use Themis from another repo. Clear repo instructions plus the normal CLI commands are enough.
|
package/docs/api.md
CHANGED
|
@@ -15,7 +15,7 @@ npx themis generate src
|
|
|
15
15
|
npx themis test
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
`npx themis generate src` writes generated tests under `tests
|
|
18
|
+
`npx themis generate src` writes generated tests under `__themis__/tests` by default.
|
|
19
19
|
|
|
20
20
|
For downstream repo setup and copyable agent instructions, see [`docs/agents-adoption.md`](agents-adoption.md) and [`templates/AGENTS.themis.md`](../templates/AGENTS.themis.md).
|
|
21
21
|
|
|
@@ -35,7 +35,7 @@ themis migrate <jest|vitest>
|
|
|
35
35
|
Creates:
|
|
36
36
|
|
|
37
37
|
- `themis.config.json` with default settings
|
|
38
|
-
- adds `.themis/` to `.gitignore`
|
|
38
|
+
- adds `.themis/`, `__themis__/reports/`, and `__themis__/shims/` to `.gitignore`
|
|
39
39
|
|
|
40
40
|
## `themis test`
|
|
41
41
|
|
|
@@ -58,7 +58,7 @@ Themis uses generation and explicit assertions as a contract-first alternative t
|
|
|
58
58
|
Default behavior:
|
|
59
59
|
|
|
60
60
|
- input directory: `src`
|
|
61
|
-
- output directory: `tests
|
|
61
|
+
- output directory: `__themis__/tests`
|
|
62
62
|
- generated files mirror the scanned source tree with `*.generated.test.ts` for TS/TSX sources and `*.generated.test.js` for JS/JSX sources
|
|
63
63
|
- generated tests import their shared contract runtime from `@vitronai/themis/contract-runtime` instead of writing framework helper files into the repo
|
|
64
64
|
- generated tests assert normalized runtime export contracts directly in generated source
|
|
@@ -96,7 +96,7 @@ Default behavior:
|
|
|
96
96
|
| `--match-export <regex>` | string | Filter candidate source files by exported symbol regex. |
|
|
97
97
|
| `--include <regex>` | string | Include only source files whose relative path matches regex. |
|
|
98
98
|
| `--exclude <regex>` | string | Exclude source files whose relative path matches regex. |
|
|
99
|
-
| `--output <path>` | string | Output directory for generated tests (default: `tests
|
|
99
|
+
| `--output <path>` | string | Output directory for generated tests (default: `__themis__/tests`). |
|
|
100
100
|
| `--force` | flag | Replace conflicting files that were not created by a prior Themis scan. |
|
|
101
101
|
|
|
102
102
|
Per-file hint sidecars are supported via `<source>.themis.json`. These can provide:
|
|
@@ -202,7 +202,7 @@ Migration options:
|
|
|
202
202
|
| `--update-contracts` | flag | Accept updated `captureContract(...)` baselines for the selected tests. |
|
|
203
203
|
| `-w`, `--watch` | flag | Rerun the selected suite when watched project files change. |
|
|
204
204
|
| `--stability <N>` | positive integer | Run selected tests `N` times and classify stability (`stable_pass`, `stable_fail`, `unstable`). |
|
|
205
|
-
| `--html-output <path>` | string | Output path for `--reporter html` (default:
|
|
205
|
+
| `--html-output <path>` | string | Output path for `--reporter html` (default: `__themis__/reports/report.html`). |
|
|
206
206
|
| `--match "<regex>"` | string | Run only tests whose full name matches regex. |
|
|
207
207
|
| `--rerun-failed` | flag | Rerun failures from `.themis/runs/failed-tests.json`. |
|
|
208
208
|
| `--fix` | flag | Apply generated-test autofixes from `.themis/runs/fix-handoff.json` and rerun the suite. |
|
|
@@ -278,7 +278,7 @@ Formal schemas:
|
|
|
278
278
|
|
|
279
279
|
Human-facing artifact:
|
|
280
280
|
|
|
281
|
-
-
|
|
281
|
+
- `__themis__/reports/report.html`: interactive HTML verdict report
|
|
282
282
|
|
|
283
283
|
Agent payload details:
|
|
284
284
|
|
|
@@ -339,14 +339,22 @@ The fake timer helpers only patch the current Themis runtime. They do not mutate
|
|
|
339
339
|
| Field | Type | Default | Notes |
|
|
340
340
|
| --- | --- | --- | --- |
|
|
341
341
|
| `testDir` | string | `"tests"` | Root directory for discovery. |
|
|
342
|
+
| `generatedTestsDir` | string | `"__themis__/tests"` | Additional discovery root for generated Themis suites. |
|
|
342
343
|
| `testRegex` | string | `"\\.(test\|spec)\\.(js\|jsx\|ts\|tsx)$"` | Regex for filenames. |
|
|
343
344
|
| `maxWorkers` | integer | `max(1, cpuCount - 1)` | Parallel worker limit. |
|
|
344
345
|
| `reporter` | `spec\|next\|json\|agent\|html` | `"next"` | Default reporter when no CLI override is set. |
|
|
345
346
|
| `environment` | `node\|jsdom` | `"node"` | Test runtime environment. |
|
|
346
347
|
| `setupFiles` | `string[]` | `[]` | Files loaded before each test file. |
|
|
347
348
|
| `tsconfigPath` | `string \| null` | `"tsconfig.json"` | Project tsconfig used for TSX and alias-aware transpilation. |
|
|
349
|
+
| `htmlReportPath` | string | `"__themis__/reports/report.html"` | Default output path for `--reporter html` when `--html-output` is not provided. |
|
|
348
350
|
| `testIgnore` | `string[]` | `[]` | Regex strings matched against repo-relative paths during discovery. Matching files and directories are skipped. |
|
|
349
351
|
|
|
352
|
+
Runtime loader note:
|
|
353
|
+
|
|
354
|
+
- Themis handles common frontend style imports (`.css`, `.scss`, `.sass`, `.less`, `.styl`, `.pcss`) and common static assets (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.avif`, `.bmp`, `.ico`, `.svg`, font/media files) without extra setup.
|
|
355
|
+
- Use `setupFiles` for actual harness bootstrapping, not as a workaround for CSS or image imports.
|
|
356
|
+
- If Themis ever needs to emit a framework-owned fallback shim file, that file belongs under `__themis__/shims/`, not under `tests/`.
|
|
357
|
+
|
|
350
358
|
## Programmatic API
|
|
351
359
|
|
|
352
360
|
Import:
|
|
@@ -450,7 +458,8 @@ Loads `themis.config.json` and merges with defaults.
|
|
|
450
458
|
|
|
451
459
|
Discovery note:
|
|
452
460
|
|
|
453
|
-
-
|
|
461
|
+
- Themis discovers both `testDir` and `generatedTestsDir` by default.
|
|
462
|
+
- `testIgnore` is applied to repo-relative file and directory paths before descent, so use it only for paths you intentionally want to skip, such as fixtures or scratch suites.
|
|
454
463
|
|
|
455
464
|
## `initConfig(cwd): void`
|
|
456
465
|
|
package/docs/roadmap.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
1. **Provider/flow depth**
|
|
4
4
|
- Expand `generate` detection to infer React Query usage, router contexts, and persisted store slices so generated tests wrap components/hooks with accurate provider shells.
|
|
5
|
-
- Surface async DOM flows in `tests
|
|
5
|
+
- Surface async DOM flows in `__themis__/tests/*` with multi-stage user journeys, empty/loading/error states, and configurable timing fixtures so the runner can validate realistic UI transitions instead of static renders.
|
|
6
6
|
- Add documentation (and optionally VS Code actions) that show how to hook a project-level `themis.generate.js` or `.themis.json` provider configuration for shared auth/session/React Query clients.
|
|
7
7
|
|
|
8
8
|
2. **Migration helpers**
|
package/docs/vscode-extension.md
CHANGED
|
@@ -24,7 +24,7 @@ The scaffold currently supports:
|
|
|
24
24
|
- reading `.themis/generate/generate-last.json`
|
|
25
25
|
- reading `.themis/generate/generate-map.json`
|
|
26
26
|
- reading `.themis/generate/generate-backlog.json`
|
|
27
|
-
- opening
|
|
27
|
+
- opening `__themis__/reports/report.html` in a webview
|
|
28
28
|
- surfacing generated source/test/hint mappings in the sidebar
|
|
29
29
|
- surfacing unresolved generate backlog and gate state in the sidebar
|
|
30
30
|
- refreshing when `.themis/**` files change
|
package/docs/why-themis.md
CHANGED
|
@@ -17,7 +17,7 @@ npx themis generate src
|
|
|
17
17
|
npx themis test
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
`npx themis init` adds `.themis/` to `.gitignore`, and `npx themis generate src` emits `.generated.test.ts` for TS/TSX sources and `.generated.test.js` for JS/JSX sources
|
|
20
|
+
`npx themis init` adds `.themis/` and `__themis__/reports/` to `.gitignore`, and `npx themis generate src` emits `.generated.test.ts` for TS/TSX sources and `.generated.test.js` for JS/JSX sources under `__themis__/tests`.
|
|
21
21
|
|
|
22
22
|
For downstream repo instructions and a copyable `AGENTS.md` template, see [`docs/agents-adoption.md`](agents-adoption.md).
|
|
23
23
|
|
package/index.d.ts
CHANGED
|
@@ -84,12 +84,14 @@ export interface RunOptions {
|
|
|
84
84
|
|
|
85
85
|
export interface ThemisConfig {
|
|
86
86
|
testDir: string;
|
|
87
|
+
generatedTestsDir: string;
|
|
87
88
|
testRegex: string;
|
|
88
89
|
maxWorkers: number;
|
|
89
90
|
reporter: string;
|
|
90
91
|
environment: TestEnvironment;
|
|
91
92
|
setupFiles: string[];
|
|
92
93
|
tsconfigPath: string | null;
|
|
94
|
+
htmlReportPath: string;
|
|
93
95
|
testIgnore: string[];
|
|
94
96
|
}
|
|
95
97
|
|
package/package.json
CHANGED
package/src/artifact-paths.js
CHANGED
|
@@ -1,72 +1,78 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
|
|
3
3
|
const ARTIFACT_DIR = '.themis';
|
|
4
|
+
const USER_OUTPUT_DIR = '__themis__';
|
|
4
5
|
const RUNS_DIR = ['runs'];
|
|
5
6
|
const DIFFS_DIR = ['diffs'];
|
|
6
7
|
const GENERATE_DIR = ['generate'];
|
|
7
|
-
const REPORTS_DIR = ['reports'];
|
|
8
8
|
const MIGRATION_DIR = ['migration'];
|
|
9
9
|
const BENCHMARKS_DIR = ['benchmarks'];
|
|
10
|
+
const REPORTS_DIR = ['reports'];
|
|
10
11
|
const SHOWCASE_COMPARISON_DIR = [...BENCHMARKS_DIR, 'showcase-comparison'];
|
|
11
12
|
const MIGRATION_FIXTURES_DIR = [...MIGRATION_DIR, 'fixtures'];
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
-
lastRun: [...RUNS_DIR, 'last-run.json'],
|
|
15
|
-
failedTests: [...RUNS_DIR, 'failed-tests.json'],
|
|
16
|
-
runDiff: [...DIFFS_DIR, 'run-diff.json'],
|
|
17
|
-
runHistory: [...RUNS_DIR, 'run-history.json'],
|
|
18
|
-
fixHandoff: [...RUNS_DIR, 'fix-handoff.json'],
|
|
19
|
-
contractDiff: [...DIFFS_DIR, 'contract-diff.json'],
|
|
20
|
-
generateMap: [...GENERATE_DIR, 'generate-map.json'],
|
|
21
|
-
generateResult: [...GENERATE_DIR, 'generate-last.json'],
|
|
22
|
-
generateHandoff: [...GENERATE_DIR, 'generate-handoff.json'],
|
|
23
|
-
generateBacklog: [...GENERATE_DIR, 'generate-backlog.json'],
|
|
24
|
-
migrationReport: [...MIGRATION_DIR, 'migration-report.json'],
|
|
25
|
-
htmlReport: [...REPORTS_DIR, 'report.html'],
|
|
26
|
-
benchmarkLast: [...BENCHMARKS_DIR, 'benchmark-last.json'],
|
|
27
|
-
migrationProof: [...BENCHMARKS_DIR, 'migration-proof.json']
|
|
14
|
+
const ARTIFACT_LOCATIONS = Object.freeze({
|
|
15
|
+
lastRun: { root: ARTIFACT_DIR, segments: [...RUNS_DIR, 'last-run.json'] },
|
|
16
|
+
failedTests: { root: ARTIFACT_DIR, segments: [...RUNS_DIR, 'failed-tests.json'] },
|
|
17
|
+
runDiff: { root: ARTIFACT_DIR, segments: [...DIFFS_DIR, 'run-diff.json'] },
|
|
18
|
+
runHistory: { root: ARTIFACT_DIR, segments: [...RUNS_DIR, 'run-history.json'] },
|
|
19
|
+
fixHandoff: { root: ARTIFACT_DIR, segments: [...RUNS_DIR, 'fix-handoff.json'] },
|
|
20
|
+
contractDiff: { root: ARTIFACT_DIR, segments: [...DIFFS_DIR, 'contract-diff.json'] },
|
|
21
|
+
generateMap: { root: ARTIFACT_DIR, segments: [...GENERATE_DIR, 'generate-map.json'] },
|
|
22
|
+
generateResult: { root: ARTIFACT_DIR, segments: [...GENERATE_DIR, 'generate-last.json'] },
|
|
23
|
+
generateHandoff: { root: ARTIFACT_DIR, segments: [...GENERATE_DIR, 'generate-handoff.json'] },
|
|
24
|
+
generateBacklog: { root: ARTIFACT_DIR, segments: [...GENERATE_DIR, 'generate-backlog.json'] },
|
|
25
|
+
migrationReport: { root: ARTIFACT_DIR, segments: [...MIGRATION_DIR, 'migration-report.json'] },
|
|
26
|
+
htmlReport: { root: USER_OUTPUT_DIR, segments: [...REPORTS_DIR, 'report.html'] },
|
|
27
|
+
benchmarkLast: { root: ARTIFACT_DIR, segments: [...BENCHMARKS_DIR, 'benchmark-last.json'] },
|
|
28
|
+
migrationProof: { root: ARTIFACT_DIR, segments: [...BENCHMARKS_DIR, 'migration-proof.json'] }
|
|
28
29
|
});
|
|
29
30
|
|
|
30
|
-
const
|
|
31
|
-
lastRun: ['last-run.json'],
|
|
32
|
-
failedTests: ['failed-tests.json'],
|
|
33
|
-
runDiff: ['run-diff.json'],
|
|
34
|
-
runHistory: ['run-history.json'],
|
|
35
|
-
fixHandoff: ['fix-handoff.json'],
|
|
36
|
-
contractDiff: ['contract-diff.json'],
|
|
37
|
-
generateMap: ['generate-map.json'],
|
|
38
|
-
generateResult: ['generate-last.json'],
|
|
39
|
-
generateHandoff: ['generate-handoff.json'],
|
|
40
|
-
generateBacklog: ['generate-backlog.json'],
|
|
41
|
-
migrationReport: ['migration-report.json'],
|
|
42
|
-
htmlReport: ['report.html'],
|
|
43
|
-
benchmarkLast: ['benchmark-last.json'],
|
|
44
|
-
migrationProof: ['migration-proof.json']
|
|
31
|
+
const LEGACY_ARTIFACT_LOCATIONS = Object.freeze({
|
|
32
|
+
lastRun: { root: ARTIFACT_DIR, segments: ['last-run.json'] },
|
|
33
|
+
failedTests: { root: ARTIFACT_DIR, segments: ['failed-tests.json'] },
|
|
34
|
+
runDiff: { root: ARTIFACT_DIR, segments: ['run-diff.json'] },
|
|
35
|
+
runHistory: { root: ARTIFACT_DIR, segments: ['run-history.json'] },
|
|
36
|
+
fixHandoff: { root: ARTIFACT_DIR, segments: ['fix-handoff.json'] },
|
|
37
|
+
contractDiff: { root: ARTIFACT_DIR, segments: ['contract-diff.json'] },
|
|
38
|
+
generateMap: { root: ARTIFACT_DIR, segments: ['generate-map.json'] },
|
|
39
|
+
generateResult: { root: ARTIFACT_DIR, segments: ['generate-last.json'] },
|
|
40
|
+
generateHandoff: { root: ARTIFACT_DIR, segments: ['generate-handoff.json'] },
|
|
41
|
+
generateBacklog: { root: ARTIFACT_DIR, segments: ['generate-backlog.json'] },
|
|
42
|
+
migrationReport: { root: ARTIFACT_DIR, segments: ['migration-report.json'] },
|
|
43
|
+
htmlReport: { root: ARTIFACT_DIR, segments: ['report.html'] },
|
|
44
|
+
benchmarkLast: { root: ARTIFACT_DIR, segments: ['benchmark-last.json'] },
|
|
45
|
+
migrationProof: { root: ARTIFACT_DIR, segments: ['migration-proof.json'] }
|
|
45
46
|
});
|
|
46
47
|
|
|
47
|
-
function joinRelative(
|
|
48
|
-
return path.posix.join(
|
|
48
|
+
function joinRelative(location) {
|
|
49
|
+
return path.posix.join(location.root, ...location.segments);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
function joinAbsolute(cwd,
|
|
52
|
-
return path.join(cwd,
|
|
52
|
+
function joinAbsolute(cwd, location) {
|
|
53
|
+
return path.join(cwd, location.root, ...location.segments);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
function buildRelativePathMap(
|
|
56
|
+
function buildRelativePathMap(locationMap) {
|
|
56
57
|
return Object.fromEntries(
|
|
57
|
-
Object.entries(
|
|
58
|
+
Object.entries(locationMap).map(([key, location]) => [key, joinRelative(location)])
|
|
58
59
|
);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
const ARTIFACT_RELATIVE_PATHS = Object.freeze(buildRelativePathMap(
|
|
62
|
-
const LEGACY_ARTIFACT_RELATIVE_PATHS = Object.freeze(buildRelativePathMap(
|
|
62
|
+
const ARTIFACT_RELATIVE_PATHS = Object.freeze(buildRelativePathMap(ARTIFACT_LOCATIONS));
|
|
63
|
+
const LEGACY_ARTIFACT_RELATIVE_PATHS = Object.freeze(buildRelativePathMap(LEGACY_ARTIFACT_LOCATIONS));
|
|
64
|
+
const ARTIFACT_SEGMENTS = Object.freeze(
|
|
65
|
+
Object.fromEntries(
|
|
66
|
+
Object.entries(ARTIFACT_LOCATIONS).map(([key, location]) => [key, [...location.segments]])
|
|
67
|
+
)
|
|
68
|
+
);
|
|
63
69
|
|
|
64
70
|
function resolveArtifactPath(cwd, key) {
|
|
65
|
-
return joinAbsolute(cwd,
|
|
71
|
+
return joinAbsolute(cwd, ARTIFACT_LOCATIONS[key]);
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
function resolveLegacyArtifactPath(cwd, key) {
|
|
69
|
-
return joinAbsolute(cwd,
|
|
75
|
+
return joinAbsolute(cwd, LEGACY_ARTIFACT_LOCATIONS[key]);
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
function resolveArtifactDir(cwd, ...segments) {
|
|
@@ -79,7 +85,7 @@ function resolveRelativeDir(...segments) {
|
|
|
79
85
|
|
|
80
86
|
function getArtifactPaths(cwd) {
|
|
81
87
|
return Object.fromEntries(
|
|
82
|
-
Object.keys(
|
|
88
|
+
Object.keys(ARTIFACT_LOCATIONS).map((key) => [key, resolveArtifactPath(cwd, key)])
|
|
83
89
|
);
|
|
84
90
|
}
|
|
85
91
|
|
|
@@ -91,6 +97,7 @@ function getArtifactPathCandidates(cwd, key) {
|
|
|
91
97
|
|
|
92
98
|
module.exports = {
|
|
93
99
|
ARTIFACT_DIR,
|
|
100
|
+
USER_OUTPUT_DIR,
|
|
94
101
|
RUNS_DIR,
|
|
95
102
|
DIFFS_DIR,
|
|
96
103
|
GENERATE_DIR,
|
package/src/cli.js
CHANGED
|
@@ -79,7 +79,7 @@ async function main(argv) {
|
|
|
79
79
|
console.log(`Scripts: updated ${formatCliPath(cwd, result.packageJsonPath)} with test:themis`);
|
|
80
80
|
}
|
|
81
81
|
if (result.gitignoreUpdated) {
|
|
82
|
-
|
|
82
|
+
console.log(`Gitignore: updated ${formatCliPath(cwd, result.gitignorePath)} with .themis/, __themis__/reports/, and __themis__/shims/`);
|
|
83
83
|
}
|
|
84
84
|
if (result.rewriteImports) {
|
|
85
85
|
console.log(`Imports: rewrote ${result.rewrittenFiles.length} file(s) to local Themis compatibility imports.`);
|
|
@@ -710,7 +710,7 @@ function resolveStabilityRuns(value) {
|
|
|
710
710
|
function printUsage() {
|
|
711
711
|
console.log('Usage: themis <command> [options]');
|
|
712
712
|
console.log('Commands:');
|
|
713
|
-
console.log(' init Create themis.config.json and gitignore .themis/
|
|
713
|
+
console.log(' init Create themis.config.json and gitignore .themis/ plus __themis__/reports/ and __themis__/shims/');
|
|
714
714
|
console.log(' generate [path] Scan source files and generate Themis contract tests');
|
|
715
715
|
console.log(' Options: [--json] [--plan] [--output path] [--files a,b] [--match-source regex] [--match-export regex] [--scenario name] [--min-confidence level] [--require-confidence level] [--include regex] [--exclude regex] [--review] [--update] [--clean] [--changed] [--force] [--strict] [--write-hints] [--fail-on-skips] [--fail-on-conflicts]');
|
|
716
716
|
console.log(' scan [path] Alias for generate');
|
package/src/config.js
CHANGED
|
@@ -4,12 +4,14 @@ const os = require('os');
|
|
|
4
4
|
|
|
5
5
|
const DEFAULT_CONFIG = {
|
|
6
6
|
testDir: 'tests',
|
|
7
|
+
generatedTestsDir: path.join('__themis__', 'tests'),
|
|
7
8
|
testRegex: '\\.(test|spec)\\.(js|jsx|ts|tsx)$',
|
|
8
9
|
maxWorkers: Math.max(1, os.cpus().length - 1),
|
|
9
10
|
reporter: 'next',
|
|
10
11
|
environment: 'node',
|
|
11
12
|
setupFiles: [],
|
|
12
13
|
tsconfigPath: 'tsconfig.json',
|
|
14
|
+
htmlReportPath: path.join('__themis__', 'reports', 'report.html'),
|
|
13
15
|
testIgnore: []
|
|
14
16
|
};
|
|
15
17
|
|
|
@@ -44,6 +46,14 @@ function normalizeConfig(config) {
|
|
|
44
46
|
throw new Error('Invalid config tsconfigPath value: expected a string path or null.');
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
if (typeof config.generatedTestsDir !== 'string' || config.generatedTestsDir.trim().length === 0) {
|
|
50
|
+
throw new Error('Invalid config generatedTestsDir value: expected a non-empty string path.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (typeof config.htmlReportPath !== 'string' || config.htmlReportPath.trim().length === 0) {
|
|
54
|
+
throw new Error('Invalid config htmlReportPath value: expected a non-empty string path.');
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
if (!Array.isArray(config.testIgnore) || !config.testIgnore.every((entry) => typeof entry === 'string')) {
|
|
48
58
|
throw new Error('Invalid config testIgnore value: expected an array of regex strings.');
|
|
49
59
|
}
|
package/src/discovery.js
CHANGED
|
@@ -2,17 +2,18 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
4
|
function discoverTests(cwd, config) {
|
|
5
|
-
const start = path.resolve(cwd, config.testDir);
|
|
6
5
|
const regex = new RegExp(config.testRegex);
|
|
7
6
|
const ignored = compileIgnorePatterns(config.testIgnore);
|
|
8
|
-
const files =
|
|
7
|
+
const files = new Set();
|
|
8
|
+
const searchRoots = resolveSearchRoots(cwd, config);
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
for (const start of searchRoots) {
|
|
11
|
+
if (!fs.existsSync(start)) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
walk(start, regex, ignored, files, cwd);
|
|
12
15
|
}
|
|
13
|
-
|
|
14
|
-
walk(start, regex, ignored, files, cwd);
|
|
15
|
-
return files.sort();
|
|
16
|
+
return [...files].sort();
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function walk(dir, regex, ignored, files, cwd) {
|
|
@@ -31,11 +32,20 @@ function walk(dir, regex, ignored, files, cwd) {
|
|
|
31
32
|
continue;
|
|
32
33
|
}
|
|
33
34
|
if (entry.isFile() && regex.test(entry.name)) {
|
|
34
|
-
files.
|
|
35
|
+
files.add(fullPath);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
function resolveSearchRoots(cwd, config) {
|
|
41
|
+
const candidates = [config.testDir, config.generatedTestsDir]
|
|
42
|
+
.map((entry) => String(entry || '').trim())
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.map((entry) => path.resolve(cwd, entry));
|
|
45
|
+
|
|
46
|
+
return [...new Set(candidates)];
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
function compileIgnorePatterns(patterns) {
|
|
40
50
|
if (!Array.isArray(patterns) || patterns.length === 0) {
|
|
41
51
|
return [];
|
package/src/generate.js
CHANGED
|
@@ -3,6 +3,7 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { execFileSync } = require('child_process');
|
|
5
5
|
const ts = require('typescript');
|
|
6
|
+
const { loadConfig } = require('./config');
|
|
6
7
|
const { ARTIFACT_RELATIVE_PATHS } = require('./artifact-paths');
|
|
7
8
|
|
|
8
9
|
const GENERATED_MARKER = '// Generated by Themis code scan. Do not edit manually.';
|
|
@@ -44,10 +45,11 @@ const SCAFFOLD_HINT_META = Object.freeze({
|
|
|
44
45
|
const GENERATED_HELPER_MODULE = '@vitronai/themis/contract-runtime';
|
|
45
46
|
function generateTestsFromSource(cwd, options = {}) {
|
|
46
47
|
const projectRoot = path.resolve(cwd || process.cwd());
|
|
48
|
+
const config = loadConfig(projectRoot);
|
|
47
49
|
const normalizedOptions = normalizeGenerateOptions(projectRoot, options);
|
|
48
50
|
const projectProviders = loadProjectProviders(projectRoot);
|
|
49
51
|
const scanTarget = resolveScanTarget(projectRoot, normalizedOptions.targetDir || 'src');
|
|
50
|
-
const outputDir = path.resolve(projectRoot, normalizedOptions.outputDir ||
|
|
52
|
+
const outputDir = path.resolve(projectRoot, normalizedOptions.outputDir || config.generatedTestsDir);
|
|
51
53
|
const helperFile = GENERATED_HELPER_MODULE;
|
|
52
54
|
const legacyHelperFile = path.join(outputDir, GENERATED_HELPER_NAME);
|
|
53
55
|
const mapFile = path.resolve(projectRoot, GENERATED_MAP_ARTIFACT);
|
package/src/init.js
CHANGED
package/src/migrate.js
CHANGED
|
@@ -45,7 +45,7 @@ function runMigrate(cwd, framework, options = {}) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, 'utf8');
|
|
48
|
-
const gitignore = ensureGitignoreEntries(projectRoot, ['.themis/']);
|
|
48
|
+
const gitignore = ensureGitignoreEntries(projectRoot, ['.themis/', '__themis__/reports/', '__themis__/shims/']);
|
|
49
49
|
|
|
50
50
|
let packageUpdated = false;
|
|
51
51
|
if (fs.existsSync(packageJsonPath)) {
|
package/src/module-loader.js
CHANGED
|
@@ -4,6 +4,28 @@ const Module = require('module');
|
|
|
4
4
|
|
|
5
5
|
const SUPPORTED_SOURCE_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx'];
|
|
6
6
|
const RESOLVABLE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.json'];
|
|
7
|
+
const STYLE_IMPORT_EXTENSIONS = new Set(['.css', '.scss', '.sass', '.less', '.styl', '.pcss']);
|
|
8
|
+
const ASSET_IMPORT_EXTENSIONS = new Set([
|
|
9
|
+
'.png',
|
|
10
|
+
'.jpg',
|
|
11
|
+
'.jpeg',
|
|
12
|
+
'.gif',
|
|
13
|
+
'.webp',
|
|
14
|
+
'.avif',
|
|
15
|
+
'.bmp',
|
|
16
|
+
'.ico',
|
|
17
|
+
'.svg',
|
|
18
|
+
'.mp4',
|
|
19
|
+
'.webm',
|
|
20
|
+
'.mp3',
|
|
21
|
+
'.wav',
|
|
22
|
+
'.ogg',
|
|
23
|
+
'.woff',
|
|
24
|
+
'.woff2',
|
|
25
|
+
'.ttf',
|
|
26
|
+
'.otf',
|
|
27
|
+
'.eot'
|
|
28
|
+
]);
|
|
7
29
|
const THEMIS_CONTRACT_RUNTIME_REQUEST = '@vitronai/themis/contract-runtime';
|
|
8
30
|
const THEMIS_CONTRACT_RUNTIME_PATH = path.join(__dirname, 'contract-runtime.js');
|
|
9
31
|
const DEFAULT_TS_COMPILER_OPTIONS = {
|
|
@@ -102,6 +124,23 @@ function createModuleLoader(options = {}) {
|
|
|
102
124
|
return materializeMock(mockRegistry.get(resolvedRequest));
|
|
103
125
|
}
|
|
104
126
|
|
|
127
|
+
if (shouldStubStyleImport(resolvedRequest, projectRoot)) {
|
|
128
|
+
return createStyleModuleStub();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (shouldStubAssetImport(resolvedRequest, projectRoot)) {
|
|
132
|
+
return createAssetModuleStub(resolvedRequest);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (shouldRejectUnsupportedProjectImport(resolvedRequest, projectRoot)) {
|
|
136
|
+
const extension = path.extname(resolvedRequest).toLowerCase();
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Unsupported project import extension "${extension}" for ${formatProjectPath(projectRoot, resolvedRequest)}. ` +
|
|
139
|
+
'Themis handles JS/TS, JSON, common style imports, and common static assets natively. ' +
|
|
140
|
+
'Prefer extending Themis support over creating ad hoc test setup files.'
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
105
144
|
return originalLoad.call(this, resolvedRequest, parent, isMain);
|
|
106
145
|
};
|
|
107
146
|
|
|
@@ -478,6 +517,106 @@ function isBuiltinRequest(request) {
|
|
|
478
517
|
return Module.builtinModules.includes(request);
|
|
479
518
|
}
|
|
480
519
|
|
|
520
|
+
function shouldStubStyleImport(resolvedRequest, projectRoot) {
|
|
521
|
+
return shouldHandleProjectResolvedFile(resolvedRequest, projectRoot, STYLE_IMPORT_EXTENSIONS);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function shouldStubAssetImport(resolvedRequest, projectRoot) {
|
|
525
|
+
return shouldHandleProjectResolvedFile(resolvedRequest, projectRoot, ASSET_IMPORT_EXTENSIONS);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function shouldRejectUnsupportedProjectImport(resolvedRequest, projectRoot) {
|
|
529
|
+
if (!shouldHandleProjectFile(resolvedRequest, projectRoot)) {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (!fs.existsSync(resolvedRequest) || !fs.statSync(resolvedRequest).isFile()) {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const extension = path.extname(resolvedRequest).toLowerCase();
|
|
538
|
+
if (!extension) {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return !SUPPORTED_SOURCE_EXTENSIONS.includes(extension)
|
|
543
|
+
&& extension !== '.json'
|
|
544
|
+
&& !STYLE_IMPORT_EXTENSIONS.has(extension)
|
|
545
|
+
&& !ASSET_IMPORT_EXTENSIONS.has(extension);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function shouldHandleProjectResolvedFile(resolvedRequest, projectRoot, extensions) {
|
|
549
|
+
if (!shouldHandleProjectFile(resolvedRequest, projectRoot)) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (!fs.existsSync(resolvedRequest) || !fs.statSync(resolvedRequest).isFile()) {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return extensions.has(path.extname(resolvedRequest).toLowerCase());
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function formatProjectPath(projectRoot, filePath) {
|
|
561
|
+
return path.relative(projectRoot, filePath).split(path.sep).join('/');
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function createStyleModuleStub() {
|
|
565
|
+
const styleModule = {};
|
|
566
|
+
const proxy = new Proxy(styleModule, {
|
|
567
|
+
get(_target, property) {
|
|
568
|
+
if (property === '__esModule') {
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
if (property === 'default') {
|
|
572
|
+
return proxy;
|
|
573
|
+
}
|
|
574
|
+
if (property === 'toJSON') {
|
|
575
|
+
return () => ({});
|
|
576
|
+
}
|
|
577
|
+
if (property === Symbol.toPrimitive) {
|
|
578
|
+
return () => '';
|
|
579
|
+
}
|
|
580
|
+
if (typeof property === 'string') {
|
|
581
|
+
return property;
|
|
582
|
+
}
|
|
583
|
+
return undefined;
|
|
584
|
+
},
|
|
585
|
+
has(_target, property) {
|
|
586
|
+
return property === '__esModule' || property === 'default' || typeof property === 'string';
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
return proxy;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function createAssetModuleStub(resolvedRequest) {
|
|
594
|
+
const assetPath = resolvedRequest.split(path.sep).join('/');
|
|
595
|
+
const stub = {
|
|
596
|
+
__esModule: true,
|
|
597
|
+
default: assetPath,
|
|
598
|
+
href: assetPath,
|
|
599
|
+
path: assetPath,
|
|
600
|
+
src: assetPath
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
if (path.extname(resolvedRequest).toLowerCase() === '.svg') {
|
|
604
|
+
stub.ReactComponent = function ThemisSvgAsset(props = {}) {
|
|
605
|
+
return {
|
|
606
|
+
$$typeof: 'react.test.element',
|
|
607
|
+
type: 'svg',
|
|
608
|
+
key: null,
|
|
609
|
+
props: {
|
|
610
|
+
...props,
|
|
611
|
+
'data-themis-asset': assetPath
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return stub;
|
|
618
|
+
}
|
|
619
|
+
|
|
481
620
|
function transpileSource({ source, filename, compilerContext }) {
|
|
482
621
|
const { ts } = compilerContext;
|
|
483
622
|
const compilerOptions = {
|
package/src/reporter.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { buildStabilityReport } = require('./stability');
|
|
4
|
+
const { loadConfig } = require('./config');
|
|
4
5
|
const { ARTIFACT_RELATIVE_PATHS, resolveArtifactPath } = require('./artifact-paths');
|
|
5
6
|
|
|
6
7
|
const REPORT_LEXICONS = {
|
|
@@ -485,6 +486,10 @@ function fnv1a32(input) {
|
|
|
485
486
|
|
|
486
487
|
function resolveHtmlOutputPath(cwd, outputPath) {
|
|
487
488
|
if (!outputPath) {
|
|
489
|
+
const config = loadConfig(cwd);
|
|
490
|
+
if (config.htmlReportPath) {
|
|
491
|
+
return path.resolve(cwd, config.htmlReportPath);
|
|
492
|
+
}
|
|
488
493
|
return resolveArtifactPath(cwd, 'htmlReport');
|
|
489
494
|
}
|
|
490
495
|
if (path.isAbsolute(outputPath)) {
|
|
@@ -6,10 +6,11 @@ Use `@vitronai/themis` as this repository's unit test framework.
|
|
|
6
6
|
|
|
7
7
|
- Install with `npm install -D @vitronai/themis`.
|
|
8
8
|
- Initialize with `npx themis init`.
|
|
9
|
-
- Keep `.themis/` in `.gitignore`.
|
|
9
|
+
- Keep `.themis/`, `__themis__/reports/`, and `__themis__/shims/` in `.gitignore`.
|
|
10
10
|
- Generate tests with `npx themis generate src`.
|
|
11
|
-
- Expect generated tests under `tests
|
|
11
|
+
- Expect generated tests under `__themis__/tests`, using `.generated.test.ts` for TS/TSX sources and `.generated.test.js` for JS/JSX sources.
|
|
12
12
|
- Run tests with `npx themis test`.
|
|
13
|
+
- Do not create ad hoc `tests/*.cjs` or `tests/*.setup.*` files just to load common `.css`, `.png`, `.jpg`, `.svg`, or font/media imports.
|
|
13
14
|
|
|
14
15
|
## Migration
|
|
15
16
|
|
|
@@ -24,7 +25,8 @@ Use `@vitronai/themis` as this repository's unit test framework.
|
|
|
24
25
|
- Prefer `test(...)` for low-level unit checks.
|
|
25
26
|
- Prefer deterministic assertions over snapshot-style baselines.
|
|
26
27
|
- Treat `.themis/` as Themis-managed artifact output.
|
|
27
|
-
- Treat
|
|
28
|
+
- Treat `__themis__/shims/` as the reserved location for framework-owned fallback shims if a shim is ever truly needed.
|
|
29
|
+
- Treat generated tests under `__themis__/tests` as Themis-managed output unless the repo says otherwise.
|
|
28
30
|
|
|
29
31
|
## Agent Behavior
|
|
30
32
|
|
|
@@ -32,3 +34,5 @@ Use `@vitronai/themis` as this repository's unit test framework.
|
|
|
32
34
|
- Do not claim Themis is "not a unit test framework".
|
|
33
35
|
- When asked to add tests from scratch, use `npx themis generate src` before hand-writing additional coverage.
|
|
34
36
|
- When asked to work with existing Jest/Vitest suites, prefer incremental migration before large manual rewrites.
|
|
37
|
+
- Do not scaffold setup files just to shim common style or asset imports; Themis handles those natively.
|
|
38
|
+
- If a framework-owned shim file is ever required, place it under `__themis__/shims/`, not under `tests/`.
|