@7n/test 0.7.0 → 0.7.2

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.2] - 2026-06-27
4
+
5
+ ### Fixed
6
+
7
+ - Виправлено парсинг JSON-репорту vitest 4.x: поле `name` замість `testFilePath` (breaking change у vitest JSON reporter). Наслідок: падаючі тести тепер коректно детектуються і передаються у fix-loop замість bootstrap.
8
+
9
+ ## [0.7.1] - 2026-06-27
10
+
11
+ ### Fixed
12
+
13
+ - `parseFailingTests` тепер коректно обробляє module-level помилки (import/syntax errors) — тести, що падають ще до запуску (`assertionResults: []`), більше не пропускаються, що усувало нескінченну bootstrap-петлю.
14
+
3
15
  ## [0.7.0] - 2026-06-27
4
16
 
5
17
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/test",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "CLI-утиліта @7n/test",
5
5
  "keywords": [
6
6
  "7n",
@@ -13,10 +13,7 @@ import { join, relative, dirname } from 'node:path'
13
13
  import { env } from 'node:process'
14
14
 
15
15
  const _require = createRequire(import.meta.url)
16
- const VITEST_BIN = join(
17
- dirname(_require.resolve('vitest/package.json')),
18
- 'vitest.mjs'
19
- )
16
+ const VITEST_BIN = join(dirname(_require.resolve('vitest/package.json')), 'vitest.mjs')
20
17
 
21
18
  const TEST_FILE_RE = /\.(test|spec)\.[^.]+$|[/\\]tests?[/\\]/
22
19
  const MAX_ERRORS_PER_FILE = 5
@@ -63,9 +60,8 @@ function parseFailingTests(jsonPath, dir) {
63
60
  const data = JSON.parse(readFileSync(jsonPath, 'utf8'))
64
61
  return (data.testResults ?? [])
65
62
  .filter(r => r.status === 'failed')
66
- .map(r => ({
67
- file: relative(dir, r.testFilePath),
68
- errors: (r.assertionResults ?? [])
63
+ .map(r => {
64
+ const assertionErrors = (r.assertionResults ?? [])
69
65
  .filter(a => a.status === 'failed')
70
66
  .slice(0, MAX_ERRORS_PER_FILE)
71
67
  .map(a => {
@@ -73,8 +69,16 @@ function parseFailingTests(jsonPath, dir) {
73
69
  const msg = (a.failureMessages?.[0] ?? '').split('\n').slice(0, MAX_ERROR_LINES).join('\n')
74
70
  return `${name}:\n${msg}`
75
71
  })
76
- }))
77
- .filter(f => !f.file.startsWith('..') && f.errors.length > 0)
72
+ // Module-level errors (import/syntax) produce no assertionResults
73
+ const errors =
74
+ assertionErrors.length > 0
75
+ ? assertionErrors
76
+ : [
77
+ `Suite error: ${(r.message ?? r.failureMessage ?? 'module-level failure').split('\n').slice(0, MAX_ERROR_LINES).join('\n')}`
78
+ ]
79
+ return { file: relative(dir, r.testFilePath ?? r.name), errors }
80
+ })
81
+ .filter(f => !f.file.startsWith('..'))
78
82
  } catch {
79
83
  return []
80
84
  }
@@ -136,8 +140,19 @@ export function getUncoveredFiles(files, threshold = 80) {
136
140
 
137
141
  const SOURCE_EXT_RE = /\.(mjs|js|ts|vue|py)$/
138
142
  const IGNORE_DIRS = new Set([
139
- 'node_modules', 'dist', 'build', 'out', '.git', '__pycache__',
140
- 'coverage', '.cursor', '.claude', '.pi', 'docs', 'bin', 'reports'
143
+ 'node_modules',
144
+ 'dist',
145
+ 'build',
146
+ 'out',
147
+ '.git',
148
+ '__pycache__',
149
+ 'coverage',
150
+ '.cursor',
151
+ '.claude',
152
+ '.pi',
153
+ 'docs',
154
+ 'bin',
155
+ 'reports'
141
156
  ])
142
157
 
143
158
  /**
package/src/fix-tests.mjs CHANGED
@@ -16,10 +16,7 @@ import { join, relative, dirname } from 'node:path'
16
16
  import { env } from 'node:process'
17
17
 
18
18
  const _require = createRequire(import.meta.url)
19
- const VITEST_BIN = join(
20
- dirname(_require.resolve('vitest/package.json')),
21
- 'vitest.mjs'
22
- )
19
+ const VITEST_BIN = join(dirname(_require.resolve('vitest/package.json')), 'vitest.mjs')
23
20
 
24
21
  const MODEL = env.N_CURSOR_FIX_TESTS_MODEL ?? env.N_CLOUD_MAX_MODEL ?? ''
25
22
  const MAX_ERRORS_PER_FILE = 5
@@ -35,11 +32,15 @@ export async function getFailingTests(dir) {
35
32
  const outputFile = join(tmpDir, 'results.json')
36
33
 
37
34
  try {
38
- spawnSync(process.execPath, [VITEST_BIN, 'run', '--reporter=json', `--outputFile=${outputFile}`, '--passWithNoTests'], {
39
- cwd: dir,
40
- stdio: 'inherit',
41
- env
42
- })
35
+ spawnSync(
36
+ process.execPath,
37
+ [VITEST_BIN, 'run', '--reporter=json', `--outputFile=${outputFile}`, '--passWithNoTests'],
38
+ {
39
+ cwd: dir,
40
+ stdio: 'inherit',
41
+ env
42
+ }
43
+ )
43
44
 
44
45
  if (!existsSync(outputFile)) return []
45
46
 
@@ -52,9 +53,8 @@ export async function getFailingTests(dir) {
52
53
 
53
54
  return (data.testResults ?? [])
54
55
  .filter(r => r.status === 'failed')
55
- .map(r => ({
56
- file: relative(dir, r.testFilePath),
57
- errors: (r.assertionResults ?? [])
56
+ .map(r => {
57
+ const assertionErrors = (r.assertionResults ?? [])
58
58
  .filter(a => a.status === 'failed')
59
59
  .slice(0, MAX_ERRORS_PER_FILE)
60
60
  .map(a => {
@@ -62,8 +62,12 @@ export async function getFailingTests(dir) {
62
62
  const msg = (a.failureMessages?.[0] ?? '').split('\n').slice(0, MAX_ERROR_LINES).join('\n')
63
63
  return `${name}:\n${msg}`
64
64
  })
65
- }))
66
- .filter(f => !f.file.startsWith('..') && f.errors.length > 0)
65
+ const errors = assertionErrors.length > 0
66
+ ? assertionErrors
67
+ : [`Suite error: ${(r.message ?? r.failureMessage ?? 'module-level failure').split('\n').slice(0, MAX_ERROR_LINES).join('\n')}`]
68
+ return { file: relative(dir, r.testFilePath ?? r.name), errors }
69
+ })
70
+ .filter(f => !f.file.startsWith('..'))
67
71
  } finally {
68
72
  await rm(tmpDir, { recursive: true, force: true }).catch(() => {})
69
73
  }