diff --git a/packages/@n8n/decorators/jest.config.js b/packages/@n8n/decorators/jest.config.js deleted file mode 100644 index dd71dd6ae3c..00000000000 --- a/packages/@n8n/decorators/jest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('jest').Config} */ -module.exports = { - ...require('../../../jest.config'), - transform: { - '^.+\\.ts$': ['ts-jest', { isolatedModules: false }], - }, - coveragePathIgnorePatterns: ['index.ts'], -}; diff --git a/packages/@n8n/decorators/package.json b/packages/@n8n/decorators/package.json index 5f92dd18bd7..873e491101b 100644 --- a/packages/@n8n/decorators/package.json +++ b/packages/@n8n/decorators/package.json @@ -11,9 +11,9 @@ "lint": "eslint . --quiet", "lint:fix": "eslint . --fix", "watch": "tsc -p tsconfig.build.json --watch", - "test": "jest", - "test:unit": "jest", - "test:dev": "jest --watch" + "test": "vitest run", + "test:unit": "vitest run", + "test:dev": "vitest" }, "main": "dist/index.js", "module": "src/index.ts", @@ -23,8 +23,10 @@ ], "devDependencies": { "@n8n/typescript-config": "workspace:*", + "@n8n/vitest-config": "workspace:*", "@types/express": "catalog:", "@types/lodash": "catalog:", + "vitest": "catalog:", "zod": "catalog:" }, "dependencies": { diff --git a/packages/@n8n/decorators/src/__tests__/redactable.test.ts b/packages/@n8n/decorators/src/__tests__/redactable.test.ts index 831af55d943..2bc133bf14e 100644 --- a/packages/@n8n/decorators/src/__tests__/redactable.test.ts +++ b/packages/@n8n/decorators/src/__tests__/redactable.test.ts @@ -200,7 +200,7 @@ describe('Redactable Decorator', () => { }); it('should correctly apply the original method', () => { - const spy = jest.spyOn(instance, 'methodWithUser'); + const spy = vi.spyOn(instance, 'methodWithUser'); const input = { user: { diff --git a/packages/@n8n/decorators/src/command/__tests__/command.test.ts b/packages/@n8n/decorators/src/command/__tests__/command.test.ts index afe59e7e5e1..35eae2e087c 100644 --- a/packages/@n8n/decorators/src/command/__tests__/command.test.ts +++ b/packages/@n8n/decorators/src/command/__tests__/command.test.ts @@ -8,7 +8,7 @@ describe('@Command decorator', () => { let commandMetadata: CommandMetadata; beforeEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); Container.reset(); commandMetadata = new CommandMetadata(); diff --git a/packages/@n8n/decorators/src/context-establishment/__tests__/context-establishment-hook.test.ts b/packages/@n8n/decorators/src/context-establishment/__tests__/context-establishment-hook.test.ts index 7da6738799f..01be0d86255 100644 --- a/packages/@n8n/decorators/src/context-establishment/__tests__/context-establishment-hook.test.ts +++ b/packages/@n8n/decorators/src/context-establishment/__tests__/context-establishment-hook.test.ts @@ -14,7 +14,7 @@ describe('@ContextEstablishmentHook decorator', () => { let hookMetadata: ContextEstablishmentHookMetadata; beforeEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); hookMetadata = new ContextEstablishmentHookMetadata(); Container.set(ContextEstablishmentHookMetadata, hookMetadata); diff --git a/packages/@n8n/decorators/src/controller/__tests__/args.test.ts b/packages/@n8n/decorators/src/controller/__tests__/args.test.ts index b823b172654..b0e7f0d2c8a 100644 --- a/packages/@n8n/decorators/src/controller/__tests__/args.test.ts +++ b/packages/@n8n/decorators/src/controller/__tests__/args.test.ts @@ -8,7 +8,7 @@ describe('Args Decorators', () => { let controllerRegistryMetadata: ControllerRegistryMetadata; beforeEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); controllerRegistryMetadata = new ControllerRegistryMetadata(); Container.set(ControllerRegistryMetadata, controllerRegistryMetadata); diff --git a/packages/@n8n/decorators/src/controller/__tests__/license.test.ts b/packages/@n8n/decorators/src/controller/__tests__/license.test.ts index 31a350f79a0..b13962dcf7f 100644 --- a/packages/@n8n/decorators/src/controller/__tests__/license.test.ts +++ b/packages/@n8n/decorators/src/controller/__tests__/license.test.ts @@ -9,7 +9,7 @@ describe('@Licensed Decorator', () => { let controllerRegistryMetadata: ControllerRegistryMetadata; beforeEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); controllerRegistryMetadata = new ControllerRegistryMetadata(); Container.set(ControllerRegistryMetadata, controllerRegistryMetadata); diff --git a/packages/@n8n/decorators/src/controller/__tests__/rest-controller.test.ts b/packages/@n8n/decorators/src/controller/__tests__/rest-controller.test.ts index 2339167210d..3eb60f6a332 100644 --- a/packages/@n8n/decorators/src/controller/__tests__/rest-controller.test.ts +++ b/packages/@n8n/decorators/src/controller/__tests__/rest-controller.test.ts @@ -8,7 +8,7 @@ describe('@RestController Decorator', () => { let controllerRegistryMetadata: ControllerRegistryMetadata; beforeEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); Container.reset(); controllerRegistryMetadata = new ControllerRegistryMetadata(); diff --git a/packages/@n8n/decorators/src/controller/__tests__/root-level-controller.test.ts b/packages/@n8n/decorators/src/controller/__tests__/root-level-controller.test.ts index 3be1adfc56f..f853f1eca75 100644 --- a/packages/@n8n/decorators/src/controller/__tests__/root-level-controller.test.ts +++ b/packages/@n8n/decorators/src/controller/__tests__/root-level-controller.test.ts @@ -8,7 +8,7 @@ describe('@RootLevelController Decorator', () => { let controllerRegistryMetadata: ControllerRegistryMetadata; beforeEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); Container.reset(); controllerRegistryMetadata = new ControllerRegistryMetadata(); diff --git a/packages/@n8n/decorators/src/controller/__tests__/route.test.ts b/packages/@n8n/decorators/src/controller/__tests__/route.test.ts index 47f19145459..b1bce8a8408 100644 --- a/packages/@n8n/decorators/src/controller/__tests__/route.test.ts +++ b/packages/@n8n/decorators/src/controller/__tests__/route.test.ts @@ -9,7 +9,7 @@ describe('Route Decorators', () => { let controllerRegistryMetadata: ControllerRegistryMetadata; beforeEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); controllerRegistryMetadata = new ControllerRegistryMetadata(); Container.set(ControllerRegistryMetadata, controllerRegistryMetadata); diff --git a/packages/@n8n/decorators/src/controller/__tests__/scoped.test.ts b/packages/@n8n/decorators/src/controller/__tests__/scoped.test.ts index aaf952cdf2d..08ecd3aae3d 100644 --- a/packages/@n8n/decorators/src/controller/__tests__/scoped.test.ts +++ b/packages/@n8n/decorators/src/controller/__tests__/scoped.test.ts @@ -9,7 +9,7 @@ describe('Scope Decorators', () => { let controllerRegistryMetadata: ControllerRegistryMetadata; beforeEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); controllerRegistryMetadata = new ControllerRegistryMetadata(); Container.set(ControllerRegistryMetadata, controllerRegistryMetadata); diff --git a/packages/@n8n/decorators/src/credential-resolver/__tests__/credential-resolver.test.ts b/packages/@n8n/decorators/src/credential-resolver/__tests__/credential-resolver.test.ts index 38b7d4281c4..3cd1c80bc4e 100644 --- a/packages/@n8n/decorators/src/credential-resolver/__tests__/credential-resolver.test.ts +++ b/packages/@n8n/decorators/src/credential-resolver/__tests__/credential-resolver.test.ts @@ -11,7 +11,7 @@ describe('@CredentialResolver decorator', () => { let resolverMetadata: CredentialResolverEntryMetadata; beforeEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); resolverMetadata = new CredentialResolverEntryMetadata(); Container.set(CredentialResolverEntryMetadata, resolverMetadata); diff --git a/packages/@n8n/decorators/src/execution-lifecycle/__tests__/on-lifecycle-event.test.ts b/packages/@n8n/decorators/src/execution-lifecycle/__tests__/on-lifecycle-event.test.ts index 0d7bf1bf0d5..1c366c68e4f 100644 --- a/packages/@n8n/decorators/src/execution-lifecycle/__tests__/on-lifecycle-event.test.ts +++ b/packages/@n8n/decorators/src/execution-lifecycle/__tests__/on-lifecycle-event.test.ts @@ -10,7 +10,7 @@ describe('OnLifecycleEvent', () => { beforeEach(() => { lifecycleMetadata = new LifecycleMetadata(); Container.set(LifecycleMetadata, lifecycleMetadata); - jest.spyOn(lifecycleMetadata, 'register'); + vi.spyOn(lifecycleMetadata, 'register'); }); it('should register a method decorated with OnLifecycleEvent', () => { diff --git a/packages/@n8n/decorators/src/module/__tests__/module.test.ts b/packages/@n8n/decorators/src/module/__tests__/module.test.ts index 66e3a27d71e..ebbb8232612 100644 --- a/packages/@n8n/decorators/src/module/__tests__/module.test.ts +++ b/packages/@n8n/decorators/src/module/__tests__/module.test.ts @@ -8,7 +8,7 @@ describe('@BackendModule decorator', () => { let moduleMetadata: ModuleMetadata; beforeEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); moduleMetadata = new ModuleMetadata(); Container.set(ModuleMetadata, moduleMetadata); diff --git a/packages/@n8n/decorators/src/multi-main/__tests__/on-multi-main-event.test.ts b/packages/@n8n/decorators/src/multi-main/__tests__/on-multi-main-event.test.ts index f47cd7a52e7..d2408c9a8aa 100644 --- a/packages/@n8n/decorators/src/multi-main/__tests__/on-multi-main-event.test.ts +++ b/packages/@n8n/decorators/src/multi-main/__tests__/on-multi-main-event.test.ts @@ -35,7 +35,7 @@ beforeEach(() => { }); it('should register methods decorated with @OnLeaderTakeover', () => { - jest.spyOn(metadata, 'register'); + vi.spyOn(metadata, 'register'); @Service() class TestService { @@ -51,7 +51,7 @@ it('should register methods decorated with @OnLeaderTakeover', () => { }); it('should register methods decorated with @OnLeaderStepdown', () => { - jest.spyOn(metadata, 'register'); + vi.spyOn(metadata, 'register'); @Service() class TestService { @@ -99,8 +99,8 @@ it('should call decorated methods when events are emitted', async () => { } const testService = Container.get(TestService); - jest.spyOn(testService, 'handleLeaderTakeover'); - jest.spyOn(testService, 'handleLeaderStepdown'); + vi.spyOn(testService, 'handleLeaderTakeover'); + vi.spyOn(testService, 'handleLeaderStepdown'); multiMainSetup.registerEventHandlers(); diff --git a/packages/@n8n/decorators/src/pubsub/__tests__/on-pubsub-event.test.ts b/packages/@n8n/decorators/src/pubsub/__tests__/on-pubsub-event.test.ts index 9e893eaa555..ede0548c28f 100644 --- a/packages/@n8n/decorators/src/pubsub/__tests__/on-pubsub-event.test.ts +++ b/packages/@n8n/decorators/src/pubsub/__tests__/on-pubsub-event.test.ts @@ -15,7 +15,7 @@ describe('@OnPubSubEvent', () => { }); it('should register methods decorated with @OnPubSubEvent', () => { - jest.spyOn(metadata, 'register'); + vi.spyOn(metadata, 'register'); @Service() class TestService { diff --git a/packages/@n8n/decorators/src/shutdown/__tests__/on-shutdown.test.ts b/packages/@n8n/decorators/src/shutdown/__tests__/on-shutdown.test.ts index 81dc414ac13..868b222e496 100644 --- a/packages/@n8n/decorators/src/shutdown/__tests__/on-shutdown.test.ts +++ b/packages/@n8n/decorators/src/shutdown/__tests__/on-shutdown.test.ts @@ -9,7 +9,7 @@ describe('OnShutdown', () => { beforeEach(() => { shutdownMetadata = new ShutdownMetadata(); Container.set(ShutdownMetadata, shutdownMetadata); - jest.spyOn(shutdownMetadata, 'register'); + vi.spyOn(shutdownMetadata, 'register'); }); it('should register a methods that is decorated with OnShutdown', () => { diff --git a/packages/@n8n/decorators/tsconfig.json b/packages/@n8n/decorators/tsconfig.json index 3fc205fd494..c9682bff556 100644 --- a/packages/@n8n/decorators/tsconfig.json +++ b/packages/@n8n/decorators/tsconfig.json @@ -2,7 +2,7 @@ "extends": "@n8n/typescript-config/tsconfig.common.json", "compilerOptions": { "rootDir": ".", - "types": ["node", "jest"], + "types": ["node", "vitest/globals"], "baseUrl": "src", "tsBuildInfoFile": "dist/typecheck.tsbuildinfo", "experimentalDecorators": true, diff --git a/packages/@n8n/decorators/vitest.config.ts b/packages/@n8n/decorators/vitest.config.ts new file mode 100644 index 00000000000..0dc81b94b75 --- /dev/null +++ b/packages/@n8n/decorators/vitest.config.ts @@ -0,0 +1,5 @@ +import { createVitestConfigWithDecorators } from '@n8n/vitest-config/node-decorators'; + +export default createVitestConfigWithDecorators({ + coveragePathIgnorePatterns: ['index.ts'], +}); diff --git a/packages/@n8n/vitest-config/node-decorators.ts b/packages/@n8n/vitest-config/node-decorators.ts new file mode 100644 index 00000000000..dfdd26e2bda --- /dev/null +++ b/packages/@n8n/vitest-config/node-decorators.ts @@ -0,0 +1,36 @@ +import swc from 'unplugin-swc'; +import { mergeConfig } from 'vitest/config'; +import type { InlineConfig } from 'vitest/node'; +import { createVitestConfig } from './node.js'; + +const swcPlugin = swc.vite({ + jsc: { + parser: { + syntax: 'typescript', + decorators: true, + }, + transform: { + legacyDecorator: true, + decoratorMetadata: true, + }, + target: 'es2022', + }, + // Include node_modules TypeScript files (like @n8n/tournament) + exclude: [], +}); + +export const createVitestConfigWithDecorators = (options: InlineConfig = {}) => { + const baseConfig = createVitestConfig(options); + return mergeConfig(baseConfig, { + plugins: [swcPlugin], + esbuild: false, // Disable esbuild - it doesn't support decoratorMetadata + server: { + deps: { + // Inline all dependencies so SWC can transform them (including TypeScript in node_modules) + inline: [/.*/], + }, + }, + }); +}; + +export const vitestConfigWithDecorators = createVitestConfigWithDecorators(); diff --git a/packages/@n8n/vitest-config/package.json b/packages/@n8n/vitest-config/package.json index 03b9ef0d081..3cb81c568f2 100644 --- a/packages/@n8n/vitest-config/package.json +++ b/packages/@n8n/vitest-config/package.json @@ -6,6 +6,9 @@ "vite": "catalog:", "vitest": "catalog:" }, + "dependencies": { + "unplugin-swc": "^1.5.1" + }, "devDependencies": { "@n8n/typescript-config": "workspace:*", "@swc/core": "^1.10.7", @@ -31,6 +34,11 @@ "import": "./dist/node.js", "require": "./dist/node.js", "types": "./dist/node.d.ts" + }, + "./node-decorators": { + "import": "./dist/node-decorators.js", + "require": "./dist/node-decorators.js", + "types": "./dist/node-decorators.d.ts" } }, "scripts": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0f2e778dc8..c7777a82a04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -920,12 +920,18 @@ importers: '@n8n/typescript-config': specifier: workspace:* version: link:../typescript-config + '@n8n/vitest-config': + specifier: workspace:* + version: link:../vitest-config '@types/express': specifier: 'catalog:' version: 5.0.1 '@types/lodash': specifier: 'catalog:' version: 4.17.17 + vitest: + specifier: 'catalog:' + version: 3.1.3(@types/debug@4.1.12)(@types/node@20.19.21)(jiti@2.6.1)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(sass@1.89.2)(terser@5.16.1)(tsx@4.19.3) zod: specifier: 3.25.67 version: 3.25.67 @@ -1638,6 +1644,10 @@ importers: version: 3.1.3(@types/debug@4.1.12)(@types/node@20.19.21)(jiti@2.6.1)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(sass@1.89.2)(terser@5.16.1)(tsx@4.19.3) packages/@n8n/vitest-config: + dependencies: + unplugin-swc: + specifier: ^1.5.1 + version: 1.5.9(@swc/core@1.15.8(@swc/helpers@0.5.17))(rollup@4.52.4) devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -7245,6 +7255,15 @@ packages: rollup: optional: true + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/rollup-android-arm-eabi@4.52.4': resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} cpu: [arm] @@ -13921,6 +13940,10 @@ packages: linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + local-pkg@0.5.0: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} @@ -17768,6 +17791,11 @@ packages: vue-template-es2015-compiler: optional: true + unplugin-swc@1.5.9: + resolution: {integrity: sha512-RKwK3yf0M+MN17xZfF14bdKqfx0zMXYdtOdxLiE6jHAoidupKq3jGdJYANyIM1X/VmABhh1WpdO+/f4+Ol89+g==} + peerDependencies: + '@swc/core': ^1.2.108 + unplugin@1.0.1: resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==} @@ -23738,6 +23766,14 @@ snapshots: optionalDependencies: rollup: 4.52.4 + '@rollup/pluginutils@5.3.0(rollup@4.52.4)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.52.4 + '@rollup/rollup-android-arm-eabi@4.52.4': optional: true @@ -32231,6 +32267,8 @@ snapshots: dependencies: uc.micro: 2.1.0 + load-tsconfig@0.2.5: {} + local-pkg@0.5.0: dependencies: mlly: 1.8.0 @@ -36905,6 +36943,15 @@ snapshots: transitivePeerDependencies: - supports-color + unplugin-swc@1.5.9(@swc/core@1.15.8(@swc/helpers@0.5.17))(rollup@4.52.4): + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.52.4) + '@swc/core': 1.15.8(@swc/helpers@0.5.17) + load-tsconfig: 0.2.5 + unplugin: 2.3.11 + transitivePeerDependencies: + - rollup + unplugin@1.0.1: dependencies: acorn: 8.14.0