@application-kit/testing 0.2.0 → 0.3.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,16 @@
1
1
  # @application-kit/testing
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2e91985: Вход `/vitest`: фабрики `defineApplicationUnitConfig` и `defineApplicationEndToEndConfig` — `vitest.config` и
8
+ `vitest.config.e2e` приложения поверх экспорта его `vite.config`. Unit — проекты `server` и `client`, ячейка шарда CI по
9
+ `--shard` и `ProjectShardSequencer`, покрытие v8; e2e — окружение `node` и алиас `graphql` на CJS-вход против двух realm
10
+ у mercurius. Оба профиля — кеш `.vitest-module-cache`, `testTimeout` 20 с, inline `@application-kit/graphql`. Спеки,
11
+ исходники клиента, покрытие, `setupFiles` и дополнительные пакеты для inline — параметры. Новый optional peer входа —
12
+ `vite`.
13
+
3
14
  ## 0.2.0
4
15
 
5
16
  ### Minor Changes
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # @application-kit/testing
2
2
 
3
3
  Тестовая инфраструктура NestJS-приложений: стабы шин CQRS, логгер-накопитель, фейковый microservice-транспорт,
4
- сборка web-приложения для e2e, Postgres в testcontainers и раскладка ячеек CI по проектам vitest (вход `/vitest`).
4
+ сборка web-приложения для e2e, Postgres в testcontainers, конфиги vitest приложения и раскладка ячеек CI по проектам
5
+ vitest (вход `/vitest`).
5
6
  Для `devDependencies` приложения, в прод не попадает.
6
7
 
7
8
  ## Установка
@@ -12,7 +13,8 @@ yarn add --dev @application-kit/testing
12
13
 
13
14
  Peer-зависимости: `@nestjs/common`, `@nestjs/core`, `@nestjs/cqrs`,
14
15
  `@nestjs/microservices`, `@nestjs/platform-fastify`, `@nestjs/testing`, `nestjs-pino`, `oxide.ts`, `rxjs`,
15
- `testcontainers`, `typeorm` — обязательные для корневого входа. Вход `/vitest` несёт свой optional peer `vitest`.
16
+ `testcontainers`, `typeorm` — обязательные для корневого входа. Вход `/vitest` несёт свои optional peers `vitest` и
17
+ `vite` (`mergeConfig` фабрик конфигов).
16
18
 
17
19
  ## Состав
18
20
 
@@ -34,32 +36,61 @@ const { dataSource } = await testDataSource({
34
36
  })
