@application-kit/testing 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @application-kit/testing
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9cf88e4: Универсальные настройки конфигов приложения стали умолчаниями пресетов: конфиг продукта называет только продукт.
8
+
9
+ `testing`: `defineStrykerConfig` сам исключает спеки, `tests/` и `.vue` и служебные каталоги, `vitestConfigFile` необязателен. У `defineStrykerLayers` необязательны `skip.configFiles` и `skip.sourceRoot`. Unit-профиль vitest сам исключает из покрытия `CODE_WITHOUT_LOGIC`, `*.exception.ts`, `*.html` и `src/*.ts`.
10
+
11
+ `lint-config`: пресет игнорирует служебные каталоги и выключает `unicorn/prefer-temporal`. Новый вход `lint-config/translated-files` — `defineTranslatedFilesConfig` с запретом литералов в переведённых экранах; плагин `@intlify/eslint-plugin-vue-i18n` — optional peer.
12
+
13
+ Что сделать потребителю: убрать из своих конфигов перечисленное, оно теперь дублирует умолчания. Список в `coverage.exclude`, `mutate.exclude` или `ignores`, отличный от умолчаний, продолжает работать как добавка, но снять умолчание больше нельзя.
14
+
15
+ ## 0.9.0
16
+
17
+ ### Minor Changes
18
+
19
+ - 94062e4: `defineApplicationUnitConfig` принимает `serverSetupFiles` — setup-файлы только проекта `server`. Туда идёт `/vitest/fast-bcrypt`: в `setupFiles` проект `client` при `node_modules` вне корня падал на `Cannot find module '/@fs/…'`.
20
+
3
21
  ## 0.8.0
4
22
 
5
23
  ### Minor Changes
package/README.md CHANGED
@@ -62,10 +62,11 @@ const E2E_SPECS = ['src/modules/*/tests/*.spec.ts']
62
62
  export default defineApplicationUnitConfig({
63
63
  clientSources: ['src/modules/*/user-interface/client/**'],
64
64
  clientSpecs: ['src/modules/*/user-interface/client/**/*.spec.ts'],
65
- coverage: { exclude: ['**/*.module.ts'], include: ['src'], thresholds: { lines: 95 } },
65
+ coverage: { include: ['src'], thresholds: { lines: 95 } },
66
66
  excludeSpecs: E2E_SPECS,
67
67
  root: import.meta.dirname,
68
- setupFiles: ['@application-kit/testing/vitest/fast-bcrypt'],
68
+ serverSetupFiles: ['@application-kit/testing/vitest/fast-bcrypt'],
69
+ setupFiles: [],
69
70
  specs: ['src/**/*.spec.ts'],
70
71
  viteConfig,
71
72
  })
@@ -80,7 +81,8 @@ export default defineApplicationEndToEndConfig({
80
81
  })
