diff --git a/jest.preset.js b/jest.preset.js index c51e590fed..47d0dd3f06 100644 --- a/jest.preset.js +++ b/jest.preset.js @@ -1,4 +1,37 @@ const nxPreset = require('@nx/jest/preset').default; +// Dependencies that ship ESM-only builds Jest cannot `require`. A suite that reaches one of them +// fails to LOAD rather than failing an assertion — Jest reports that as a suite error, which reads +// like "no tests here" instead of "coverage is zero", so the tests silently stop running. +// +// This list used to live in `packages/core/jest.config.ts` (with a copy in integration-zapier), so +// the other projects that inherit this preset started without it. That is why the videos, +// job-proposal and wakatime service/controller specs — whose testing modules load `@gauzy/core` — +// failed to load. It lives here so a project gets it by inheriting the preset. Keep it to packages +// actually reached by a spec, and add whole dependency subtrees (walk for `"type": "module"`) rather +// than one package per failing run: +// sanitize-html + its parser stack -> rich-html-sanitizer / public-html-sanitizer +// uuid -> time-tracking + email-check suites, and any spec that loads +// `@gauzy/core` for real +// camelcase -> time-tracking suites +// @faker-js/faker -> reached through the entity graph (core/seeds) +// @nestjs/axios -> ships a raw `index.ts` that re-exports `./dist` +// Listing packages, rather than transforming all of node_modules, bounds the transform work. The +// `(?:.*/)?` prefix also matches nested copies and same-named directories inside other packages +// (e.g. `@smithy/uuid`); that only costs some transform time. +// +// Things this depends on that are easy to break from elsewhere: +// - ts-jest (>= 29.3.2; pinned at 29.4.6) compiles a `.js` file under `node_modules` to CommonJS +// whenever Jest hands it over, whatever `allowJs` says — so a project needs nothing else for this +// list to take effect. `allowJs` only matters for repo-local `.js` files. +// - A project that sets its own `transformIgnorePatterns` REPLACES this one rather than adding to +// it: the Angular projects (for jest-preset-angular's `.mjs` handling), `docs` (a narrower list) +// and `integration-zapier` (a copy of this list, which has to track it). +// - `@gauzy/*` is deliberately absent, and adding it would do nothing. Jest resolves workspace +// packages to their real source path (`packages//src/index.ts`), which has no +// `node_modules/` segment, so this pattern is never consulted for them. +const transformIgnorePatterns = [ + 'node_modules/(?!(?:.*/)?(sanitize-html|htmlparser2|domelementtype|domhandler|domutils|dom-serializer|entities|nanoid|parse-srcset|uuid|camelcase|@faker-js|@nestjs/axios)/)' +]; -module.exports = { ...nxPreset }; +module.exports = { ...nxPreset, transformIgnorePatterns }; diff --git a/packages/core/jest.config.ts b/packages/core/jest.config.ts index 4fd460bc8b..c41b4001a1 100644 --- a/packages/core/jest.config.ts +++ b/packages/core/jest.config.ts @@ -6,32 +6,11 @@ module.exports = { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }] }, moduleFileExtensions: ['ts', 'js', 'html'], - // Dependencies that ship ESM-only builds Jest cannot `require`. A suite that reaches one of - // them fails to LOAD rather than failing an assertion — Jest reports that as a suite error, - // which reads like "no tests here" instead of "coverage is zero", so the tests silently stop - // running. That is how both server-side sanitization suites, every time-tracking suite and - // both email-check suites came to contribute nothing. + // `transformIgnorePatterns` — the ESM-only dependencies Jest must transform — is inherited from + // `jest.preset.js`, which documents why each package is there. It used to be defined here (and + // copied into integration-zapier), so the other projects that inherit the preset started without + // it. Do not redefine it here: a project's own key REPLACES the preset's, so a copy would drift. // - // Keep this list to packages actually reached by a spec, and add whole dependency subtrees - // (walk for `"type": "module"`) rather than one package per failing run: - // sanitize-html + its parser stack -> rich-html-sanitizer / public-html-sanitizer - // uuid -> time-tracking + email-check suites - // camelcase -> time-tracking suites - // @faker-js/faker -> reached through the entity graph (core/seeds) - // @nestjs/axios -> ships a raw `index.ts` that re-exports `./dist` - // Listing packages explicitly, rather than transforming all of node_modules, is what keeps the - // run fast. - // - // Two things this list depends on that are easy to break from elsewhere: - // - `allowJs: true` in tsconfig.spec.json — ts-jest hands a `.js` file back UNCHANGED when - // allowJs is false, so without it every exception here silently stops working. - // - `@gauzy/*` is deliberately absent, and adding it would do nothing. Jest resolves - // workspace packages to their real source path (`packages//src/index.ts`), which has - // no `node_modules/` segment, so this pattern is never consulted for them. A second, older - // copy of this key used to list `@gauzy`; it was inert there too. - transformIgnorePatterns: [ - 'node_modules/(?!(?:.*/)?(sanitize-html|htmlparser2|domelementtype|domhandler|domutils|dom-serializer|entities|nanoid|parse-srcset|uuid|camelcase|@faker-js|@nestjs/axios)/)' - ], // Jest's own default (`/node_modules/`) plus the fresh-database migration smoke test. That one // spec runs the whole migration chain through ts-jest — about 3-6 minutes with a warm transform // cache, and up to half an hour on a cold one — so leaving it in `nx test core` would make every diff --git a/packages/core/src/lib/core/testing/idempotency/README.md b/packages/core/src/lib/core/testing/idempotency/README.md index ea44459eb3..cda1f8b66c 100644 --- a/packages/core/src/lib/core/testing/idempotency/README.md +++ b/packages/core/src/lib/core/testing/idempotency/README.md @@ -76,7 +76,8 @@ test-tooling layer only (`packages/plugins/integration-zapier/jest.config.ts` + `tsconfig.spec.json`): `isolatedModules: true` on the ts-jest transform (transpile per-file instead of type-checking the whole program — the same reason `@gauzy/core`'s own bootstrap doesn't need to satisfy a downstream consumer's stricter flags) plus the same `transformIgnorePatterns` + -`allowJs: true` `packages/core/jest.config.ts`/`tsconfig.spec.json` already needed for the ESM-only +`allowJs: true` `packages/core/jest.config.ts`/`tsconfig.spec.json` already needed (the pattern has +since moved to the root `jest.preset.js`) for the ESM-only deps (`uuid`, ...) reached through `@gauzy/core`'s entity graph. No production code touched by this part of the fix. diff --git a/packages/plugins/integration-wakatime/src/lib/wakatime.controller.spec.ts b/packages/plugins/integration-wakatime/src/lib/wakatime.controller.spec.ts index 900377dd02..2c048ee041 100644 --- a/packages/plugins/integration-wakatime/src/lib/wakatime.controller.spec.ts +++ b/packages/plugins/integration-wakatime/src/lib/wakatime.controller.spec.ts @@ -1,11 +1,18 @@ import { Test } from '@nestjs/testing'; +import { MikroOrmWakatimeRepository } from './repository/mikro-orm-wakatime.repository'; +import { TypeOrmWakatimeRepository } from './repository/type-orm-wakatime.repository'; import { WakatimeController } from './wakatime.controller'; import { WakatimeService } from './wakatime.service'; describe('WakatimeController', () => { let controller: WakatimeController; beforeEach(async () => { const module = await Test.createTestingModule({ - providers: [WakatimeService], + providers: [ + WakatimeService, + // `WakatimeService` is instantiated for real here, so the repositories it injects must resolve. + { provide: TypeOrmWakatimeRepository, useValue: {} }, + { provide: MikroOrmWakatimeRepository, useValue: {} } + ], controllers: [WakatimeController] }).compile(); controller = module.get(WakatimeController); diff --git a/packages/plugins/integration-wakatime/src/lib/wakatime.service.spec.ts b/packages/plugins/integration-wakatime/src/lib/wakatime.service.spec.ts index 498ebe1a45..7f7a1c0da4 100644 --- a/packages/plugins/integration-wakatime/src/lib/wakatime.service.spec.ts +++ b/packages/plugins/integration-wakatime/src/lib/wakatime.service.spec.ts @@ -1,10 +1,17 @@ import { Test } from '@nestjs/testing'; +import { MikroOrmWakatimeRepository } from './repository/mikro-orm-wakatime.repository'; +import { TypeOrmWakatimeRepository } from './repository/type-orm-wakatime.repository'; import { WakatimeService } from './wakatime.service'; describe('WakatimeService', () => { let service: WakatimeService; beforeEach(async () => { const module = await Test.createTestingModule({ - providers: [WakatimeService] + providers: [ + WakatimeService, + // The repositories the constructor injects; being constructible is all this spec checks. + { provide: TypeOrmWakatimeRepository, useValue: {} }, + { provide: MikroOrmWakatimeRepository, useValue: {} } + ] }).compile(); service = module.get(WakatimeService); }); diff --git a/packages/plugins/integration-zapier/jest.config.ts b/packages/plugins/integration-zapier/jest.config.ts index d9d5df095c..907293b9b8 100644 --- a/packages/plugins/integration-zapier/jest.config.ts +++ b/packages/plugins/integration-zapier/jest.config.ts @@ -9,9 +9,10 @@ module.exports = { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }] }, moduleFileExtensions: ['ts', 'js', 'html'], - // Mirrors packages/core/jest.config.ts's own list, for the same reason: importing `@gauzy/core` - // reaches ESM-only builds Jest cannot `require` (e.g. `uuid`, via `core/context/request-context`). - // Keep in sync with that list if it grows. + // Mirrors the list in the root `jest.preset.js` (it used to live in packages/core/jest.config.ts), + // for the same reason: importing `@gauzy/core` reaches ESM-only builds Jest cannot `require` + // (e.g. `uuid`, via `core/context/request-context`). This key REPLACES the preset's rather than + // adding to it, so keep it in sync with that list if it grows. transformIgnorePatterns: [ 'node_modules/(?!(?:.*/)?(sanitize-html|htmlparser2|domelementtype|domhandler|domutils|dom-serializer|entities|nanoid|parse-srcset|uuid|camelcase|@faker-js|@nestjs/axios)/)' ], diff --git a/packages/plugins/job-proposal/src/lib/proposal-template/employee-proposal-template.controller.spec.ts b/packages/plugins/job-proposal/src/lib/proposal-template/employee-proposal-template.controller.spec.ts index e8ff69fe3f..9fb5bbf836 100644 --- a/packages/plugins/job-proposal/src/lib/proposal-template/employee-proposal-template.controller.spec.ts +++ b/packages/plugins/job-proposal/src/lib/proposal-template/employee-proposal-template.controller.spec.ts @@ -1,11 +1,24 @@ +import { PermissionGuard, TenantPermissionGuard } from '@gauzy/core'; import { Test, TestingModule } from '@nestjs/testing'; import { EmployeeProposalTemplateController } from './employee-proposal-template.controller'; +import { EmployeeProposalTemplateService } from './employee-proposal-template.service'; describe('EmployeeProposalTemplateController', () => { let controller: EmployeeProposalTemplateController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - controllers: [EmployeeProposalTemplateController] - }).compile(); + controllers: [EmployeeProposalTemplateController], + // The service the controller's constructor injects. Its own construction is covered by + // `employee-proposal-template.service.spec.ts`; here it only has to resolve. + providers: [{ provide: EmployeeProposalTemplateService, useValue: {} }] + }) + // `@UseGuards` guards are instantiated by the module that owns the controller, so the real ones + // would drag in the cache manager and the role-permission service. They are not constructor + // dependencies of the controller, so they are out of scope for this check. + .overrideGuard(TenantPermissionGuard) + .useValue({ canActivate: () => true }) + .overrideGuard(PermissionGuard) + .useValue({ canActivate: () => true }) + .compile(); controller = module.get(EmployeeProposalTemplateController); }); it('should be defined', () => { diff --git a/packages/plugins/job-proposal/src/lib/proposal-template/employee-proposal-template.service.spec.ts b/packages/plugins/job-proposal/src/lib/proposal-template/employee-proposal-template.service.spec.ts index cc58d7d6cf..3763a3db1d 100644 --- a/packages/plugins/job-proposal/src/lib/proposal-template/employee-proposal-template.service.spec.ts +++ b/packages/plugins/job-proposal/src/lib/proposal-template/employee-proposal-template.service.spec.ts @@ -1,10 +1,17 @@ import { Test, TestingModule } from '@nestjs/testing'; import { EmployeeProposalTemplateService } from './employee-proposal-template.service'; +import { MikroOrmEmployeeProposalTemplateRepository } from './repository/mikro-orm-employee-proposal-template.repository'; +import { TypeOrmEmployeeProposalTemplateRepository } from './repository/type-orm-employee-proposal-template.repository'; describe('EmployeeProposalTemplateService', () => { let service: EmployeeProposalTemplateService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [EmployeeProposalTemplateService] + providers: [ + EmployeeProposalTemplateService, + // The repositories the constructor injects; being constructible is all this spec checks. + { provide: TypeOrmEmployeeProposalTemplateRepository, useValue: {} }, + { provide: MikroOrmEmployeeProposalTemplateRepository, useValue: {} } + ] }).compile(); service = module.get(EmployeeProposalTemplateService); }); diff --git a/packages/plugins/videos/src/lib/queries/handlers/videos-employee-scope.spec.ts b/packages/plugins/videos/src/lib/queries/handlers/videos-employee-scope.spec.ts index e5dfd92a83..0ca587f5d2 100644 --- a/packages/plugins/videos/src/lib/queries/handlers/videos-employee-scope.spec.ts +++ b/packages/plugins/videos/src/lib/queries/handlers/videos-employee-scope.spec.ts @@ -1,6 +1,6 @@ /** * `@gauzy/core` is mocked so this suite stays a unit test: importing it for real pulls the whole entity - * graph (and ESM-only dependencies this package's jest config does not transform). + * graph, which takes minutes to compile. */ jest.mock('../../services/videos.service', () => ({ VideosService: class {} })); jest.mock('@gauzy/core', () => ({ diff --git a/packages/plugins/videos/src/lib/videos.controller.spec.ts b/packages/plugins/videos/src/lib/videos.controller.spec.ts index 4c587d77c4..be35050ed5 100644 --- a/packages/plugins/videos/src/lib/videos.controller.spec.ts +++ b/packages/plugins/videos/src/lib/videos.controller.spec.ts @@ -1,4 +1,8 @@ +import { EmployeeTrackedDataGuard, PermissionGuard, TenantPermissionGuard } from '@gauzy/core'; +import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { Test, TestingModule } from '@nestjs/testing'; +import { MikroOrmVideoRepository } from './repositories/mikro-orm-video.repository'; +import { TypeOrmVideoRepository } from './repositories/type-orm-video.repository'; import { VideosService } from './services/videos.service'; import { VideosController } from './videos.controller'; describe('VideosController', () => { @@ -6,8 +10,28 @@ describe('VideosController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [VideosController], - providers: [VideosService] - }).compile(); + providers: [ + VideosService, + // The controller dispatches through CQRS; these are the buses its constructor injects. + { provide: CommandBus, useValue: {} }, + { provide: QueryBus, useValue: {} }, + // `VideosService` is not a dependency of the controller, which dispatches through CQRS. It is + // kept because the original spec declared it; the module still instantiates it, so its + // repositories must resolve. + { provide: TypeOrmVideoRepository, useValue: {} }, + { provide: MikroOrmVideoRepository, useValue: {} } + ] + }) + // `@UseGuards` guards are instantiated by the module that owns the controller, so the real ones + // would drag in the cache manager, the role-permission service and a DataSource. They are not + // constructor dependencies of the controller, so they are out of scope for this check. + .overrideGuard(TenantPermissionGuard) + .useValue({ canActivate: () => true }) + .overrideGuard(PermissionGuard) + .useValue({ canActivate: () => true }) + .overrideGuard(EmployeeTrackedDataGuard) + .useValue({ canActivate: () => true }) + .compile(); controller = module.get(VideosController); }); it('should be defined', () => { diff --git a/packages/plugins/videos/src/lib/videos.service.spec.ts b/packages/plugins/videos/src/lib/videos.service.spec.ts index b26e3bf0e6..282019b17c 100644 --- a/packages/plugins/videos/src/lib/videos.service.spec.ts +++ b/packages/plugins/videos/src/lib/videos.service.spec.ts @@ -1,10 +1,17 @@ import { Test, TestingModule } from '@nestjs/testing'; +import { MikroOrmVideoRepository } from './repositories/mikro-orm-video.repository'; +import { TypeOrmVideoRepository } from './repositories/type-orm-video.repository'; import { VideosService } from './services/videos.service'; describe('VideosService', () => { let service: VideosService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [VideosService] + providers: [ + VideosService, + // The repositories the constructor injects; being constructible is all this spec checks. + { provide: TypeOrmVideoRepository, useValue: {} }, + { provide: MikroOrmVideoRepository, useValue: {} } + ] }).compile(); service = module.get(VideosService); });