@7n/test 0.16.0 → 0.16.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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/coverage/js-collector.mjs +68 -35
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.16.1] - 2026-07-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- coverage: три реальні баги, знайдені живою перевіркою на справжньому Storybook 10 + Vue 3 проєкті (storybook init-скаффолд) — (1) --project=!storybook падав з 'No projects matched the filter' для проєктів без окремого JS unit-test vitest-проєкту (типовий default-скаффолд), тепер трактується як passWithNoTests; (2) .vue на Storybook-root виключено з JS-виміру Stryker-мутації — vitest-runner структурно не може прогнати dry-run, коли єдиний vitest-проєкт це browser-mode 'storybook', раніше це валило ВЕСЬ прогін; (3) Stryker CLI configFile — позиційний аргумент, не --configFile. Задокументовано нове невирішене обмеження full-режиму command runner на реальних проєктах (npm/docs/stryker-storybook-config.md) — --changed-executor лишається єдиним підтвердженим робочим шляхом
|
|
8
|
+
|
|
3
9
|
## [0.16.0] - 2026-07-18
|
|
4
10
|
|
|
5
11
|
### Added
|
package/package.json
CHANGED
|
@@ -33,6 +33,7 @@ const JS_FILE = /\.(c|m)?[jt]sx?$|\.vue$/
|
|
|
33
33
|
const TEST_FILE = /\.(test|spec)\.[^.]+$/
|
|
34
34
|
/** `.vue`-компоненти + `*.stories.*` — сигнал для Storybook-змінного scope (line coverage). */
|
|
35
35
|
const VUE_OR_STORIES_FILE = /\.vue$|\.stories\.[^.]+$/
|
|
36
|
+
const VUE_FILE_RE = /\.vue$/
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* Звужує список змінених файлів (relative до cwd) до тих, що лежать під `jsRoot`,
|
|
@@ -81,6 +82,8 @@ const VITEST_HINT =
|
|
|
81
82
|
const STORYBOOK_STRYKER_CONFIG = 'stryker.storybook.config.mjs'
|
|
82
83
|
/** Шлях mutation.json того ж прогону (окремий від `reports/stryker/` JS-виміру). */
|
|
83
84
|
const STORYBOOK_STRYKER_REPORT = join('reports', 'stryker-storybook', 'mutation.json')
|
|
85
|
+
/** vitest hard-fail коли `--project=!storybook` не залишає жодного проєкту (не test-порожньо). */
|
|
86
|
+
const NO_PROJECTS_MATCHED_RE = /No projects matched the filter/
|
|
84
87
|
|
|
85
88
|
/**
|
|
86
89
|
* Чи у пакеті встановлено vitest (через dependencies або devDependencies).
|
|
@@ -311,20 +314,36 @@ const defaultRunner = {
|
|
|
311
314
|
// зайве дублювання з collectStorybookForRoot і ризик втягнути Playwright-залежність
|
|
312
315
|
// у звичайний coverage-прогін.
|
|
313
316
|
const projectArgs = excludeStorybookProject ? ['--project=!storybook'] : []
|
|
314
|
-
const
|
|
315
|
-
'
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
{ cwd, stdio: 'inherit', env: process.env }
|
|
327
|
-
|
|
317
|
+
const args = [
|
|
318
|
+
'vitest',
|
|
319
|
+
'run',
|
|
320
|
+
'--passWithNoTests',
|
|
321
|
+
'--coverage',
|
|
322
|
+
'--coverage.reporter=lcov',
|
|
323
|
+
`--coverage.reportsDirectory=${lcovDir}`,
|
|
324
|
+
...projectArgs,
|
|
325
|
+
...changedArgs
|
|
326
|
+
]
|
|
327
|
+
|
|
328
|
+
if (!excludeStorybookProject) {
|
|
329
|
+
const r = spawnSync('bunx', args, { cwd, stdio: 'inherit', env: process.env })
|
|
330
|
+
return r.status ?? 1
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// excludeStorybookProject-гілка захоплює stdout/stderr (не stdio:'inherit'), бо треба
|
|
334
|
+
// програмно розрізнити "--project=!storybook не залишив ЖОДНОГО проєкту" (типовий
|
|
335
|
+
// canonical @storybook/addon-vitest setup БЕЗ окремого unit-test проєкту — Storybook-root
|
|
336
|
+
// з єдиним named-проєктом "storybook") від реальної помилки. vitest у цьому кейсі кидає
|
|
337
|
+
// hard "No projects matched the filter" і завершується exit≠0 ДО будь-якого запуску тестів
|
|
338
|
+
// (`--passWithNoTests` цей кейс не покриває — той стосується тестів усередині вже
|
|
339
|
+
// знайдених проєктів, не відсутності самих проєктів). Підтверджено емпірично на
|
|
340
|
+
// реальному `storybook init` (Storybook 10 + Vue3, 2026-07): весь `test.projects` — лише
|
|
341
|
+
// "storybook", без окремого JS unit-test проєкту.
|
|
342
|
+
const r = spawnSync('bunx', args, { cwd, encoding: 'utf8', env: process.env })
|
|
343
|
+
const out = (r.stdout ?? '') + (r.stderr ?? '')
|
|
344
|
+
process.stdout.write(r.stdout ?? '')
|
|
345
|
+
process.stderr.write(r.stderr ?? '')
|
|
346
|
+
if (r.status !== 0 && NO_PROJECTS_MATCHED_RE.test(out)) return 0
|
|
328
347
|
return r.status ?? 1
|
|
329
348
|
},
|
|
330
349
|
runBunCoverage({ cwd, lcovDir }) {
|
|
@@ -421,7 +440,10 @@ const defaultRunner = {
|
|
|
421
440
|
// конфіга задокументовано в npm/docs/stryker-storybook-config.md. Той самий
|
|
422
441
|
// резолвінг local-bin, що й runStryker (plugin-discovery відносно install-каталогу).
|
|
423
442
|
const strykerBin = resolveLocalStrykerBin()
|
|
424
|
-
|
|
443
|
+
// `configFile` — позиційний аргумент Stryker CLI (`stryker run [options] [configFile]`),
|
|
444
|
+
// НЕ `--configFile` — емпірично підтверджено (реальний прогін, 2026-07): `--configFile`
|
|
445
|
+
// дає `error: unknown option '--configFile'`.
|
|
446
|
+
const args = ['run', STORYBOOK_STRYKER_CONFIG]
|
|
425
447
|
const r = strykerBin
|
|
426
448
|
? spawnSync(strykerBin, args, { cwd, stdio: 'inherit', env: process.env })
|
|
427
449
|
: spawnSync('npx', ['@stryker-mutator/core', ...args], { cwd, stdio: 'inherit', env: process.env })
|
|
@@ -463,18 +485,28 @@ const defaultRunner = {
|
|
|
463
485
|
*/
|
|
464
486
|
async function collectOneRoot(jsRoot, cwd, runner, scope = null) {
|
|
465
487
|
const wsRel = relative(cwd, jsRoot)
|
|
466
|
-
// У changed-режимі production-файли для мутації = змінені JS/Vue цього root без
|
|
467
|
-
// тест-файлів і без *.stories.* (сторі — не production-код, окремий Storybook-вимір).
|
|
468
|
-
// `.vue` тут ЗАЛИШАЄТЬСЯ — Stryker core мутує <script>/<script setup> SFC (з v7+),
|
|
469
|
-
// допоки покриття дають НЕ-browser-mode тести (@vue/test-utils тощо, не Storybook-сторі).
|
|
470
|
-
const mutateSrc = scope ? scope.files.filter(f => !TEST_FILE.test(f) && !STORIES_FILE_RE.test(f)) : null
|
|
471
488
|
|
|
472
489
|
// Bun-native workspace: coverage через `bun test`, mutation пропускається
|
|
473
490
|
// (Stryker vitest-runner не виконає код з `import ... from 'bun'`).
|
|
474
491
|
const bunNative = await isBunNativeRoot(jsRoot)
|
|
475
492
|
// Storybook root: named vitest-проєкт "storybook" (browser mode) типово ділить один
|
|
476
493
|
// vitest.config.mjs зі звичайним suite — виключаємо його з JS-прогону (див. runJsCoverage).
|
|
477
|
-
const
|
|
494
|
+
const isStorybook = !bunNative && (await isStorybookRoot(jsRoot))
|
|
495
|
+
const excludeStorybookProject = isStorybook
|
|
496
|
+
|
|
497
|
+
// У changed-режимі production-файли для мутації = змінені JS/Vue цього root без
|
|
498
|
+
// тест-файлів і без *.stories.* (сторі — не production-код, окремий Storybook-вимір).
|
|
499
|
+
// `.vue` на Storybook-root ТЕЖ виключено: якщо root — Storybook-only (типовий
|
|
500
|
+
// `storybook init`-скаффолд, ЄДИНИЙ vitest-проєкт — "storybook"), Stryker vitest-runner
|
|
501
|
+
// структурно не може прогнати dry-run (немає non-browser проєкту для виконання) —
|
|
502
|
+
// емпірично підтверджено (2026-07, реальний Storybook 10 + Vue3 скаффолд): dry-run
|
|
503
|
+
// валиться, `collectOneRoot` кидає, і це ламає ВЕСЬ прогін (обидва виміри), не лише
|
|
504
|
+
// JS-рядок. `.vue`-мутація на Storybook-root — виключно відповідальність
|
|
505
|
+
// `collectStorybookForRoot` (own executor / command-runner), яка вже покриває SFC
|
|
506
|
+
// повністю; дублювання через JS-вимір там і зайве, і структурно ламке.
|
|
507
|
+
const mutateSrc = scope
|
|
508
|
+
? scope.files.filter(f => !TEST_FILE.test(f) && !STORIES_FILE_RE.test(f) && !(isStorybook && VUE_FILE_RE.test(f)))
|
|
509
|
+
: null
|
|
478
510
|
if (bunNative && !(await hasRunnableTests(jsRoot))) {
|
|
479
511
|
// `bun test` без тестів завершується помилкою — graceful skip як vitest --passWithNoTests.
|
|
480
512
|
return scope
|
|
@@ -598,21 +630,22 @@ async function collectOneRoot(jsRoot, cwd, runner, scope = null) {
|
|
|
598
630
|
* Meta ACH): детермінована генерація мутантів + реальний прогін як суддя — без LLM
|
|
599
631
|
* у контурі виконання.
|
|
600
632
|
*
|
|
601
|
-
*
|
|
602
|
-
*
|
|
633
|
+
* **Full-режим — Stryker command runner (`runStorybookStrykerFull`, опційний у
|
|
634
|
+
* runner-і), АЛЕ з відомим невирішеним обмеженням (перевіряй перед покладанням —
|
|
635
|
+
* секція «Обмеження» в `npm/docs/stryker-storybook-config.md`):**
|
|
636
|
+
* Spike на синтетичному JS-репо (2026-07-17) підтвердив саму МЕХАНІКУ — command
|
|
637
|
+
* runner + browser mode ПРАЦЮЄ з обов'язковим
|
|
603
638
|
* `define: { 'process.env.__STRYKER_ACTIVE_MUTANT__': JSON.stringify(process.env.__STRYKER_ACTIVE_MUTANT__ ?? '') }`
|
|
604
|
-
* у vite-конфізі
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
* власний executor: дешевший на кількох файлах, і активація не залежить від
|
|
615
|
-
* env-пропагації взагалі.
|
|
639
|
+
* у vite-конфізі (без define — тихий провал, 0% killed). Але живий прогін на
|
|
640
|
+
* РЕАЛЬНОМУ `storybook init`-скаффолді (2026-07-18) показав: dry-run падає з
|
|
641
|
+
* `Failed to fetch dynamically imported module` для `*.stories.js`, і ЖОДНА з
|
|
642
|
+
* перевірених гіпотез (symlink sandbox, `inPlace: true`, vue-docgen-plugin,
|
|
643
|
+
* concurrency) не root-cause. Той самий `vitest run --project=storybook`
|
|
644
|
+
* бездоганно працює через ЗВИЧАЙНИЙ `spawnSync` (наш `--changed`-executor) —
|
|
645
|
+
* проблема специфічна саме до того, як Stryker spawn-ить/оточує процес.
|
|
646
|
+
* Висновок: full-режим лишається задокументованим напрямком, не підтвердженим
|
|
647
|
+
* робочим рішенням; `--changed`-executor (нижче) — єдиний перевірений на
|
|
648
|
+
* реальному проєкті шлях отримати справжній mutation score для Storybook-коду.
|
|
616
649
|
*
|
|
617
650
|
* Changed-режим: запускається тільки якщо серед змінених файлів root-а є хоча б
|
|
618
651
|
* один `.vue`/`*.stories.*` (`scope.files` — вже звужений через `scopeToStorybookRoot`
|