81
82
  ```
82
83
 
83
- Спеки, исходники клиента, покрытие (глобы, исключения, пороги), `setupFiles` и дополнительные пакеты для
84
+ Спеки, исходники клиента, покрытие (глобы, исключения, пороги), `setupFiles` (`serverSetupFiles` — только проекту
85
+ `server`) и дополнительные пакеты для
84
86
  `server.deps.inline` (`inlineDependencies`) — у приложения. Фабрики решают сами:
85
87
 
86
88
  - оба профиля — `globals`, `isolate: false`, `testTimeout` 20 с (таймаут ловит зависший промис, а не медленный раннер),
@@ -99,13 +101,18 @@ export default defineApplicationEndToEndConfig({
99
101
 
100
102
  ## Вход `/vitest/fast-bcrypt`: дешёвый bcrypt
101
103
 
102
- Setup-файл unit-профиля (`setupFiles: ['@application-kit/testing/vitest/fast-bcrypt']`): `vi.mock('bcrypt')` считает хеш
104
+ Setup-файл unit-профиля (`serverSetupFiles: ['@application-kit/testing/vitest/fast-bcrypt']`): `vi.mock('bcrypt')` считает хеш
103
105
  с cost 4 (≈ 1 мс против ≈ 300 мс у cost 12), а поле стоимости в строке переписывает на запрошенную — `getRounds` и
104
106
  решение о перехешировании видят настоящую стоимость; `compare` перед сверкой возвращает поле на 4. Сверяются только хеши,
105
107
  выпущенные подменой: литерал хеша с настоящим cost 12 не сойдётся. Подмена видна коду, прошедшему трансформ vitest
106
108
  (исходники приложения); внешний пакет, импортирующий `bcrypt`, ускоряется только через `inlineDependencies`. E2e-профилю
107
109
  файл не нужен.
108
110
 
111
+ Файл идёт в `serverSetupFiles`, а не в `setupFiles`: клиентскому коду bcrypt не нужен, а проект `client` грузит setup-файлы
112
+ через vite-сервер, который отдаёт только файлы из `server.fs.allow` (по умолчанию — корень проекта). При `node_modules` вне
113
+ корня (образ CI, git worktree со ссылкой на чужой `node_modules`) файл пакета в `setupFiles` роняет все спеки `client` на
114
+ `Cannot find module '/@fs/…'`.
115
+
109
116
  ## Ячейки CI по проектам
110
117
 
111
118
  `vitest --shard=i/n` сам режет спеки хешем, и при двух проектах разных трансформов (`server` в `node`, `client` в
@@ -124,23 +131,21 @@ web-карту того же файла, и statements/functions задваив
124
131
  Вход не тянет `vitest`: конфиг Stryker — обычный объект. `stryker.config.mjs` приложения собирается из трёх частей.
125
132
 
126
133
  `CODE_WITHOUT_LOGIC` — глобы файлов без логики (команды, DTO, модули, мапперы, манифесты, сущности и миграции TypeORM,
127
- навигация, виджет-плагины). Список один на оба инструмента: `defineStrykerConfig` добавляет к нему `!` для `mutate`, а
128
- приложение кладёт его в `coverage.exclude` vitest. Расхождение допустимо только в явных добавках приложения:
129
- `*.spec.ts` и `tests/**` для мутаций, `*.html` для покрытия.
134
+ навигация, виджет-плагины). Список один на оба инструмента, и пресеты подключают его сами: `defineStrykerConfig` добавляет
135
+ к нему `!` для `mutate`, unit-профиль vitest кладёт его в `coverage.exclude`. Сверх него `defineStrykerConfig` исключает
136
+ `*.spec.ts`, `*.vue` и `tests/**`, а unit-профиль — `*.exception.ts`, `*.html` и `src/*.ts`.
130
137
 
131
138
  ```js
132
- import { CODE_WITHOUT_LOGIC, defineStrykerConfig } from '@application-kit/testing/stryker'
139
+ import { defineStrykerConfig } from '@application-kit/testing/stryker'
133
140
 
134
141
  // stryker.conf.mjs — база
135
142
  export default defineStrykerConfig({
136
- ignorePatterns: ['docs', 'tasks'],
137
- mutate: { exclude: ['src/**/*.spec.ts', 'src/**/*.vue', 'src/**/tests/**'], include: ['src/modules/**/*.ts', 'src/libs/**/*.ts'] },
143
+ mutate: { exclude: ['src/libs/testing-module/**'], include: ['src/modules/**/*.ts', 'src/libs/**/*.ts'] },
138
144
  thresholds: { break: 94, high: 100, low: 95 },
139
- vitestConfigFile: 'vitest.config.mts',
140
145
  })
141
146
 
142
147
  // vitest.config.mts