35
37
  ```
36
38
 
37
- ## Вход `/vitest`: ячейки CI по проектам
39
+ ## Вход `/vitest`: конфиги vitest приложения
40
+
41
+ `defineApplicationUnitConfig(options)` и `defineApplicationEndToEndConfig(options)` собирают `vitest.config` и
42
+ `vitest.config.e2e` поверх экспорта `vite.config` приложения (`viteConfig` — объект, `mergeConfig` из `vite`):
43
+
44
+ ```ts
45
+ import { defineApplicationEndToEndConfig, defineApplicationUnitConfig } from '@application-kit/testing/vitest'
46
+
47
+ import viteConfig from './vite.config.mjs'
48
+
49
+ const E2E_SPECS = ['src/modules/*/tests/*.spec.ts']
50
+
51
+ // vitest.config.mts
52
+ export default defineApplicationUnitConfig({
53
+ clientSources: ['src/modules/*/user-interface/client/**'],
54
+ clientSpecs: ['src/modules/*/user-interface/client/**/*.spec.ts'],
55
+ coverage: { exclude: ['**/*.module.ts'], include: ['src'], thresholds: { lines: 95 } },
56
+ excludeSpecs: E2E_SPECS,
57
+ root: import.meta.dirname,
58
+ setupFiles: ['./src/testing/fast-hashing.ts'],
59
+ specs: ['src/**/*.spec.ts'],
60
+ viteConfig,
61
+ })
62
+
63
+ // vitest.config.e2e.mts
64
+ export default defineApplicationEndToEndConfig({
65
+ coverage: { include: E2E_SPECS, thresholds: { 100: true } },
66
+ root: import.meta.dirname,
67
+ setupFiles: ['./src/testing/e2e-environment.ts'],
68
+ specs: E2E_SPECS,
69
+ viteConfig,
70
+ })
71
+ ```
72
+
73
+ Спеки, исходники клиента, покрытие (глобы, исключения, пороги), `setupFiles` и дополнительные пакеты для
74
+ `server.deps.inline` (`inlineDependencies`) — у приложения. Фабрики решают сами:
75
+
76
+ - оба профиля — `globals`, `isolate: false`, `testTimeout` 20 с (таймаут ловит зависший промис, а не медленный раннер),
77
+ кеш трансформов `.vitest-module-cache` (путь — контракт кеша джоб `ts:test:*` шаблона CI), покрытие v8 с отчётом
78
+ `text` и `skipFull`; `@application-kit/graphql` идёт через Vite, а не нативно через Node — иначе ошибка из пакета и код
79
+ приложения видят разные копии `graphql`, и `instanceof GraphQLError` ложен;
80
+ - unit — проекты `server` (`node`, спеки `specs` без `excludeSpecs` и `clientSpecs`) и `client` (`jsdom`, `clientSpecs`),
81
+ ячейки шарда CI и `ProjectShardSequencer` (ниже);
82
+ - e2e — окружение `node` и алиас `graphql` на CJS-вход `node_modules/graphql/index.js` под `root`: Vite для SSR берёт
83
+ ESM-сборку `graphql`, внешний `mercurius` — CJS, и без алиаса схема и валидация запроса живут в разных realm.
84
+
85
+ ## Ячейки CI по проектам
38
86
 
39
87
  `vitest --shard=i/n` сам режет спеки хешем, и при двух проектах разных трансформов (`server` в `node`, `client` в
40
88
  `jsdom`) объединение blob-отчётов покрытия неточное: непокрытые файлы одна ячейка синтезирует ssr-картой, а другая даёт
41
89
  web-карту того же файла, и statements/functions задваиваются. `ProjectShardSequencer` отдаёт первой ячейке проект
42
90
  `client` целиком, остальным — проект `server` по размеру спек (LPT, одна и та же раскладка в каждой ячейке без обмена
43
91
  данными). `parseShard(process.argv)` читает `--shard=i/n` и `--shard i/n`; без флага, с одной ячейкой или с мусором в
44
- значении — `undefined`, то есть обычный прогон (и `--merge-reports`, и Stryker). Каталоги проекта `client` и выбор
45
- ячейки — у приложения:
92
+ значении — `undefined`, то есть обычный прогон (и `--merge-reports`, и Stryker).
46
93
 
47
- ```ts
48
- import { parseShard, ProjectShardSequencer } from '@application-kit/testing/vitest'
49
-
50
- // Исходники, которые загружает только проект client: в ячейках server они не считаются вовсе.
51
- const CLIENT_SOURCES = ['src/modules/*/user-interface/client/**']
52
- const shard = parseShard(process.argv)
53
- const cell = shard === undefined ? 'all' : shard.index === 1 ? 'client' : 'server'
54
-
55
- export default defineConfig({
56
- test: {
57
- coverage: {
58
- exclude: cell === 'server' ? CLIENT_SOURCES : [],
59
- include: cell === 'client' ? CLIENT_SOURCES : ['src'],
60
- },
61
- projects: [serverProject, clientProject].filter((project) => cell === 'all' || project.test.name === cell),
62
- sequence: { sequencer: ProjectShardSequencer },
63
- },
64
- })
65
- ```
94
+ `defineApplicationUnitConfig` выбирает ячейку по `process.argv` в момент вызова: 1 — только проект `client` и покрытие
95
+ по `clientSources`, 2..n — только проект `server` и покрытие без `clientSources`, без флага — оба проекта. Свой конфиг
96
+ без фабрики собирается из `parseShard` и `ProjectShardSequencer` напрямую.
@@ -0,0 +1,20 @@
1
+ import type { UserConfig } from 'vite';
2
+ import type { CoverageOptions } from 'vitest/node';
3
+ export interface ApplicationTestConfigOptionsInterface {
4
+ coverage: CoverageScopeInterface;
5
+ inlineDependencies?: readonly string[];
6
+ root: string;
7
+ setupFiles: readonly string[];
8
+ specs: readonly string[];
9
+ viteConfig: UserConfig;
10
+ }
11
+ export interface ApplicationUnitConfigOptionsInterface extends ApplicationTestConfigOptionsInterface {
12
+ clientSources: readonly string[];
13
+ clientSpecs: readonly string[];
14
+ excludeSpecs: readonly string[];
15
+ }
16
+ export interface CoverageScopeInterface {
17
+ exclude?: readonly string[];
18
+ include: readonly string[];
19
+ thresholds: NonNullable<CoverageOptions['thresholds']>;
20
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ import type { ViteUserConfig } from 'vitest/config';
2
+ import type { ApplicationTestConfigOptionsInterface, ApplicationUnitConfigOptionsInterface } from './application-vitest-config-options.interface.js';
3
+ export declare const defineApplicationUnitConfig: (options: ApplicationUnitConfigOptionsInterface) => ViteUserConfig;
4
+ export declare const defineApplicationEndToEndConfig: (options: ApplicationTestConfigOptionsInterface) => ViteUserConfig;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.defineApplicationEndToEndConfig = exports.defineApplicationUnitConfig = void 0;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const node_process_1 = __importDefault(require("node:process"));
9
+ const vite_1 = require("vite");
10
+ const project_shard_sequencer_js_1 = require("./project-shard-sequencer.js");
11
+ const PACKAGES_THROUGH_VITE = ['@application-kit/graphql'];
12
+ const TEST_TIMEOUT = 20_000;
13
+ const cellOf = (argv) => {
14
+ const shard = (0, project_shard_sequencer_js_1.parseShard)(argv);
15
+ if (shard === undefined) {
16
+ return 'all';
17
+ }
18
+ return shard.index === 1 ? 'client' : 'server';
19
+ };
20
+ const coverageOf = (scope) => ({
21
+ clean: true,
22
+ cleanOnRerun: true,
23
+ exclude: [...(scope.exclude ?? [])],
24
+ include: [...scope.include],
25
+ provider: 'v8',
26
+ reporter: ['text'],
27
+ skipFull: true,
28
+ thresholds: scope.thresholds,
29
+ });
30
+ const sharedTestOptions = (options) => ({
31
+ experimental: { fsModuleCache: true, fsModuleCachePath: '.vitest-module-cache' },
32
+ globals: true,
33
+ isolate: false,
34
+ server: { deps: { inline: [...PACKAGES_THROUGH_VITE, ...(options.inlineDependencies ?? [])] } },
35
+ setupFiles: [...options.setupFiles],
36
+ testTimeout: TEST_TIMEOUT,
37
+ });
38
+ const serverProject = (options) => ({
39
+ extends: true,
40
+ test: {
41
+ environment: 'node',
42
+ exclude: [...options.excludeSpecs, ...options.clientSpecs],
43
+ include: [...options.specs],
44
+ name: 'server',
45
+ },
46
+ });
47
+ const clientProject = (options) => ({
48
+ extends: true,
49
+ test: { environment: 'jsdom', include: [...options.clientSpecs], name: 'client' },
50
+ });
51
+ const PROJECTS_OF_CELL = {
52
+ all: [serverProject, clientProject],
53
+ client: [clientProject],
54
+ server: [serverProject],
55
+ };
56
+ const unitCoverageScope = (cell, options) => ({
57
+ exclude: [...(cell === 'server' ? options.clientSources : []), ...(options.coverage.exclude ?? [])],
58
+ include: cell === 'client' ? options.clientSources : options.coverage.include,
59
+ thresholds: options.coverage.thresholds,
60
+ });
61
+ const defineApplicationUnitConfig = (options) => {
62
+ const cell = cellOf(node_process_1.default.argv);
63
+ return (0, vite_1.mergeConfig)(options.viteConfig, {
64
+ root: options.root,
65
+ test: {
66
+ ...sharedTestOptions(options),
67
+ coverage: coverageOf(unitCoverageScope(cell, options)),
68
+ projects: PROJECTS_OF_CELL[cell].map((project) => project(options)),
69
+ sequence: { sequencer: project_shard_sequencer_js_1.ProjectShardSequencer },
70
+ },
71
+ });
72
+ };
73
+ exports.defineApplicationUnitConfig = defineApplicationUnitConfig;
74
+ const defineApplicationEndToEndConfig = (options) => {
75
+ const graphql = node_path_1.default.resolve(options.root, 'node_modules/graphql/index.js');
76
+ return (0, vite_1.mergeConfig)(options.viteConfig, {
77
+ resolve: { alias: { graphql } },
78
+ root: options.root,
79
+ test: {
80
+ ...sharedTestOptions(options),
81
+ coverage: coverageOf(options.coverage),
82
+ environment: 'node',
83
+ include: [...options.specs],
84
+ },
85
+ });
86
+ };
87
+ exports.defineApplicationEndToEndConfig = defineApplicationEndToEndConfig;
@@ -1 +1,3 @@
1
+ export * from './application-vitest-config.js';
2
+ export * from './application-vitest-config-options.interface.js';
1
3
  export * from './project-shard-sequencer.js';
@@ -14,4 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./application-vitest-config.js"), exports);
18
+ __exportStar(require("./application-vitest-config-options.interface.js"), exports);
17
19
  __exportStar(require("./project-shard-sequencer.js"), exports);
@@ -0,0 +1,20 @@
1
+ import type { UserConfig } from 'vite';
2
+ import type { CoverageOptions } from 'vitest/node';
3
+ export interface ApplicationTestConfigOptionsInterface {
4
+ coverage: CoverageScopeInterface;
5
+ inlineDependencies?: readonly string[];
6
+ root: string;
7
+ setupFiles: readonly string[];
8
+ specs: readonly string[];
9
+ viteConfig: UserConfig;
10
+ }
11
+ export interface ApplicationUnitConfigOptionsInterface extends ApplicationTestConfigOptionsInterface {
12
+ clientSources: readonly string[];
13
+ clientSpecs: readonly string[];
14
+ excludeSpecs: readonly string[];
15
+ }
16
+ export interface CoverageScopeInterface {
17
+ exclude?: readonly string[];
18
+ include: readonly string[];
19
+ thresholds: NonNullable<CoverageOptions['thresholds']>;
20
+ }
@@ -0,0 +1,4 @@
1
+ import type { ViteUserConfig } from 'vitest/config';
2
+ import type { ApplicationTestConfigOptionsInterface, ApplicationUnitConfigOptionsInterface } from './application-vitest-config-options.interface.js';
3
+ export declare const defineApplicationUnitConfig: (options: ApplicationUnitConfigOptionsInterface) => ViteUserConfig;
4
+ export declare const defineApplicationEndToEndConfig: (options: ApplicationTestConfigOptionsInterface) => ViteUserConfig;
@@ -0,0 +1,79 @@
1
+ import path from 'node:path';
2
+ import process from 'node:process';
3
+ import { mergeConfig } from 'vite';
4
+ import { parseShard, ProjectShardSequencer } from './project-shard-sequencer.js';
5
+ const PACKAGES_THROUGH_VITE = ['@application-kit/graphql'];
6
+ const TEST_TIMEOUT = 20_000;
7
+ const cellOf = (argv) => {
8
+ const shard = parseShard(argv);
9
+ if (shard === undefined) {
10
+ return 'all';
11
+ }
12
+ return shard.index === 1 ? 'client' : 'server';
13
+ };
14
+ const coverageOf = (scope) => ({
15
+ clean: true,
16
+ cleanOnRerun: true,
17
+ exclude: [...(scope.exclude ?? [])],
18
+ include: [...scope.include],
19
+ provider: 'v8',
20
+ reporter: ['text'],
21
+ skipFull: true,
22
+ thresholds: scope.thresholds,
23
+ });
24
+ const sharedTestOptions = (options) => ({
25
+ experimental: { fsModuleCache: true, fsModuleCachePath: '.vitest-module-cache' },
26
+ globals: true,
27
+ isolate: false,
28
+ server: { deps: { inline: [...PACKAGES_THROUGH_VITE, ...(options.inlineDependencies ?? [])] } },
29
+ setupFiles: [...options.setupFiles],
30
+ testTimeout: TEST_TIMEOUT,
31
+ });
32
+ const serverProject = (options) => ({
33
+ extends: true,
34
+ test: {
35
+ environment: 'node',
36
+ exclude: [...options.excludeSpecs, ...options.clientSpecs],
37
+ include: [...options.specs],
38
+ name: 'server',
39
+ },
40
+ });
41
+ const clientProject = (options) => ({
42
+ extends: true,
43
+ test: { environment: 'jsdom', include: [...options.clientSpecs], name: 'client' },
44
+ });
45
+ const PROJECTS_OF_CELL = {
46
+ all: [serverProject, clientProject],
47
+ client: [clientProject],
48
+ server: [serverProject],
49
+ };
50
+ const unitCoverageScope = (cell, options) => ({
51
+ exclude: [...(cell === 'server' ? options.clientSources : []), ...(options.coverage.exclude ?? [])],
52
+ include: cell === 'client' ? options.clientSources : options.coverage.include,
53
+ thresholds: options.coverage.thresholds,
54
+ });
55
+ export const defineApplicationUnitConfig = (options) => {
56
+ const cell = cellOf(process.argv);
57
+ return mergeConfig(options.viteConfig, {
58
+ root: options.root,
59
+ test: {
60
+ ...sharedTestOptions(options),
61
+ coverage: coverageOf(unitCoverageScope(cell, options)),
62
+ projects: PROJECTS_OF_CELL[cell].map((project) => project(options)),
63
+ sequence: { sequencer: ProjectShardSequencer },
64
+ },
65
+ });
66
+ };
67
+ export const defineApplicationEndToEndConfig = (options) => {
68
+ const graphql = path.resolve(options.root, 'node_modules/graphql/index.js');
69
+ return mergeConfig(options.viteConfig, {
70
+ resolve: { alias: { graphql } },
71
+ root: options.root,
72
+ test: {
73
+ ...sharedTestOptions(options),
74
+ coverage: coverageOf(options.coverage),
75
+ environment: 'node',
76
+ include: [...options.specs],
77
+ },
78
+ });
79
+ };
@@ -1 +1,3 @@
1
+ export * from './application-vitest-config.js';
2
+ export * from './application-vitest-config-options.interface.js';
1
3
  export * from './project-shard-sequencer.js';
@@ -1 +1,3 @@
1
+ export * from './application-vitest-config.js';
2
+ export * from './application-vitest-config-options.interface.js';
1
3
  export * from './project-shard-sequencer.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@application-kit/testing",
3
- "version": "0.2.0",
4
- "description": "Тестовая инфраструктура NestJS-приложений: шины CQRS, логгер, фейковый microservice, сборка web-приложения, Postgres в testcontainers и раскладка ячеек CI по проектам vitest",
3
+ "version": "0.3.0",
4
+ "description": "Тестовая инфраструктура NestJS-приложений: шины CQRS, логгер, фейковый microservice, сборка web-приложения, Postgres в testcontainers, раскладка ячеек CI по проектам vitest и фабрики конфигов vitest",
5
5
  "keywords": [
6
6
  "cqrs",
7
7
  "nestjs",
@@ -76,6 +76,7 @@
76
76
  "testcontainers": "12.1.0",
77
77
  "typeorm": "^1.1.0",
78
78
  "typescript": "^6.0.3",
79
+ "vite": "^8.2.1",
79
80
  "vitest": "^4.1.11"
80
81
  },
81
82
  "peerDependencies": {
@@ -90,9 +91,13 @@
90
91
  "rxjs": "^7.8.2",
91
92
  "testcontainers": "^12.1.0",
92
93
  "typeorm": "^1.0.0",
94
+ "vite": "^8.0.0",
93
95
  "vitest": "^4.1.0"
94
96
  },
95
97
  "peerDependenciesMeta": {
98
+ "vite": {
99
+ "optional": true
100
+ },
96
101
  "vitest": {
97
102
  "optional": true
98
103
  }