143
- coverage: { exclude: [...CODE_WITHOUT_LOGIC, '**/*.html'], include: ['src'], thresholds: { lines: 95 } }
148
+ coverage: { include: ['src'], thresholds: { lines: 95 } }
144
149
  ```
145
150
 
146
151
  `defineStrykerLayers` режет прогон на группы каталогов: ячейка CI мутирует одну группу со своим порогом `break` (общий
@@ -2,7 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.defineStrykerConfig = void 0;
4
4
  const code_without_logic_js_1 = require("./code-without-logic.js");
5
- const DEFAULT_IGNORE_PATTERNS = ['coverage', 'dist', 'node_modules', 'reports'];
5
+ const DEFAULT_IGNORE_PATTERNS = [
6
+ '.claude',
7
+ '.cursor',
8
+ '.yarn',
9
+ 'coverage',
10
+ 'dist',
11
+ 'docs',
12
+ 'node_modules',
13
+ 'reports',
14
+ 'stryker.*.conf.*',
15
+ 'tasks',
16
+ 'var',
17
+ ];
18
+ const NOT_MUTATED = ['src/**/*.spec.ts', 'src/**/*.vue', 'src/**/tests/**'];
6
19
  const compare = (left, right) => left.localeCompare(right);
7
20
  const defineStrykerConfig = (options) => ({
8
21
  clearTextReporter: { reportTests: false },
@@ -14,11 +27,11 @@ const defineStrykerConfig = (options) => ({
14
27
  logLevel: 'warn',
15
28
  mutate: [
16
29
  ...options.mutate.include,
17
- ...[...code_without_logic_js_1.CODE_WITHOUT_LOGIC, ...(options.mutate.exclude ?? [])].map((pattern) => `!${pattern}`),
30
+ ...[...code_without_logic_js_1.CODE_WITHOUT_LOGIC, ...NOT_MUTATED, ...(options.mutate.exclude ?? [])].map((pattern) => `!${pattern}`),
18
31
  ],
19
32
  tempDirName: options.tempDirName ?? '/tmp/.stryker-tmp',
20
33
  testRunner: 'vitest',
21
34
  thresholds: options.thresholds,
22
- vitest: { configFile: options.vitestConfigFile, dir: './' },
35
+ vitest: { configFile: options.vitestConfigFile ?? 'vitest.config.mts', dir: './' },
23
36
  });
24
37
  exports.defineStrykerConfig = defineStrykerConfig;
@@ -7,6 +7,12 @@ exports.defineStrykerLayers = void 0;
7
7
  const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const node_process_1 = __importDefault(require("node:process"));
10
+ const DEFAULT_RUN_CONFIG_FILES = /^(?:\.gitlab-ci\.yml|tsconfig[^/]*\.json|vite\.config\.mts|vitest[^/]*\.mts|stryker[^/]*\.conf\.[a-z]+)$/u;
11
+ const resolveSkip = (scope) => ({
12
+ configFiles: scope.configFiles ?? DEFAULT_RUN_CONFIG_FILES,
13
+ sharedDirectories: scope.sharedDirectories,
14
+ sourceRoot: scope.sourceRoot ?? 'src',
15
+ });
10
16
  const DEFAULT_LAYER_VARIABLE = 'TYPESCRIPT_MUTATION_LAYER';
11
17
  const TEMPLATE_LAYER_SENTINEL = 'all';
12
18
  const MATRIX_PATTERN = (variable) => {
@@ -113,7 +119,7 @@ const defineStrykerLayers = (options) => {
113
119
  names,
114
120
  paths: pathsOf,
115
121
  roots,
116
- skip: options.skip,
122
+ skip: resolveSkip(options.skip),
117
123
  };
118
124
  };
119
125
  exports.defineStrykerLayers = defineStrykerLayers;
@@ -10,7 +10,7 @@ export interface StrykerConfigOptionsInterface {
10
10
  mutate: StrykerMutateScopeInterface;
11
11
  tempDirName?: string;
12
12
  thresholds: StrykerThresholdsInterface;
13
- vitestConfigFile: string;
13
+ vitestConfigFile?: string;
14
14
  }
15
15
  export interface StrykerMutateScopeInterface {
16
16
  exclude?: readonly string[];
@@ -15,6 +15,9 @@ export interface StrykerLayerSkipOptionsInterface {
15
15
  sharedDirectories: readonly string[];
16
16
  sourceRoot: string;
17
17
  }
18
+ export interface StrykerLayerSkipScopeInterface extends Partial<StrykerLayerSkipOptionsInterface> {
19
+ sharedDirectories: readonly string[];
20
+ }
18
21
  export interface StrykerLayersOptionsInterface {
19
22
  base: StrykerConfigInterface;
20
23
  ciFile: string;
@@ -27,5 +30,5 @@ export interface StrykerLayersOptionsInterface {
27
30
  directories: readonly string[];
28
31
  parents: readonly string[];
29
32
  };
30
- skip: StrykerLayerSkipOptionsInterface;
33
+ skip: StrykerLayerSkipScopeInterface;
31
34
  }
@@ -12,6 +12,7 @@ export interface ApplicationUnitConfigOptionsInterface extends ApplicationTestCo
12
12
  clientSources: readonly string[];
13
13
  clientSpecs: readonly string[];
14
14
  excludeSpecs: readonly string[];
15
+ serverSetupFiles?: readonly string[];
15
16
  }
16
17
  export interface CoverageScopeInterface {
17
18
  exclude?: readonly string[];
@@ -9,6 +9,7 @@ const node_path_1 = __importDefault(require("node:path"));
9
9
  const node_process_1 = __importDefault(require("node:process"));
10
10
  const vite_1 = require("vite");
11
11
  const node_1 = require("vitest/node");
12
+ const code_without_logic_js_1 = require("../stryker/code-without-logic.js");
12
13
  const project_shard_sequencer_js_1 = require("./project-shard-sequencer.js");
13
14
  const PACKAGES_THROUGH_VITE = ['@application-kit/graphql'];
14
15
  const TEST_TIMEOUT = 20_000;
@@ -51,6 +52,7 @@ const serverProject = (options) => ({
51
52
  exclude: [...options.excludeSpecs, ...options.clientSpecs],
52
53
  include: [...options.specs],
53
54
  name: 'server',
55
+ setupFiles: [...(options.serverSetupFiles ?? [])],
54
56
  },
55
57
  });
56
58
  const clientProject = (options) => ({
@@ -73,8 +75,13 @@ const graphqlEntryOf = (root) => {
73
75
  throw error;
74
76
  }
75
77
  };
78
+ const UNIT_COVERAGE_EXCLUDE = [...code_without_logic_js_1.CODE_WITHOUT_LOGIC, '**/*.exception.ts', '**/*.html', 'src/*.ts'];
76
79
  const unitCoverageScope = (cell, options) => ({
77
- exclude: [...(cell === 'server' ? options.clientSources : []), ...(options.coverage.exclude ?? [])],
80
+ exclude: [
81
+ ...(cell === 'server' ? options.clientSources : []),
82
+ ...UNIT_COVERAGE_EXCLUDE,
83
+ ...(options.coverage.exclude ?? []),
84
+ ],
78
85
  include: cell === 'client' ? options.clientSources : options.coverage.include,
79
86
  thresholds: options.coverage.thresholds,
80
87
  });
@@ -1,5 +1,18 @@
1
1
  import { CODE_WITHOUT_LOGIC } from './code-without-logic.js';
2
- const DEFAULT_IGNORE_PATTERNS = ['coverage', 'dist', 'node_modules', 'reports'];
2
+ const DEFAULT_IGNORE_PATTERNS = [
3
+ '.claude',
4
+ '.cursor',
5
+ '.yarn',
6
+ 'coverage',
7
+ 'dist',
8
+ 'docs',
9
+ 'node_modules',
10
+ 'reports',
11
+ 'stryker.*.conf.*',
12
+ 'tasks',
13
+ 'var',
14
+ ];
15
+ const NOT_MUTATED = ['src/**/*.spec.ts', 'src/**/*.vue', 'src/**/tests/**'];
3
16
  const compare = (left, right) => left.localeCompare(right);
4
17
  export const defineStrykerConfig = (options) => ({
5
18
  clearTextReporter: { reportTests: false },
@@ -11,10 +24,10 @@ export const defineStrykerConfig = (options) => ({
11
24
  logLevel: 'warn',
12
25
  mutate: [
13
26
  ...options.mutate.include,
14
- ...[...CODE_WITHOUT_LOGIC, ...(options.mutate.exclude ?? [])].map((pattern) => `!${pattern}`),
27
+ ...[...CODE_WITHOUT_LOGIC, ...NOT_MUTATED, ...(options.mutate.exclude ?? [])].map((pattern) => `!${pattern}`),
15
28
  ],
16
29
  tempDirName: options.tempDirName ?? '/tmp/.stryker-tmp',
17
30
  testRunner: 'vitest',
18
31
  thresholds: options.thresholds,
19
- vitest: { configFile: options.vitestConfigFile, dir: './' },
32
+ vitest: { configFile: options.vitestConfigFile ?? 'vitest.config.mts', dir: './' },
20
33
  });
@@ -1,6 +1,12 @@
1
1
  import { existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import process from 'node:process';
4
+ const DEFAULT_RUN_CONFIG_FILES = /^(?:\.gitlab-ci\.yml|tsconfig[^/]*\.json|vite\.config\.mts|vitest[^/]*\.mts|stryker[^/]*\.conf\.[a-z]+)$/u;
5
+ const resolveSkip = (scope) => ({
6
+ configFiles: scope.configFiles ?? DEFAULT_RUN_CONFIG_FILES,
7
+ sharedDirectories: scope.sharedDirectories,
8
+ sourceRoot: scope.sourceRoot ?? 'src',
9
+ });
4
10
  const DEFAULT_LAYER_VARIABLE = 'TYPESCRIPT_MUTATION_LAYER';
5
11
  const TEMPLATE_LAYER_SENTINEL = 'all';
6
12
  const MATRIX_PATTERN = (variable) => {
@@ -107,6 +113,6 @@ export const defineStrykerLayers = (options) => {
107
113
  names,
108
114
  paths: pathsOf,
109
115
  roots,
110
- skip: options.skip,
116
+ skip: resolveSkip(options.skip),
111
117
  };
112
118
  };
@@ -10,7 +10,7 @@ export interface StrykerConfigOptionsInterface {
10
10
  mutate: StrykerMutateScopeInterface;
11
11
  tempDirName?: string;
12
12
  thresholds: StrykerThresholdsInterface;
13
- vitestConfigFile: string;
13
+ vitestConfigFile?: string;
14
14
  }
15
15
  export interface StrykerMutateScopeInterface {
16
16
  exclude?: readonly string[];
@@ -15,6 +15,9 @@ export interface StrykerLayerSkipOptionsInterface {
15
15
  sharedDirectories: readonly string[];
16
16
  sourceRoot: string;
17
17
  }
18
+ export interface StrykerLayerSkipScopeInterface extends Partial<StrykerLayerSkipOptionsInterface> {
19
+ sharedDirectories: readonly string[];
20
+ }
18
21
  export interface StrykerLayersOptionsInterface {
19
22
  base: StrykerConfigInterface;
20
23
  ciFile: string;
@@ -27,5 +30,5 @@ export interface StrykerLayersOptionsInterface {
27
30
  directories: readonly string[];
28
31
  parents: readonly string[];
29
32
  };
30
- skip: StrykerLayerSkipOptionsInterface;
33
+ skip: StrykerLayerSkipScopeInterface;
31
34
  }
@@ -12,6 +12,7 @@ export interface ApplicationUnitConfigOptionsInterface extends ApplicationTestCo
12
12
  clientSources: readonly string[];
13
13
  clientSpecs: readonly string[];
14
14
  excludeSpecs: readonly string[];
15
+ serverSetupFiles?: readonly string[];
15
16
  }
16
17
  export interface CoverageScopeInterface {
17
18
  exclude?: readonly string[];
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import process from 'node:process';
4
4
  import { mergeConfig } from 'vite';
5
5
  import { version } from 'vitest/node';
6
+ import { CODE_WITHOUT_LOGIC } from '../stryker/code-without-logic.js';
6
7
  import { parseShard, ProjectShardSequencer } from './project-shard-sequencer.js';
7
8
  const PACKAGES_THROUGH_VITE = ['@application-kit/graphql'];
8
9
  const TEST_TIMEOUT = 20_000;
@@ -45,6 +46,7 @@ const serverProject = (options) => ({
45
46
  exclude: [...options.excludeSpecs, ...options.clientSpecs],
46
47
  include: [...options.specs],
47
48
  name: 'server',
49
+ setupFiles: [...(options.serverSetupFiles ?? [])],
48
50
  },
49
51
  });
50
52
  const clientProject = (options) => ({
@@ -67,8 +69,13 @@ const graphqlEntryOf = (root) => {
67
69
  throw error;
68
70
  }
69
71
  };
72
+ const UNIT_COVERAGE_EXCLUDE = [...CODE_WITHOUT_LOGIC, '**/*.exception.ts', '**/*.html', 'src/*.ts'];
70
73
  const unitCoverageScope = (cell, options) => ({
71
- exclude: [...(cell === 'server' ? options.clientSources : []), ...(options.coverage.exclude ?? [])],
74
+ exclude: [
75
+ ...(cell === 'server' ? options.clientSources : []),
76
+ ...UNIT_COVERAGE_EXCLUDE,
77
+ ...(options.coverage.exclude ?? []),
78
+ ],
72
79
  include: cell === 'client' ? options.clientSources : options.coverage.include,
73
80
  thresholds: options.coverage.thresholds,
74
81
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@application-kit/testing",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Тестовая инфраструктура NestJS-приложений: шины CQRS, логгер, фейковый microservice, сборка web-приложения, Postgres в testcontainers, запуск e2e-приложения, изоляция окружения прогона, дешёвый bcrypt для unit-прогона, раскладка ячеек CI по проектам vitest и фабрики конфигов vitest",
5
5
  "keywords": [
6
6
  "cqrs",
@@ -81,7 +81,7 @@
81
81
  "test:unit": "vitest run"
82
82
  },
83
83
  "dependencies": {
84
- "@application-kit/bootstrap": "^0.2.3"
84
+ "@application-kit/bootstrap": "^0.2.4"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@nestjs/common": "^11.2.7",