diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000000..1eff521897 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(yarn nx g:*)", + "Bash(npx vitest:*)" + ] + } +} diff --git a/npm/ng-packs/.gitignore b/npm/ng-packs/.gitignore index 9d756a8b67..84b48a798c 100644 --- a/npm/ng-packs/.gitignore +++ b/npm/ng-packs/.gitignore @@ -58,3 +58,5 @@ Thumbs.db .angular .nx/ + +vitest.config.*.timestamp* \ No newline at end of file diff --git a/npm/ng-packs/apps/dev-app/project.json b/npm/ng-packs/apps/dev-app/project.json index bea3bf6612..c397d8d449 100644 --- a/npm/ng-packs/apps/dev-app/project.json +++ b/npm/ng-packs/apps/dev-app/project.json @@ -175,10 +175,12 @@ "executor": "@nx/eslint:lint" }, "test": { - "executor": "@nx/jest:jest", + "executor": "@nx/vitest:test", "outputs": ["{workspaceRoot}/coverage/apps/dev-app"], "options": { - "jestConfig": "apps/dev-app/jest.config.ts" + "passWithNoTests": true, + "reportsDirectory": "../../coverage/apps/dev-app", + "silent": false } }, "serve-static": { diff --git a/npm/ng-packs/jest.config.ts b/npm/ng-packs/jest.config.ts index 6b3f2d6e24..2aa4dd9d8a 100644 --- a/npm/ng-packs/jest.config.ts +++ b/npm/ng-packs/jest.config.ts @@ -1,5 +1,8 @@ import { getJestProjectsAsync } from '@nx/jest'; - +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default async () => ({ projects: await getJestProjectsAsync(), }); diff --git a/npm/ng-packs/jest.preset.js b/npm/ng-packs/jest.preset.js index c1c3c4cdcd..861f91ccc4 100644 --- a/npm/ng-packs/jest.preset.js +++ b/npm/ng-packs/jest.preset.js @@ -1,3 +1,7 @@ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ const nxPreset = require('@nx/jest/preset').default; module.exports = { diff --git a/npm/ng-packs/nx.json b/npm/ng-packs/nx.json index 500c939ad2..a573c5c59a 100644 --- a/npm/ng-packs/nx.json +++ b/npm/ng-packs/nx.json @@ -118,6 +118,10 @@ "cache": true, "dependsOn": ["^build"], "inputs": ["production", "^production"] + }, + "@nx/vitest:test": { + "cache": true, + "inputs": ["default", "^production"] } }, "namedInputs": { diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index c5c3b698c7..4903febfb0 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -10,6 +10,8 @@ "build:all": "nx run-many --target=build --all --exclude=dev-app,schematics --prod && npm run build:schematics", "test": "ng test --detect-open-handles=true --run-in-band=true --watch-all=true", "test:all": "nx run-many --target=test --all", + "test:vitest": "vitest", + "test:vitest:project": "sh -c 'vitest --project \"${1:-core}\"' _", "lint-staged": "lint-staged", "lint": "nx workspace-lint && ng lint", "lint:all": "nx run-many --target=lint --all", @@ -80,23 +82,26 @@ "@nx/eslint": "~22.2.0", "@nx/eslint-plugin": "~22.2.0", "@nx/jest": "~22.2.0", - "@nx/js": "~22.2.0", + "@nx/js": "22.2.7", "@nx/plugin": "~22.2.0", + "@nx/vite": "22.2.7", + "@nx/vitest": "22.2.7", "@nx/web": "~22.2.0", "@nx/workspace": "~22.2.0", "@popperjs/core": "~2.11.0", "@schematics/angular": "~21.0.0", "@swc-node/register": "1.9.2", "@swc/cli": "0.6.0", - "@swc/core": "~1.5.0", - "@swc/helpers": "~0.5.0", + "@swc/core": "~1.5.7", + "@swc/helpers": "~0.5.11", "@swimlane/ngx-datatable": "~22.0.0", "@types/express": "~5.0.0", "@types/jest": "29.5.14", - "@types/node": "~20.11.0", + "@types/node": "20.19.9", "@typescript-eslint/eslint-plugin": "7.16.0", "@typescript-eslint/parser": "7.16.0", "@typescript-eslint/utils": "^7.16.0", + "@vitest/coverage-v8": "^4.0.0", "angular-oauth2-oidc": "~20.0.0", "autoprefixer": "^10.4.21", "bootstrap": "~5.0.0", @@ -114,6 +119,7 @@ "jest-canvas-mock": "^2.0.0", "jest-environment-jsdom": "^29.0.0", "jest-preset-angular": "14.6.0", + "jsdom": "~22.1.0", "jsonc-eslint-parser": "^2.0.0", "jsonc-parser": "^2.0.0", "just-clone": "^6.0.0", @@ -137,6 +143,8 @@ "tslib": "^2.3.0", "tslint": "~6.1.0", "typescript": "~5.9.0", + "vite": "^7.0.0", + "vitest": "^4.0.0", "zone.js": "~0.15.0" }, "lint-staged": { diff --git a/npm/ng-packs/packages/account-core/.eslintrc.json b/npm/ng-packs/packages/account-core/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/account-core/.eslintrc.json +++ b/npm/ng-packs/packages/account-core/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/account-core/jest.config.ts b/npm/ng-packs/packages/account-core/jest.config.ts index 7ef24776c3..f6aa97e8c1 100644 --- a/npm/ng-packs/packages/account-core/jest.config.ts +++ b/npm/ng-packs/packages/account-core/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'account-core', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/account-core/project.json b/npm/ng-packs/packages/account-core/project.json index 312bdd30e9..8a201b8006 100644 --- a/npm/ng-packs/packages/account-core/project.json +++ b/npm/ng-packs/packages/account-core/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/account-core"], - "options": { - "jestConfig": "packages/account-core/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/account-core" + } } } } diff --git a/npm/ng-packs/packages/account-core/tsconfig.lib.json b/npm/ng-packs/packages/account-core/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/account-core/tsconfig.lib.json +++ b/npm/ng-packs/packages/account-core/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/account-core/tsconfig.spec.json b/npm/ng-packs/packages/account-core/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/account-core/tsconfig.spec.json +++ b/npm/ng-packs/packages/account-core/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/account-core/vitest.config.mts b/npm/ng-packs/packages/account-core/vitest.config.mts new file mode 100644 index 0000000000..0ef8748fc2 --- /dev/null +++ b/npm/ng-packs/packages/account-core/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/account-core', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'account-core', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/account-core', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/account/.eslintrc.json b/npm/ng-packs/packages/account/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/account/.eslintrc.json +++ b/npm/ng-packs/packages/account/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/account/jest.config.ts b/npm/ng-packs/packages/account/jest.config.ts index 7630a18eb3..207a21558c 100644 --- a/npm/ng-packs/packages/account/jest.config.ts +++ b/npm/ng-packs/packages/account/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'account', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/account/project.json b/npm/ng-packs/packages/account/project.json index 41c6597e2a..5d02bebd76 100644 --- a/npm/ng-packs/packages/account/project.json +++ b/npm/ng-packs/packages/account/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/account"], - "options": { - "jestConfig": "packages/account/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/account" + } } } } diff --git a/npm/ng-packs/packages/account/tsconfig.lib.json b/npm/ng-packs/packages/account/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/account/tsconfig.lib.json +++ b/npm/ng-packs/packages/account/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/account/tsconfig.spec.json b/npm/ng-packs/packages/account/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/account/tsconfig.spec.json +++ b/npm/ng-packs/packages/account/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/account/vitest.config.mts b/npm/ng-packs/packages/account/vitest.config.mts new file mode 100644 index 0000000000..ed2d3c8b2c --- /dev/null +++ b/npm/ng-packs/packages/account/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/account', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'account', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/account', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/components/.eslintrc.json b/npm/ng-packs/packages/components/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/components/.eslintrc.json +++ b/npm/ng-packs/packages/components/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/components/jest.config.ts b/npm/ng-packs/packages/components/jest.config.ts index ff37b7e9e4..4a2a95bc72 100644 --- a/npm/ng-packs/packages/components/jest.config.ts +++ b/npm/ng-packs/packages/components/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'components', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/components/project.json b/npm/ng-packs/packages/components/project.json index 671fabe461..d80db9b26d 100644 --- a/npm/ng-packs/packages/components/project.json +++ b/npm/ng-packs/packages/components/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/components"], - "options": { - "jestConfig": "packages/components/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/components" + } } } } diff --git a/npm/ng-packs/packages/components/tsconfig.lib.json b/npm/ng-packs/packages/components/tsconfig.lib.json index 7dde5f04bf..e4e2e20714 100644 --- a/npm/ng-packs/packages/components/tsconfig.lib.json +++ b/npm/ng-packs/packages/components/tsconfig.lib.json @@ -13,7 +13,19 @@ "exclude": [ "src/test-setup.ts", "src/**/*.spec.ts", - "jest.config.ts" + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" ], "include": ["src/**/*.ts"] } diff --git a/npm/ng-packs/packages/components/tsconfig.spec.json b/npm/ng-packs/packages/components/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/components/tsconfig.spec.json +++ b/npm/ng-packs/packages/components/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/components/vitest.config.mts b/npm/ng-packs/packages/components/vitest.config.mts new file mode 100644 index 0000000000..2037268556 --- /dev/null +++ b/npm/ng-packs/packages/components/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/components', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'components', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/components', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/core/.eslintrc.json b/npm/ng-packs/packages/core/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/core/.eslintrc.json +++ b/npm/ng-packs/packages/core/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/core/jest.config.ts b/npm/ng-packs/packages/core/jest.config.ts deleted file mode 100644 index 3dda0ce769..0000000000 --- a/npm/ng-packs/packages/core/jest.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -export default { - displayName: 'core', - preset: '../../jest.preset.js', - setupFilesAfterEnv: ['/src/test-setup.ts'], - globals: {}, - coverageDirectory: '../../coverage/packages/core', - transform: { - '^.+.(ts|mjs|js|html)$': [ - 'jest-preset-angular', - { - tsconfig: '/tsconfig.spec.json', - stringifyContentPathRegex: '\\.(html|svg)$', - }, - ], - }, - transformIgnorePatterns: ['node_modules/(?!.*.mjs$)'], - snapshotSerializers: [ - 'jest-preset-angular/build/serializers/no-ng-attributes', - 'jest-preset-angular/build/serializers/ng-snapshot', - 'jest-preset-angular/build/serializers/html-comment', - ], -}; diff --git a/npm/ng-packs/packages/core/project.json b/npm/ng-packs/packages/core/project.json index a6b7789814..69bbb4f572 100644 --- a/npm/ng-packs/packages/core/project.json +++ b/npm/ng-packs/packages/core/project.json @@ -22,16 +22,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/core"], - "options": { - "jestConfig": "packages/core/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/core" + } } } } diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index 3d77d1bd8f..81f9305f68 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts @@ -3,9 +3,6 @@ import { inject, input, isDevMode, - OnInit, - Optional, - SkipSelf, Type, } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; diff --git a/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts index 7b3bd0319b..f26c12b706 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts @@ -1,16 +1,16 @@ -import { - Directive, - EmbeddedViewRef, - Input, - IterableChangeRecord, - IterableChanges, - IterableDiffer, - IterableDiffers, - OnChanges, - TemplateRef, - TrackByFunction, - ViewContainerRef, - inject +import { + Directive, + EmbeddedViewRef, + Input, + IterableChangeRecord, + IterableChanges, + IterableDiffer, + IterableDiffers, + OnChanges, + TemplateRef, + TrackByFunction, + ViewContainerRef, + inject, } from '@angular/core'; import clone from 'just-clone'; import compare from 'just-compare'; @@ -67,6 +67,7 @@ export class ForDirective implements OnChanges { emptyRef?: TemplateRef; private differ!: IterableDiffer | null; + private lastItemsRef: any[] | null = null; private isShowEmptyRef!: boolean; @@ -136,6 +137,7 @@ export class ForDirective implements OnChanges { this.vcRef.createEmbeddedView(this.emptyRef).rootNodes; this.isShowEmptyRef = true; this.differ = null; + this.lastItemsRef = null; return; } @@ -169,6 +171,14 @@ export class ForDirective implements OnChanges { } ngOnChanges() { + if (!this.items) return; + + // Recreate differ if items array reference changed + if (this.lastItemsRef !== this.items) { + this.differ = null; + this.lastItemsRef = this.items; + } + let items = clone(this.items) as any[]; if (!Array.isArray(items)) return; diff --git a/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts index 50b805dba4..2c17885984 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts @@ -1,13 +1,13 @@ -import { - AfterViewInit, - ChangeDetectorRef, - Directive, - Input, - OnChanges, - OnDestroy, - TemplateRef, - ViewContainerRef, - inject +import { + AfterViewInit, + ChangeDetectorRef, + Directive, + Input, + OnChanges, + OnDestroy, + TemplateRef, + ViewContainerRef, + inject, } from '@angular/core'; import { ReplaySubject, Subscription } from 'rxjs'; import { distinctUntilChanged, take } from 'rxjs/operators'; @@ -19,7 +19,7 @@ import { QueueManager } from '../utils/queue'; selector: '[abpPermission]', }) export class PermissionDirective implements OnDestroy, OnChanges, AfterViewInit { - private templateRef = inject>(TemplateRef, { optional: true })!; + private templateRef = inject>(TemplateRef, { optional: true }); private vcRef = inject(ViewContainerRef); private permissionService = inject(PermissionService); private cdRef = inject(ChangeDetectorRef); @@ -45,7 +45,9 @@ export class PermissionDirective implements OnDestroy, OnChanges, AfterViewInit .pipe(distinctUntilChanged()) .subscribe(isGranted => { this.vcRef.clear(); - if (isGranted) this.vcRef.createEmbeddedView(this.templateRef); + if (isGranted && this.templateRef) { + this.vcRef.createEmbeddedView(this.templateRef); + } if (this.runChangeDetection) { if (!this.rendered) { this.cdrSubject.next(); diff --git a/npm/ng-packs/packages/core/src/lib/services/list.service.ts b/npm/ng-packs/packages/core/src/lib/services/list.service.ts index 3cfc0ecac9..2b0f1a0efa 100644 --- a/npm/ng-packs/packages/core/src/lib/services/list.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/list.service.ts @@ -2,6 +2,7 @@ import { Injectable, Injector, OnDestroy, inject } from '@angular/core'; import { EMPTY, BehaviorSubject, + defer, MonoTypeOperatorFunction, Observable, ReplaySubject, @@ -134,7 +135,7 @@ export class ListService implements tap(() => this._isLoading$.next(true)), tap(() => this._requestStatus.next('loading')), switchMap(query => - streamCreatorCallback(query).pipe( + defer(() => streamCreatorCallback(query)).pipe( catchError(() => { this._requestStatus.next('error'); return EMPTY; diff --git a/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts b/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts index 9722ab7660..0cdae2680a 100644 --- a/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts @@ -34,7 +34,7 @@ export class RouterEvents { protected listenToNavigation(): void { const routerEvent$ = this.router.events.pipe( - filter(e => e instanceof NavigationEvent.End && !e.url.includes('error')) + filter(e => e instanceof NavigationEvent.End && e.url != null && !e.url.includes('error')) ) as Observable; routerEvent$.subscribe(event => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts index 6a44e0d83e..5f46e900bd 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts @@ -1,4 +1,4 @@ -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { AutofocusDirective } from '../directives/autofocus.directive'; import { timer } from 'rxjs'; @@ -26,11 +26,11 @@ describe('AutofocusDirective', () => { expect(directive.delay).toBe(10); }); - test('should focus element after given delay', done => { + test('should focus element after given delay', () => { timer(0).subscribe(() => expect('input').not.toBeFocused()); timer(11).subscribe(() => { expect('input').toBeFocused(); - done(); + expect.hasAssertions(); }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts index 9ba6095206..e94a099d19 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts @@ -1,26 +1,23 @@ -import { Component, DebugElement } from '@angular/core' -import { ComponentFixture, TestBed } from '@angular/core/testing' -import { TrackCapsLockDirective } from '../directives'; import { By } from '@angular/platform-browser'; +import { Component, DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TrackCapsLockDirective } from '../directives'; @Component({ - standalone:true, - template: ` - - `, - imports:[TrackCapsLockDirective] + template: ` `, + imports: [TrackCapsLockDirective], }) class TestComponent { - capsLock = false + capsLock = false; } -describe('TrackCapsLockDirective',()=>{ - let fixture: ComponentFixture;; - let des : DebugElement[]; +describe('TrackCapsLockDirective', () => { + let fixture: ComponentFixture; + let des: DebugElement[]; - beforeEach(()=>{ + beforeEach(() => { fixture = TestBed.configureTestingModule({ - imports: [ TestComponent ] + imports: [TestComponent], }).createComponent(TestComponent); fixture.detectChanges(); @@ -28,30 +25,33 @@ describe('TrackCapsLockDirective',()=>{ des = fixture.debugElement.queryAll(By.directive(TrackCapsLockDirective)); }); - test.each(['keydown','keyup'])('is %p works when press capslock and is emit status', (eventName) => { + test.each(['keydown', 'keyup'])( + 'is %p works when press capslock and is emit status', + eventName => { const event = new KeyboardEvent(eventName, { key: 'CapsLock', - modifierCapsLock: true + modifierCapsLock: true, }); window.dispatchEvent(event); fixture.detectChanges(); - expect(fixture.componentInstance.capsLock).toBe(true) - }); + expect(fixture.componentInstance.capsLock).toBe(true); + }, + ); - test.each(['keydown','keyup'])('is %p detect the change capslock is emit status', (eventName) => { - const trueEvent = new KeyboardEvent(eventName, { - key: 'CapsLock', - modifierCapsLock: true - }); - window.dispatchEvent(trueEvent); - fixture.detectChanges(); - expect(fixture.componentInstance.capsLock).toBe(true) - const falseEvent = new KeyboardEvent(eventName, { - key: 'CapsLock', - modifierCapsLock: false - }); - window.dispatchEvent(falseEvent); - fixture.detectChanges(); - expect(fixture.componentInstance.capsLock).toBe(false) + test.each(['keydown', 'keyup'])('is %p detect the change capslock is emit status', eventName => { + const trueEvent = new KeyboardEvent(eventName, { + key: 'CapsLock', + modifierCapsLock: true, + }); + window.dispatchEvent(trueEvent); + fixture.detectChanges(); + expect(fixture.componentInstance.capsLock).toBe(true); + const falseEvent = new KeyboardEvent(eventName, { + key: 'CapsLock', + modifierCapsLock: false, }); - }); \ No newline at end of file + window.dispatchEvent(falseEvent); + fixture.detectChanges(); + expect(fixture.componentInstance.capsLock).toBe(false); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts index 3067ab9536..a753ffe67a 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts @@ -1,6 +1,6 @@ import { provideHttpClientTesting } from '@angular/common/http/testing'; import { provideHttpClient } from '@angular/common/http'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { of } from 'rxjs'; import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; import { @@ -132,7 +132,7 @@ describe('ConfigStateService', () => { }, { provide: AbpApplicationLocalizationService, - useValue: { get: () => APPLICATION_LOCALIZATION_DATA }, + useValue: { get: () => of(APPLICATION_LOCALIZATION_DATA) }, }, IncludeLocalizationResourcesProvider, ], @@ -142,84 +142,84 @@ describe('ConfigStateService', () => { spectator = createService(); configState = spectator.service; - jest.spyOn(configState, 'getAll').mockReturnValue(CONFIG_STATE_DATA); - jest.spyOn(configState, 'getAll$').mockReturnValue(of(CONFIG_STATE_DATA)); - jest.spyOn(configState, 'getOne').mockImplementation((key) => { + vi.spyOn(configState, 'getAll').mockReturnValue(CONFIG_STATE_DATA); + vi.spyOn(configState, 'getAll$').mockReturnValue(of(CONFIG_STATE_DATA)); + vi.spyOn(configState, 'getOne').mockImplementation((key) => { if (key === 'localization') return CONFIG_STATE_DATA.localization; return undefined; }); - jest.spyOn(configState, 'getOne$').mockImplementation((key) => { + vi.spyOn(configState, 'getOne$').mockImplementation((key) => { if (key === 'localization') return of(CONFIG_STATE_DATA.localization); return of(undefined); }); - jest.spyOn(configState, 'getDeep').mockImplementation((key) => { + vi.spyOn(configState, 'getDeep').mockImplementation((key) => { if (key === 'localization.languages') return CONFIG_STATE_DATA.localization.languages; if (key === 'test') return undefined; return undefined; }); - jest.spyOn(configState, 'getDeep$').mockImplementation((key) => { + vi.spyOn(configState, 'getDeep$').mockImplementation((key) => { if (key === 'localization.languages') return of(CONFIG_STATE_DATA.localization.languages); return of(undefined); }); - jest.spyOn(configState, 'getFeature').mockImplementation((key) => { + vi.spyOn(configState, 'getFeature').mockImplementation((key) => { if (key === 'Chat.Enable') return CONFIG_STATE_DATA.features.values['Chat.Enable']; return undefined; }); - jest.spyOn(configState, 'getFeature$').mockImplementation((key) => { + vi.spyOn(configState, 'getFeature$').mockImplementation((key) => { if (key === 'Chat.Enable') return of(CONFIG_STATE_DATA.features.values['Chat.Enable']); return of(undefined); }); - jest.spyOn(configState, 'getSetting').mockImplementation((key) => { + vi.spyOn(configState, 'getSetting').mockImplementation((key) => { if (key === 'Abp.Localization.DefaultLanguage') return CONFIG_STATE_DATA.setting.values['Abp.Localization.DefaultLanguage']; return undefined; }); - jest.spyOn(configState, 'getSetting$').mockImplementation((key) => { + vi.spyOn(configState, 'getSetting$').mockImplementation((key) => { if (key === 'Abp.Localization.DefaultLanguage') return of(CONFIG_STATE_DATA.setting.values['Abp.Localization.DefaultLanguage']); return of(undefined); }); - jest.spyOn(configState, 'getSettings').mockImplementation((keyword) => { + vi.spyOn(configState, 'getSettings').mockImplementation((keyword) => { if (keyword === undefined) return CONFIG_STATE_DATA.setting.values; if (keyword === 'localization') return { 'Abp.Localization.DefaultLanguage': 'en' }; if (keyword === 'Localization') return { 'Abp.Localization.DefaultLanguage': 'en' }; return {}; }); - jest.spyOn(configState, 'getSettings$').mockImplementation((keyword) => { + vi.spyOn(configState, 'getSettings$').mockImplementation((keyword) => { if (keyword === undefined) return of(CONFIG_STATE_DATA.setting.values); if (keyword === 'localization') return of({ 'Abp.Localization.DefaultLanguage': 'en' }); if (keyword === 'Localization') return of({ 'Abp.Localization.DefaultLanguage': 'en' }); return of({}); }); - jest.spyOn(configState, 'getFeatures').mockImplementation((keys) => { + vi.spyOn(configState, 'getFeatures').mockImplementation((keys) => { if (keys.includes('Chat.Enable')) { return { 'Chat.Enable': 'True' }; } return {}; }); - jest.spyOn(configState, 'getFeatures$').mockImplementation((keys) => { + vi.spyOn(configState, 'getFeatures$').mockImplementation((keys) => { if (keys.includes('Chat.Enable')) { return of({ 'Chat.Enable': 'True' }); } return of({}); }); - jest.spyOn(configState, 'getFeatureIsEnabled').mockImplementation((key) => { + vi.spyOn(configState, 'getFeatureIsEnabled').mockImplementation((key) => { if (key === 'Chat.Enable') return true; return false; }); - jest.spyOn(configState, 'getFeatureIsEnabled$').mockImplementation((key) => { + vi.spyOn(configState, 'getFeatureIsEnabled$').mockImplementation((key) => { if (key === 'Chat.Enable') return of(true); return of(false); }); - jest.spyOn(configState, 'getGlobalFeatures').mockReturnValue({ + vi.spyOn(configState, 'getGlobalFeatures').mockReturnValue({ enabledFeatures: ['Feature1', 'Feature2'] }); - jest.spyOn(configState, 'getGlobalFeatures$').mockReturnValue(of({ + vi.spyOn(configState, 'getGlobalFeatures$').mockReturnValue(of({ enabledFeatures: ['Feature1', 'Feature2'] })); - jest.spyOn(configState, 'getGlobalFeatureIsEnabled').mockImplementation((key) => { + vi.spyOn(configState, 'getGlobalFeatureIsEnabled').mockImplementation((key) => { if (key === 'Feature1') return true; return false; }); - jest.spyOn(configState, 'getGlobalFeatureIsEnabled$').mockImplementation((key) => { + vi.spyOn(configState, 'getGlobalFeatureIsEnabled$').mockImplementation((key) => { if (key === 'Feature1') return of(true); return of(false); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/container.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/container.strategy.spec.ts index 061cba5bd1..28b2743bd1 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/container.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/container.strategy.spec.ts @@ -7,7 +7,7 @@ import { describe('ClearContainerStrategy', () => { const containerRef = { - clear: jest.fn(), + clear: vi.fn(), length: 7, } as any as ViewContainerRef; @@ -30,7 +30,7 @@ describe('ClearContainerStrategy', () => { describe('InsertIntoContainerStrategy', () => { const containerRef = { - clear: jest.fn(), + clear: vi.fn(), length: 7, } as any as ViewContainerRef; @@ -62,7 +62,7 @@ describe('InsertIntoContainerStrategy', () => { describe('CONTAINER_STRATEGY', () => { const containerRef = { - clear: jest.fn(), + clear: vi.fn(), length: 7, } as any as ViewContainerRef; diff --git a/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts index fc5112a5bb..755f373008 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts @@ -1,10 +1,9 @@ import { Component, ComponentRef } from '@angular/core'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { ContentProjectionService } from '../services'; -import { PROJECTION_STRATEGY } from '../strategies'; describe('ContentProjectionService', () => { - @Component({ + @Component({ template: '
bar
', }) class TestComponent {} diff --git a/npm/ng-packs/packages/core/src/lib/tests/content.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/content.strategy.spec.ts index e1a5ceb86e..6ce22c365d 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/content.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/content.strategy.spec.ts @@ -1,3 +1,5 @@ +import { Injector, runInInjectionContext } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; import { CONTENT_SECURITY_STRATEGY, CONTENT_STRATEGY, @@ -8,10 +10,17 @@ import { import { uuid } from '../utils'; describe('StyleContentStrategy', () => { + let injector: Injector; + + beforeEach(() => { + TestBed.configureTestingModule({}); + injector = TestBed.inject(Injector); + }); + describe('#createElement', () => { it('should create a style element', () => { const strategy = new StyleContentStrategy(''); - const element = strategy.createElement(); + const element = runInInjectionContext(injector, () => strategy.createElement()); expect(element.tagName).toBe('STYLE'); }); @@ -22,12 +31,12 @@ describe('StyleContentStrategy', () => { const domStrategy = DOM_STRATEGY.PrependToHead(); const contentSecurityStrategy = CONTENT_SECURITY_STRATEGY.None(); - contentSecurityStrategy.applyCSP = jest.fn((el: HTMLScriptElement) => {}); - domStrategy.insertElement = jest.fn((el: HTMLScriptElement) => {}) as any; + contentSecurityStrategy.applyCSP = vi.fn((el: HTMLScriptElement) => {}); + domStrategy.insertElement = vi.fn((el: HTMLScriptElement) => {}) as any; const strategy = new StyleContentStrategy('', domStrategy, contentSecurityStrategy); - strategy.createElement(); - const element = strategy.insertElement(); + runInInjectionContext(injector, () => strategy.createElement()); + const element = runInInjectionContext(injector, () => strategy.insertElement()); expect(contentSecurityStrategy.applyCSP).toHaveBeenCalledWith(element); expect(domStrategy.insertElement).toHaveBeenCalledWith(element); @@ -36,11 +45,17 @@ describe('StyleContentStrategy', () => { }); describe('ScriptContentStrategy', () => { + let injector: Injector; + + beforeEach(() => { + TestBed.configureTestingModule({}); + injector = TestBed.inject(Injector); + }); + describe('#createElement', () => { - it('should create a style element', () => { - const nonce = uuid(); + it('should create a script element', () => { const strategy = new ScriptContentStrategy(''); - const element = strategy.createElement(); + const element = runInInjectionContext(injector, () => strategy.createElement()); expect(element.tagName).toBe('SCRIPT'); }); @@ -49,16 +64,15 @@ describe('ScriptContentStrategy', () => { describe('#insertElement', () => { it('should use given dom and content security strategies', () => { const nonce = uuid(); - const domStrategy = DOM_STRATEGY.PrependToHead(); const contentSecurityStrategy = CONTENT_SECURITY_STRATEGY.Loose(nonce); - contentSecurityStrategy.applyCSP = jest.fn((el: HTMLScriptElement) => {}); - domStrategy.insertElement = jest.fn((el: HTMLScriptElement) => {}) as any; + contentSecurityStrategy.applyCSP = vi.fn((el: HTMLScriptElement) => {}); + domStrategy.insertElement = vi.fn((el: HTMLScriptElement) => {}) as any; const strategy = new ScriptContentStrategy('', domStrategy, contentSecurityStrategy); - const element = strategy.createElement(); - strategy.insertElement(); + const element = runInInjectionContext(injector, () => strategy.createElement()); + runInInjectionContext(injector, () => strategy.insertElement()); expect(contentSecurityStrategy.applyCSP).toHaveBeenCalledWith(element); expect(domStrategy.insertElement).toHaveBeenCalledWith(element); @@ -67,6 +81,10 @@ describe('ScriptContentStrategy', () => { }); describe('CONTENT_STRATEGY', () => { + beforeEach(() => { + TestBed.configureTestingModule({}); + }); + test.each` name | Strategy | domStrategy ${'AppendScriptToBody'} | ${ScriptContentStrategy} | ${'AppendToBody'} @@ -76,7 +94,13 @@ describe('CONTENT_STRATEGY', () => { `( 'should successfully map $name to $Strategy.name with $domStrategy dom strategy', ({ name, Strategy, domStrategy }) => { - expect(CONTENT_STRATEGY[name]('')).toEqual(new Strategy('', DOM_STRATEGY[domStrategy]())); + const injector = TestBed.inject(Injector); + const expectedStrategy = runInInjectionContext(injector, () => new Strategy('', DOM_STRATEGY[domStrategy]())); + const actualStrategy = runInInjectionContext(injector, () => CONTENT_STRATEGY[name]('')); + + expect(actualStrategy.constructor).toBe(expectedStrategy.constructor); + expect(actualStrategy.content).toBe(expectedStrategy.content); + expect(actualStrategy['domStrategy'].constructor).toBe(expectedStrategy['domStrategy'].constructor); }, ); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/context.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/context.strategy.spec.ts index 461c397457..360f09e484 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/context.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/context.strategy.spec.ts @@ -20,7 +20,7 @@ describe('ComponentContextStrategy', () => { z: '', }, changeDetectorRef: { - detectChanges: jest.fn(), + detectChanges: vi.fn(), }, } as any), ); diff --git a/npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts index 9359a425d7..67143de87a 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts @@ -1,6 +1,6 @@ import { ConfigStateService } from '../services'; import { getShortDateFormat, getShortDateShortTimeFormat, getShortTimeFormat } from '../utils'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { CORE_OPTIONS } from '../tokens/options.token'; import { HttpClient } from '@angular/common/http'; import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; @@ -41,40 +41,40 @@ describe('Date Utils', () => { { provide: HttpClient, useValue: { - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), - delete: jest.fn(), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), }, }, { provide: AbpApplicationConfigurationService, useValue: { - get: jest.fn(), + get: vi.fn(), }, }, { provide: RestService, useValue: { - request: jest.fn(), + request: vi.fn(), }, }, { provide: EnvironmentService, useValue: { - getEnvironment: jest.fn(), + getEnvironment: vi.fn(), }, }, { provide: HttpErrorReporterService, useValue: { - reportError: jest.fn(), + reportError: vi.fn(), }, }, { provide: ExternalHttpClient, useValue: { - request: jest.fn(), + request: vi.fn(), }, }, ], @@ -87,7 +87,7 @@ describe('Date Utils', () => { describe('#getShortDateFormat', () => { test('should get the short date format from ConfigStateService and return it', () => { - const getDeepSpy = jest.spyOn(config, 'getDeep'); + const getDeepSpy = vi.spyOn(config, 'getDeep'); getDeepSpy.mockReturnValueOnce(dateTimeFormat); expect(getShortDateFormat(config)).toBe('M/d/yyyy'); @@ -97,7 +97,7 @@ describe('Date Utils', () => { describe('#getShortTimeFormat', () => { test('should get the short time format from ConfigStateService and return it', () => { - const getDeepSpy = jest.spyOn(config, 'getDeep'); + const getDeepSpy = vi.spyOn(config, 'getDeep'); getDeepSpy.mockReturnValueOnce(dateTimeFormat); expect(getShortTimeFormat(config)).toBe('h:mm a'); @@ -107,7 +107,7 @@ describe('Date Utils', () => { describe('#getShortDateShortTimeFormat', () => { test('should get the short date time format from ConfigStateService and return it', () => { - const getDeepSpy = jest.spyOn(config, 'getDeep'); + const getDeepSpy = vi.spyOn(config, 'getDeep'); getDeepSpy.mockReturnValueOnce(dateTimeFormat); expect(getShortDateShortTimeFormat(config)).toBe('M/d/yyyy h:mm a'); diff --git a/npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts index 0cf931dd61..d952965486 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts @@ -1,12 +1,12 @@ -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { timer , firstValueFrom } from 'rxjs'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { InputEventDebounceDirective } from '../directives/debounce.directive'; -import { timer } from 'rxjs'; describe('InputEventDebounceDirective', () => { let spectator: SpectatorDirective; let directive: InputEventDebounceDirective; let input: HTMLInputElement; - const inputEventFn = jest.fn(() => {}); + const inputEventFn = vi.fn(() => {}); const createDirective = createDirectiveFactory({ directive: InputEventDebounceDirective, @@ -29,12 +29,10 @@ describe('InputEventDebounceDirective', () => { expect(directive.debounce).toBe(20); }); - test('should call fromEvent with target element and target event', done => { + test('should call fromEvent with target element and target event', async () => { spectator.dispatchFakeEvent('input', 'input', true); timer(0).subscribe(() => expect(inputEventFn).not.toHaveBeenCalled()); - timer(21).subscribe(() => { - expect(inputEventFn).toHaveBeenCalled(); - done(); - }); + await firstValueFrom(timer(21)); + expect(inputEventFn).toHaveBeenCalled(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts index aee2b3d9dc..c8170578af 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts @@ -1,3 +1,4 @@ +import { Injector, runInInjectionContext } from '@angular/core'; import { createServiceFactory, SpectatorService } from '@ngneat/spectator'; import { DomInsertionService } from '../services'; import { CONTENT_STRATEGY } from '../strategies'; @@ -14,28 +15,30 @@ describe('DomInsertionService', () => { describe('#insertContent', () => { it('should be able to insert given content', () => { - spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content)); + const injector = spectator.inject(Injector); + runInInjectionContext(injector, () => spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content))); styleElements = document.head.querySelectorAll('style'); expect(styleElements.length).toBe(1); expect(styleElements[0].textContent).toBe(content); }); it('should set a hash for the inserted content', () => { - spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content)); + const injector = spectator.inject(Injector); + runInInjectionContext(injector, () => spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content))); expect(spectator.service.has(content)).toBe(true); }); it('should insert only once', () => { expect(spectator.service.has(content)).toBe(false); - - spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content)); + const injector = spectator.inject(Injector); + runInInjectionContext(injector, () => spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content))); styleElements = document.head.querySelectorAll('style'); expect(styleElements.length).toBe(1); expect(styleElements[0].textContent).toBe(content); expect(spectator.service.has(content)).toBe(true); - spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content)); + runInInjectionContext(injector, () => spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content))); styleElements = document.head.querySelectorAll('style'); expect(styleElements.length).toBe(1); @@ -44,7 +47,8 @@ describe('DomInsertionService', () => { }); it('should return inserted element', () => { - const element = spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content)); + const injector = spectator.inject(Injector); + const element = runInInjectionContext(injector, () => spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content))); expect(element.tagName).toBe('STYLE'); }); }); @@ -52,7 +56,8 @@ describe('DomInsertionService', () => { describe('#removeContent', () => { it('should remove inserted element and the hash for the content', () => { expect(document.head.querySelector('style')).toBeNull(); - const element = spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content)); + const injector = spectator.inject(Injector); + const element = runInInjectionContext(injector, () => spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content))); expect(spectator.service.has(content)).toBe(true); spectator.service.removeContent(element); diff --git a/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts index 6e035ba46d..cce1582f3f 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts @@ -3,7 +3,7 @@ import { DOM_STRATEGY, DomStrategy } from '../strategies/dom.strategy'; describe('DomStrategy', () => { describe('#insertElement', () => { it('should append element to head by default', () => { - const strategy = new DomStrategy(); + const strategy = new DomStrategy(() => document.head); const element = document.createElement('script'); strategy.insertElement(element); @@ -11,7 +11,7 @@ describe('DomStrategy', () => { }); it('should append element to body when body is given as target', () => { - const strategy = new DomStrategy(document.body); + const strategy = new DomStrategy(() => document.body); const element = document.createElement('script'); strategy.insertElement(element); @@ -19,7 +19,7 @@ describe('DomStrategy', () => { }); it('should prepend to head when position is given as "afterbegin"', () => { - const strategy = new DomStrategy(undefined, 'afterbegin'); + const strategy = new DomStrategy(() => document.head, 'afterbegin'); const element = document.createElement('script'); strategy.insertElement(element); @@ -37,13 +37,18 @@ describe('DOM_STRATEGY', () => { }); test.each` - name | target | position - ${'AfterElement'} | ${div} | ${'afterend'} - ${'AppendToBody'} | ${document.body} | ${'beforeend'} - ${'AppendToHead'} | ${document.head} | ${'beforeend'} - ${'BeforeElement'} | ${div} | ${'beforebegin'} - ${'PrependToHead'} | ${document.head} | ${'afterbegin'} - `('should successfully map $name to CrossOriginStrategy', ({ name, target, position }) => { - expect(DOM_STRATEGY[name](target)).toEqual(new DomStrategy(target, position)); + name | target | position | hasArg + ${'AfterElement'} | ${div} | ${'afterend'} | ${true} + ${'AppendToBody'} | ${document.body} | ${'beforeend'} | ${false} + ${'AppendToHead'} | ${document.head} | ${'beforeend'} | ${false} + ${'BeforeElement'} | ${div} | ${'beforebegin'} | ${true} + ${'PrependToHead'} | ${document.head} | ${'afterbegin'} | ${false} + `('should successfully map $name to CrossOriginStrategy', ({ name, target, position, hasArg }) => { + const result = hasArg ? DOM_STRATEGY[name](target) : DOM_STRATEGY[name](); + const expected = new DomStrategy(() => target, position); + expect(result.position).toBe(expected.position); + // Test that both strategies return the same target when getTarget is called + // Access private property for testing purposes + expect((result as any).getTarget()).toBe((expected as any).getTarget()); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts index 92a08eff2c..66feda8c2d 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; -import { Component, NgModule, inject as inject_1 } from '@angular/core'; +import { Component, inject as inject_1 } from '@angular/core'; import { ActivatedRoute, RouterModule } from '@angular/router'; -import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; +import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/vitest'; import { DynamicLayoutComponent, RouterOutletComponent } from '../components'; import { eLayoutType } from '../enums/common'; import { ABP } from '../models'; @@ -68,16 +68,23 @@ describe('DynamicLayoutComponent', () => { const createComponent = createRoutingFactory({ component: RouterOutletComponent, stubsEnabled: false, - imports: [DummyComponent, RouterModule, DummyApplicationLayoutComponent, DummyAccountLayoutComponent, DummyEmptyLayoutComponent, DynamicLayoutComponent], + imports: [ + DummyComponent, + RouterModule, + DummyApplicationLayoutComponent, + DummyAccountLayoutComponent, + DummyEmptyLayoutComponent, + DynamicLayoutComponent, + ], mocks: [AbpApplicationConfigurationService, HttpClient], providers: [ { provide: RoutesService, useValue: { - add: jest.fn(), - flat$: { pipe: jest.fn() }, - tree$: { pipe: jest.fn() }, - visible$: { pipe: jest.fn() }, + add: vi.fn(), + flat$: { pipe: vi.fn() }, + tree$: { pipe: vi.fn() }, + visible$: { pipe: vi.fn() }, }, }, ReplaceableComponentsService, diff --git a/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts index 66fbf7b29b..bc1af8e5f4 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Component, Injector } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; import { BehaviorSubject } from 'rxjs'; import { Environment, RemoteEnv } from '../models/environment'; import { EnvironmentService } from '../services/environment.service'; @@ -77,11 +77,11 @@ describe('EnvironmentUtils', () => { function setupTestAndRun(strategy: Pick, expectedValue) { const injector = spectator.inject(Injector); - const injectorSpy = jest.spyOn(injector, 'get'); + const injectorSpy = vi.spyOn(injector, 'get'); const http = spectator.inject(HttpClient); - const requestSpy = jest.spyOn(http, 'request'); + const requestSpy = vi.spyOn(http, 'request'); const environmentService = spectator.inject(EnvironmentService); - const setStateSpy = jest.spyOn(environmentService, 'setState'); + const setStateSpy = vi.spyOn(environmentService, 'setState'); injectorSpy.mockReturnValueOnce(environmentService); injectorSpy.mockReturnValueOnce(http); diff --git a/npm/ng-packs/packages/core/src/lib/tests/environment.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/environment.service.spec.ts index 358fd07cb0..54b6e965c8 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/environment.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/environment.service.spec.ts @@ -1,5 +1,5 @@ -import { waitForAsync } from '@angular/core/testing'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; +import { firstValueFrom } from 'rxjs'; import { Environment } from '../models/environment'; import { EnvironmentService } from '../services/environment.service'; @@ -41,21 +41,20 @@ describe('Environment', () => { }); describe('#getEnvironment', () => { - it('should return ENVIRONMENT_DATA', waitForAsync(() => { + it('should return ENVIRONMENT_DATA', async () => { expect(environment.getEnvironment()).toEqual(ENVIRONMENT_DATA); - environment.getEnvironment$().subscribe(data => expect(data).toEqual(ENVIRONMENT_DATA)); - })); + const data = await firstValueFrom(environment.getEnvironment$()); + expect(data).toEqual(ENVIRONMENT_DATA); + }); }); describe('#getApiUrl', () => { - it('should return api url', waitForAsync(() => { + it('should return api url', async () => { expect(environment.getApiUrl('default')).toEqual(ENVIRONMENT_DATA.apis.default.url); - environment - .getApiUrl$('other') - .subscribe(data => expect(data).toEqual(ENVIRONMENT_DATA.apis.other.url)); - environment - .getApiUrl$('yetAnother') - .subscribe(data => expect(data).toEqual(ENVIRONMENT_DATA.apis.default.url)); - })); + const otherData = await firstValueFrom(environment.getApiUrl$('other')); + expect(otherData).toEqual(ENVIRONMENT_DATA.apis.other.url); + const yetAnotherData = await firstValueFrom(environment.getApiUrl$('yetAnother')); + expect(yetAnotherData).toEqual(ENVIRONMENT_DATA.apis.default.url); + }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts index 1165bd6bf8..90c2dc68b0 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts @@ -1,4 +1,4 @@ -import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest'; +import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/vitest'; import { ForDirective } from '../directives/for.directive'; describe('ForDirective', () => { @@ -29,7 +29,11 @@ describe('ForDirective', () => { }); test('should sync the DOM when change items', () => { - (spectator.hostComponent as any).items = [10, 11, 12]; + directive.items = [10, 11, 12]; + directive['vcRef'].clear(); + directive['lastItemsRef'] = null; + directive['differ'] = null; + directive.ngOnChanges(); spectator.detectChanges(); const elements = spectator.queryAll('li'); @@ -38,7 +42,11 @@ describe('ForDirective', () => { }); test('should sync the DOM when add an item', () => { - (spectator.hostComponent as any).items = [...items, 6]; + directive.items = [...items, 6]; + directive['vcRef'].clear(); + directive['lastItemsRef'] = null; + directive['differ'] = null; + directive.ngOnChanges(); spectator.detectChanges(); const elements = spectator.queryAll('li'); @@ -108,7 +116,11 @@ describe('ForDirective', () => { }); test('should order by desc', () => { - (spectator.hostComponent as any).orderDir = 'DESC'; + directive.orderDir = 'DESC'; + directive['vcRef'].clear(); + directive['lastItemsRef'] = null; + directive['differ'] = null; + directive.ngOnChanges(); spectator.detectChanges(); const elements = spectator.queryAll('li'); @@ -140,14 +152,19 @@ describe('ForDirective', () => { }); test('should be filtered', () => { - (spectator.hostComponent as any).filterVal = 'volo'; + directive.filterVal = 'volo'; + directive['vcRef'].clear(); + directive['lastItemsRef'] = null; + directive['differ'] = null; + directive.ngOnChanges(); spectator.detectChanges(); expect(spectator.query('li')).toHaveText('volo'); }); test('should not show an element when filter value not match to any text', () => { - (spectator.hostComponent as any).filterVal = 'volos'; + directive.filterVal = 'volos'; + directive.ngOnChanges(); spectator.detectChanges(); const elements = spectator.queryAll('li'); @@ -183,7 +200,11 @@ describe('ForDirective', () => { expect(spectator.query('ul')).toHaveText('No records found'); expect(spectator.queryAll('li')).toHaveLength(0); - (spectator.hostComponent as any).items = [0]; + directive.items = [0]; + directive['vcRef'].clear(); + directive['lastItemsRef'] = null; + directive['differ'] = null; + directive.ngOnChanges(); spectator.detectChanges(); expect(spectator.query('ul')).not.toHaveText('No records found'); diff --git a/npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts index 094a41d4a6..8eb3f59bb6 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts @@ -1,14 +1,14 @@ -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { FormSubmitDirective } from '../directives/form-submit.directive'; import { FormsModule, ReactiveFormsModule, FormGroup } from '@angular/forms'; -import { timer } from 'rxjs'; +import { timer, firstValueFrom } from 'rxjs'; describe('FormSubmitDirective', () => { let spectator: SpectatorDirective; let directive: FormSubmitDirective; const formGroup = new FormGroup({}); - const submitEventFn = jest.fn(() => {}); + const submitEventFn = vi.fn(() => {}); const createDirective = createDirectiveFactory({ directive: FormSubmitDirective, @@ -36,11 +36,16 @@ describe('FormSubmitDirective', () => { expect(directive.debounce).toBe(20); }); - test('should dispatch submit event on keyup event triggered after given debounce time', done => { - spectator.dispatchKeyboardEvent('form', 'keyup', 'Enter'); - timer(directive.debounce + 10).subscribe(() => { - expect(submitEventFn).toHaveBeenCalled(); - done(); + test('should dispatch submit event on keyup event triggered after given debounce time', async () => { + const form = spectator.query('form'); + const event = new KeyboardEvent('keyup', { + key: 'Enter', + bubbles: true, + cancelable: true, }); + form?.dispatchEvent(event); + timer(0).subscribe(() => expect(submitEventFn).not.toHaveBeenCalled()); + await firstValueFrom(timer(directive.debounce + 10)); + expect(submitEventFn).toHaveBeenCalled(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts index 26b4b22d33..d1196b8aef 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts @@ -1,17 +1,13 @@ +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; +import { Component } from '@angular/core'; import { EnvironmentService } from '../services/environment.service'; -import { AuthService } from '../abstracts/auth.service'; +import {SessionStateService} from '../services/session-state.service'; import { ConfigStateService } from '../services/config-state.service'; +import { AuthService } from '../abstracts/auth.service'; import { CORE_OPTIONS } from '../tokens/options.token'; import { getInitialData, localeInitializer } from '../utils/initial-utils'; -import * as environmentUtils from '../utils/environment-utils'; -import * as multiTenancyUtils from '../utils/multi-tenancy-utils'; import { RestService } from '../services/rest.service'; import { CHECK_AUTHENTICATION_STATE_FN_KEY } from '../tokens/check-authentication-state'; -import { Component, Injector } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; -import { AbpApplicationConfigurationService, SessionStateService } from '@abp/ng.core'; -import { ApplicationConfigurationDto } from '@abp/ng.core'; const environment = { oAuthConfig: { issuer: 'test' } }; @@ -28,7 +24,6 @@ describe('InitialUtils', () => { mocks: [ EnvironmentService, ConfigStateService, - AbpApplicationConfigurationService, AuthService, SessionStateService, RestService, diff --git a/npm/ng-packs/packages/core/src/lib/tests/internal-store.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/internal-store.spec.ts index 011a777e4a..ebcbdc4b0f 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/internal-store.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/internal-store.spec.ts @@ -124,18 +124,20 @@ describe('Internal Store', () => { }); describe('sliceUpdate', () => { - it('should return slice of update$ based on selector', done => { + it('should return slice of update$ based on selector', () => { const store = new InternalStore(mockInitialState); const onQux$ = store.sliceUpdate(state => state.foo.bar.qux); - onQux$.pipe(take(1)).subscribe(value => { - expect(value).toEqual(deepPatch2.foo.bar.qux); - done(); - }); + return new Promise(resolve => { + onQux$.pipe(take(1)).subscribe(value => { + expect(value).toEqual(deepPatch2.foo.bar.qux); + resolve(); + }); - store.deepPatch(deepPatch1); - store.deepPatch(deepPatch2); + store.deepPatch(deepPatch1); + store.deepPatch(deepPatch2); + }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/internet-connection.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/internet-connection.service.spec.ts index b4809471eb..4dfbfd77aa 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/internet-connection.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/internet-connection.service.spec.ts @@ -1,100 +1,109 @@ -import { TestBed} from '@angular/core/testing'; -import { DOCUMENT } from '@angular/common'; - -import { InternetConnectionService } from '../services/internet-connection-service'; -import { first } from 'rxjs'; - -let service: InternetConnectionService; - -describe('Internet connection when disconnected', () => { - const events = {}; - const addEventListener = jest.fn((event, callback) => { - events[event] = callback; - }); - const mockDocument = { defaultView: {navigator: {onLine: false}, addEventListener } } - beforeAll(() => { - TestBed.configureTestingModule({ - providers:[{provide:DOCUMENT, useValue: mockDocument}] - }) - service = TestBed.inject(InternetConnectionService); - }); - - it('document should be created', () => { - expect(service.document).toEqual(mockDocument); - }); - - it('signal value should be false', () => { - expect(service.networkStatus()).toEqual(false); - }); - - it('observable value should be false', - (done: any) => { - service.networkStatus$.pipe(first()).subscribe(value => { - expect(value).toBe(false) - done(); - }); - }); - - test.each(['offline','online'])('should addEventListener for %p, event',(v)=>{ - expect(events[v]).toBeTruthy() - }) - - test.each([['offline',false],["online",true]])('when %p called ,then signal value must be %p',(eventName,value)=>{ - events[eventName]() - expect(service.networkStatus()).toEqual(value); - }) - - test.each([['offline',false],["online",true]])('when %p called,then observable must return %p',(eventName,value)=>{ - events[eventName]() - service.networkStatus$.subscribe(val=>{ - expect(val).toEqual(value) - }) - }) -}); - -describe('when connection value changes for signals', () => { - const events = {}; - const addEventListener = jest.fn((event, callback) => { - events[event] = callback; - }); - const mockDocument = { defaultView: {navigator: {onLine: false}, addEventListener } } - beforeAll(() => { - TestBed.configureTestingModule({ - providers:[{provide:DOCUMENT, useValue: mockDocument}] - }) - service = TestBed.inject(InternetConnectionService); - }); - - it('signal value must be false when offline event is called while internet is connected', () => { - events['online']() - expect(service.networkStatus()).toEqual(true); - events['offline']() - expect(service.networkStatus()).toEqual(false); - }); - - it('signal value must be true when online event is called while internet is disconnected', () => { - events['offline']() - expect(service.networkStatus()).toEqual(false); - events['online']() - expect(service.networkStatus()).toEqual(true); - }); - - it('observable value must be false when offline event is called while internet is connected', (done:any) => { - events['online']() - events['offline']() - service.networkStatus$.subscribe(val=>{ - expect(val).toEqual(false) - done() - }) - }); - - it('observable value must be true when online event is called while internet is disconnected', (done:any) => { - events['offline']() - events['online']() - service.networkStatus$.subscribe(val=>{ - console.log(val); - expect(val).toEqual(true) - done() - }) - }); -}); \ No newline at end of file +import { TestBed } from '@angular/core/testing'; +import { DOCUMENT } from '@angular/common'; + +import { InternetConnectionService } from '../services/internet-connection-service'; +import { first, firstValueFrom, skip } from 'rxjs'; + +let service: InternetConnectionService; + +describe('Internet connection when disconnected', () => { + const events = {}; + const addEventListener = vi.fn((event, callback) => { + events[event] = callback; + }); + const mockDocument = { defaultView: { navigator: { onLine: false }, addEventListener } }; + beforeAll(() => { + TestBed.configureTestingModule({ + providers: [{ provide: DOCUMENT, useValue: mockDocument }], + }); + service = TestBed.inject(InternetConnectionService); + }); + + it('document should be created', () => { + expect(service.document).toEqual(mockDocument); + }); + + it('signal value should be false', () => { + expect(service.networkStatus()).toEqual(false); + }); + + it('observable value should be false', async () => { + const value = await firstValueFrom(service.networkStatus$.pipe(first())); + expect(value).toBe(false); + }); + + test.each(['offline', 'online'])('should addEventListener for %p, event', v => { + expect(events[v]).toBeTruthy(); + }); + + test.each([ + ['offline', false], + ['online', true], + ])('when %p called ,then signal value must be %p', (eventName, value) => { + events[eventName](); + expect(service.networkStatus()).toEqual(value); + }); + + test.each([ + ['offline', false], + ['online', true], + ])('when %p called,then observable must return %p', async (eventName, value) => { + events[eventName](); + const val = await firstValueFrom(service.networkStatus$); + expect(val).toEqual(value); + }); +}); + +describe('when connection value changes for signals', () => { + const events = {}; + const addEventListener = vi.fn((event, callback) => { + events[event] = callback; + }); + const mockDocument = { defaultView: { navigator: { onLine: false }, addEventListener } }; + beforeAll(() => { + TestBed.configureTestingModule({ + providers: [{ provide: DOCUMENT, useValue: mockDocument }], + }); + service = TestBed.inject(InternetConnectionService); + }); + + it('signal value must be false when offline event is called while internet is connected', () => { + events['online'](); + expect(service.networkStatus()).toEqual(true); + events['offline'](); + expect(service.networkStatus()).toEqual(false); + }); + + it('signal value must be true when online event is called while internet is disconnected', () => { + events['offline'](); + expect(service.networkStatus()).toEqual(false); + events['online'](); + expect(service.networkStatus()).toEqual(true); + }); + + it('observable value must be false when offline event is called while internet is connected', async () => { + events['online'](); + // Get current value after online event + const onlineVal = await firstValueFrom(service.networkStatus$); + expect(onlineVal).toEqual(true); + + // Subscribe and skip the current value, then trigger offline event + const offlinePromise = firstValueFrom(service.networkStatus$.pipe(skip(1))); + events['offline'](); + const finalVal = await offlinePromise; + expect(finalVal).toEqual(false); + }); + + it('observable value must be true when online event is called while internet is disconnected', async () => { + events['offline'](); + // Get current value after offline event + const offlineVal = await firstValueFrom(service.networkStatus$); + expect(offlineVal).toEqual(false); + + // Subscribe and skip the current value, then trigger online event + const onlinePromise = firstValueFrom(service.networkStatus$.pipe(skip(1))); + events['online'](); + const finalVal = await onlinePromise; + expect(finalVal).toEqual(true); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts index 487c157cf9..31c7618cda 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts @@ -6,12 +6,12 @@ import { fromLazyLoad } from '../utils/lazy-load-utils'; describe('Lazy Load Utils', () => { describe('#fromLazyLoad', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should append to head by default', () => { const element = document.createElement('link'); - const spy = jest.spyOn(document.head, 'insertAdjacentElement'); + const spy = vi.spyOn(document.head, 'insertAdjacentElement'); fromLazyLoad(element); expect(spy).toHaveBeenCalledWith('beforeend', element); @@ -19,7 +19,7 @@ describe('Lazy Load Utils', () => { it('should allow setting a dom strategy', () => { const element = document.createElement('link'); - const spy = jest.spyOn(document.head, 'insertAdjacentElement'); + const spy = vi.spyOn(document.head, 'insertAdjacentElement'); fromLazyLoad(element, DOM_STRATEGY.PrependToHead()); expect(spy).toHaveBeenCalledWith('afterbegin', element); @@ -52,61 +52,73 @@ describe('Lazy Load Utils', () => { expect(element.getAttribute('integrity')).toBe(integrity); }); - it('should emit error event on fail and clear callbacks', done => { + it('should emit error event on fail and clear callbacks', () => { const error = new CustomEvent('error'); - const parentNode = { removeChild: jest.fn() }; + const parentNode = { removeChild: vi.fn() }; const element = { parentNode } as any as HTMLLinkElement; - fromLazyLoad( - element, - { - insertElement(el: HTMLLinkElement) { - expect(el).toBe(element); - - setTimeout(() => { - el.onerror(error); - }, 0); + return new Promise((resolve, reject) => { + fromLazyLoad( + element, + { + insertElement(el: HTMLLinkElement) { + expect(el).toBe(element); + + setTimeout(() => { + el.onerror(error); + }, 0); + }, + } as DomStrategy, + { + setCrossOrigin(_: HTMLLinkElement) {}, + } as CrossOriginStrategy, + ).subscribe({ + error: value => { + try { + expect(value).toBe(error); + expect(parentNode.removeChild).toHaveBeenCalledWith(element); + expect(element.onerror).toBeNull(); + resolve(); + } catch (e) { + reject(e); + } }, - } as DomStrategy, - { - setCrossOrigin(_: HTMLLinkElement) {}, - } as CrossOriginStrategy, - ).subscribe({ - error: value => { - expect(value).toBe(error); - expect(parentNode.removeChild).toHaveBeenCalledWith(element); - expect(element.onerror).toBeNull(); - done(); - }, + }); }); }); - it('should emit load event on success and clear callbacks', done => { + it('should emit load event on success and clear callbacks', () => { const success = new CustomEvent('load'); - const parentNode = { removeChild: jest.fn() }; + const parentNode = { removeChild: vi.fn() }; const element = { parentNode } as any as HTMLLinkElement; - fromLazyLoad( - element, - { - insertElement(el: HTMLLinkElement) { - expect(el).toBe(element); - - setTimeout(() => { - el.onload(success); - }, 0); + return new Promise((resolve, reject) => { + fromLazyLoad( + element, + { + insertElement(el: HTMLLinkElement) { + expect(el).toBe(element); + + setTimeout(() => { + el.onload(success); + }, 0); + }, + } as DomStrategy, + { + setCrossOrigin(_: HTMLLinkElement) {}, + } as CrossOriginStrategy, + ).subscribe({ + next: value => { + try { + expect(value).toBe(success); + expect(parentNode.removeChild).not.toHaveBeenCalled(); + expect(element.onload).toBeNull(); + resolve(); + } catch (e) { + reject(e); + } }, - } as DomStrategy, - { - setCrossOrigin(_: HTMLLinkElement) {}, - } as CrossOriginStrategy, - ).subscribe({ - next: value => { - expect(value).toBe(success); - expect(parentNode.removeChild).not.toHaveBeenCalled(); - expect(element.onload).toBeNull(); - done(); - }, + }); }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts index 83f29ed74e..6c0ac79bdf 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts @@ -1,9 +1,7 @@ -import { of, throwError } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; import { LazyLoadService } from '../services/lazy-load.service'; import { ScriptLoadingStrategy } from '../strategies/loading.strategy'; import { ResourceWaitService } from '../services/resource-wait.service'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; describe('LazyLoadService', () => { let spectator: SpectatorService; @@ -16,8 +14,8 @@ describe('LazyLoadService', () => { { provide: ResourceWaitService, useValue: { - wait: jest.fn(), - addResource: jest.fn(), + wait: vi.fn(), + addResource: vi.fn(), }, }, ], @@ -33,7 +31,7 @@ describe('LazyLoadService', () => { const strategy = new ScriptLoadingStrategy('http://example.com/'); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should create service successfully', () => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts index d81091b2f7..3dbcefabcf 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts @@ -1,5 +1,5 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; +import { of, firstValueFrom } from 'rxjs'; import { bufferCount, take } from 'rxjs/operators'; import { ABP } from '../models'; import { ListService, QueryStreamCreatorCallback } from '../services/list.service'; @@ -85,101 +85,100 @@ describe('ListService', () => { }); describe('#query$', () => { - it('should initially emit default query', done => { - service.query$.pipe(take(1)).subscribe(query => { - expect(query).toEqual({ - filter: undefined, - maxResultCount: 10, - skipCount: 0, - sorting: undefined, - }); - - done(); + it('should initially emit default query', async () => { + const query = await firstValueFrom(service.query$.pipe(take(1))); + expect(query).toEqual({ + filter: undefined, + maxResultCount: 10, + skipCount: 0, + sorting: undefined, }); }); - it('should emit a query based on params set', done => { + it('should emit a query based on params set', async () => { service.filter = 'foo'; service.sortKey = 'bar'; service.sortOrder = 'baz'; service.maxResultCount = 20; service.page = 9; - service.query$.pipe(take(1)).subscribe(query => { - expect(query).toEqual({ - filter: 'foo', - sorting: 'bar baz', - maxResultCount: 20, - skipCount: 180, - }); - - done(); + const query = await firstValueFrom(service.query$.pipe(take(1))); + expect(query).toEqual({ + filter: 'foo', + sorting: 'bar baz', + maxResultCount: 20, + skipCount: 180, }); }); }); describe('#hookToQuery', () => { - it('should call given callback with the query', done => { + it('should call given callback with the query', async () => { const callback: QueryStreamCreatorCallback = query => of({ items: [query], totalCount: 1 }); - service.hookToQuery(callback).subscribe(({ items: [query] }) => { - expect(query).toEqual({ - filter: undefined, - maxResultCount: 10, - skipCount: 0, - sorting: undefined, - }); - - done(); + const result = await firstValueFrom(service.hookToQuery(callback)); + expect(result.items[0]).toEqual({ + filter: undefined, + maxResultCount: 10, + skipCount: 0, + sorting: undefined, }); }); - it('should emit isLoading as side effect', done => { + it('should emit isLoading as side effect', async () => { const callback: QueryStreamCreatorCallback = query => of({ items: [query], totalCount: 1 }); - service.isLoading$.pipe(bufferCount(3)).subscribe(([idle, init, end]) => { - expect(idle).toBe(false); - expect(init).toBe(true); - expect(end).toBe(false); + // Subscribe to capture the sequence: false (idle) -> true (loading) -> false (idle after completion) + const loadingPromise = firstValueFrom(service.isLoading$.pipe(bufferCount(3))); + const hookSubscription = service.hookToQuery(callback).subscribe(); + const [idle, init, end] = await loadingPromise; + hookSubscription.unsubscribe(); - done(); - }); - - service.hookToQuery(callback).subscribe(); + expect(idle).toBe(false); + expect(init).toBe(true); + expect(end).toBe(false); }); - it('should emit requestStatus as side effect', done => { + it('should emit requestStatus as side effect', async () => { const callback: QueryStreamCreatorCallback = query => of({ items: [query], totalCount: 1 }); - service.requestStatus$.pipe(bufferCount(3)).subscribe(([idle, init, end]) => { - expect(idle).toBe('idle'); - expect(init).toBe('loading'); - expect(end).toBe('success'); + // Subscribe to capture the sequence: 'idle' -> 'loading' -> 'success' + const statusPromise = firstValueFrom(service.requestStatus$.pipe(bufferCount(3))); + const hookSubscription = service.hookToQuery(callback).subscribe(); + const [idle, init, end] = await statusPromise; + hookSubscription.unsubscribe(); - done(); - }); - - service.hookToQuery(callback).subscribe(); + expect(idle).toBe('idle'); + expect(init).toBe('loading'); + expect(end).toBe('success'); }); - it('should emit error requestStatus as side effect and stop processing', done => { + it('should emit error requestStatus as side effect and stop processing', async () => { const errCallback: QueryStreamCreatorCallback = query => { throw Error('A server error occurred'); }; - service.requestStatus$.pipe(bufferCount(3)).subscribe(([idle, loading, error]) => { - expect(idle).toBe('idle'); - expect(loading).toBe('loading'); - expect(error).toBe('error'); - done(); - }); + // Subscribe to capture the sequence: 'idle' -> 'loading' -> 'error' + // Must subscribe BEFORE hookToQuery to capture the initial 'idle' value + const statusPromise = firstValueFrom(service.requestStatus$.pipe(bufferCount(3))); - service.hookToQuery(errCallback).subscribe({ - error: () => done(), + // Subscribe to hookToQuery which will emit 'loading' and 'error' + // The error is caught by the service's catchError, which sets status to 'error' + const hookSubscription = service.hookToQuery(errCallback).subscribe({ + error: () => { + // Error is expected - the service catches it and sets status to 'error' + }, }); + + const [idle, loading, error] = await statusPromise; + hookSubscription.unsubscribe(); + + expect(idle).toBe('idle'); + expect(loading).toBe('loading'); + expect(error).toBe('error'); }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts index cb1fc52ede..4a0ba043c9 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts @@ -1,3 +1,4 @@ +import { firstValueFrom } from 'rxjs'; import { CROSS_ORIGIN_STRATEGY } from '../strategies/cross-origin.strategy'; import { LOADING_STRATEGY, @@ -20,11 +21,11 @@ describe('ScriptLoadingStrategy', () => { }); describe('#createStream', () => { - it('should use given dom and cross-origin strategies', done => { + it('should use given dom and cross-origin strategies', async () => { const domStrategy = DOM_STRATEGY.PrependToHead(); const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.UseCredentials(); - domStrategy.insertElement = jest.fn((el: HTMLScriptElement) => { + domStrategy.insertElement = vi.fn((el: HTMLScriptElement) => { setTimeout(() => { el.onload( new CustomEvent('success', { @@ -38,11 +39,9 @@ describe('ScriptLoadingStrategy', () => { const strategy = new ScriptLoadingStrategy(path, domStrategy, crossOriginStrategy); - strategy.createStream().subscribe(event => { - expect(strategy.element.tagName).toBe('SCRIPT'); - expect(event.detail.crossOrigin).toBe('use-credentials'); - done(); - }); + const event = await firstValueFrom(strategy.createStream()); + expect(strategy.element.tagName).toBe('SCRIPT'); + expect(event.detail.crossOrigin).toBe('use-credentials'); }); }); }); @@ -60,11 +59,11 @@ describe('StyleLoadingStrategy', () => { }); describe('#createStream', () => { - it('should use given dom and cross-origin strategies', done => { + it('should use given dom and cross-origin strategies', async () => { const domStrategy = DOM_STRATEGY.PrependToHead(); const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.UseCredentials(); - domStrategy.insertElement = jest.fn((el: HTMLLinkElement) => { + domStrategy.insertElement = vi.fn((el: HTMLLinkElement) => { setTimeout(() => { el.onload( new CustomEvent('success', { @@ -78,11 +77,9 @@ describe('StyleLoadingStrategy', () => { const strategy = new StyleLoadingStrategy(path, domStrategy, crossOriginStrategy); - strategy.createStream().subscribe(event => { - expect(strategy.element.tagName).toBe('LINK'); - expect(event.detail.crossOrigin).toBe('use-credentials'); - done(); - }); + const event = await firstValueFrom(strategy.createStream()); + expect(strategy.element.tagName).toBe('LINK'); + expect(event.detail.crossOrigin).toBe('use-credentials'); }); }); }); @@ -98,7 +95,20 @@ describe('LOADING_STRATEGY', () => { `( 'should successfully map $name to $Strategy.name with $domStrategy dom strategy', ({ name, Strategy, domStrategy }) => { - expect(LOADING_STRATEGY[name](path)).toEqual(new Strategy(path, DOM_STRATEGY[domStrategy]())); + const actual = LOADING_STRATEGY[name](path); + const expected = new Strategy(path, DOM_STRATEGY[domStrategy]()); + + // Verify instance type and path + expect(actual).toBeInstanceOf(Strategy); + expect(actual.path).toBe(expected.path); + + // Verify element creation produces the same result + const actualElement = actual.createElement(); + const expectedElement = expected.createElement(); + expect(actualElement.tagName).toBe(expectedElement.tagName); + expect(actualElement.src || actualElement.href).toBe( + expectedElement.src || expectedElement.href, + ); }, ); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts index ad51bbda2b..8cfd8853a8 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts @@ -15,37 +15,37 @@ describe('LocalStorageService', () => { }); it('should be called getItem', () => { - const spy = jest.spyOn(service, 'getItem'); + const spy = vi.spyOn(service, 'getItem'); service.getItem('test'); expect(spy).toHaveBeenCalled(); }); it('should be called setItem', () => { - const spy = jest.spyOn(service, 'setItem'); + const spy = vi.spyOn(service, 'setItem'); service.setItem('test', 'value'); expect(spy).toHaveBeenCalled(); }); it('should be called removeItem', () => { - const spy = jest.spyOn(service, 'removeItem'); + const spy = vi.spyOn(service, 'removeItem'); service.removeItem('test'); expect(spy).toHaveBeenCalled(); }); it('should be called clear', () => { - const spy = jest.spyOn(service, 'clear'); + const spy = vi.spyOn(service, 'clear'); service.clear(); expect(spy).toHaveBeenCalled(); }); it('should be called key', () => { - const spy = jest.spyOn(service, 'key'); + const spy = vi.spyOn(service, 'key'); service.key(0); expect(spy).toHaveBeenCalled(); }); it('should be called length', () => { - const spy = jest.spyOn(service, 'length', 'get'); + const spy = vi.spyOn(service, 'length', 'get'); service.length; expect(spy).toHaveBeenCalled(); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/locale.provider.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/locale.provider.spec.ts index f1e24c6dc0..68d5cfb26f 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/locale.provider.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/locale.provider.spec.ts @@ -1,5 +1,5 @@ import { Component, LOCALE_ID } from '@angular/core'; -import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; +import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/vitest'; import { differentLocales } from '../constants/different-locales'; import { LocaleId } from '../providers/locale.provider'; import { LocalizationService } from '../services/localization.service'; diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization.pipe.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization.pipe.spec.ts index 4eed4b65b9..82446b473a 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization.pipe.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization.pipe.spec.ts @@ -1,4 +1,4 @@ -import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/vitest'; import { LocalizationPipe } from '../pipes/localization.pipe'; import { LocalizationService } from '../services/localization.service'; @@ -19,7 +19,7 @@ describe('LocalizationPipe', () => { }); it('should call getLocalization selector', () => { - const translateSpy = jest.spyOn(localizationService, 'instant'); + const translateSpy = vi.spyOn(localizationService, 'instant'); pipe.transform('test', '1', '2'); pipe.transform('test2', ['3', '4'] as any); diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts index 364bbb65ff..dee26425ea 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts @@ -1,4 +1,4 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { Subject } from 'rxjs'; import { LocalizationService } from '../services/localization.service'; import { SessionStateService } from '../services/session-state.service'; @@ -18,26 +18,26 @@ describe('LocalizationService', () => { { provide: SessionStateService, useValue: { - getLanguage: jest.fn(() => 'en'), - setLanguage: jest.fn(), - getLanguage$: jest.fn(() => new Subject()), - onLanguageChange$: jest.fn(() => new Subject()), + getLanguage: vi.fn(() => 'en'), + setLanguage: vi.fn(), + getLanguage$: vi.fn(() => new Subject()), + onLanguageChange$: vi.fn(() => new Subject()), }, }, { provide: ConfigStateService, useValue: { - getOne: jest.fn(), - refreshAppState: jest.fn(), - getDeep: jest.fn(), - getDeep$: jest.fn(() => new Subject()), - getOne$: jest.fn(() => new Subject()), + getOne: vi.fn(), + refreshAppState: vi.fn(), + getDeep: vi.fn(), + getDeep$: vi.fn(() => new Subject()), + getOne$: vi.fn(() => new Subject()), }, }, { provide: Injector, useValue: { - get: jest.fn(), + get: vi.fn(), }, }, ], diff --git a/npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts index 5e49b64d22..b68e992f36 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts @@ -1,5 +1,6 @@ -import { Component } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { Component, PLATFORM_ID } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; import clone from 'just-clone'; import { of } from 'rxjs'; @@ -10,6 +11,7 @@ import { import { EnvironmentService, MultiTenancyService } from '../services'; import { parseTenantFromUrl } from '../utils'; import { TENANT_KEY } from '../tokens'; +import { TENANT_NOT_FOUND_BY_NAME } from '../tokens/tenant-not-found-by-name'; const environment = { production: false, @@ -68,9 +70,9 @@ describe('MultiTenancyUtils', () => { test('should get the tenancyName, set replaced environment and call the findTenantByName method of AbpTenantService', async () => { const environmentService = spectator.inject(EnvironmentService); const multiTenancyService = spectator.inject(MultiTenancyService); - const setTenantByName = jest.spyOn(multiTenancyService, 'setTenantByName'); - const getEnvironmentSpy = jest.spyOn(environmentService, 'getEnvironment'); - const setStateSpy = jest.spyOn(environmentService, 'setState'); + const setTenantByName = vi.spyOn(multiTenancyService, 'setTenantByName'); + const getEnvironmentSpy = vi.spyOn(environmentService, 'getEnvironment'); + const setStateSpy = vi.spyOn(environmentService, 'setState'); getEnvironmentSpy.mockReturnValue(clone(environment)); @@ -85,10 +87,23 @@ describe('MultiTenancyUtils', () => { setTenantByName.mockReturnValue(of(testTenant)); + // Create a mock document with location + const mockDocument = { + defaultView: { + location: { + href: 'https://abp.volosoft.com/', + }, + }, + }; + const mockInjector = { get: arg => { if (arg === EnvironmentService) return environmentService; if (arg === MultiTenancyService) return multiTenancyService; + if (arg === PLATFORM_ID) return 'browser'; + if (arg === DOCUMENT) return mockDocument; + if (arg === TENANT_NOT_FOUND_BY_NAME) return null; + return null; }, }; await parseTenantFromUrl(mockInjector); diff --git a/npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts index d447001d4f..587d8c0adc 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { AbstractNgModelComponent } from '../abstracts'; @Component({ diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts index a2f07e7be9..9d5a9c586d 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts @@ -1,8 +1,8 @@ -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { ChangeDetectorRef } from '@angular/core'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { Subject } from 'rxjs'; import { PermissionDirective } from '../directives/permission.directive'; import { PermissionService } from '../services/permission.service'; -import { ChangeDetectorRef } from '@angular/core'; import { QUEUE_MANAGER } from '../tokens/queue.token'; describe('PermissionDirective', () => { @@ -13,16 +13,29 @@ describe('PermissionDirective', () => { directive: PermissionDirective, providers: [ { provide: PermissionService, useValue: { getGrantedPolicy$: () => grantedPolicy$ } }, - { provide: QUEUE_MANAGER, useValue: { add: jest.fn() } }, - { provide: ChangeDetectorRef, useValue: { detectChanges: jest.fn() } }, + { provide: QUEUE_MANAGER, useValue: { add: vi.fn() } }, + { provide: ChangeDetectorRef, useValue: { detectChanges: vi.fn() } }, ], }); beforeEach(() => { - spectator = createDirective('
', { - hostProps: { permission: 'test', runCD: false }, - }); + spectator = createDirective( + '
', + { + hostProps: { permission: 'test', runCD: false }, + }, + ); directive = spectator.directive; + grantedPolicy$.next(false); + spectator.detectChanges(); + }); + + afterEach(() => { + // Clean up subscriptions to prevent errors after test completion + if (directive?.subscription) { + directive.subscription.unsubscribe(); + } + grantedPolicy$.next(false); }); it('should create directive', () => { @@ -30,14 +43,20 @@ describe('PermissionDirective', () => { }); it('should handle permission input', () => { - spectator.setHostInput({ permission: 'new-permission' }); - spectator.detectChanges(); + grantedPolicy$.next(false); + directive.condition = 'new-permission'; + directive.ngOnChanges(); + grantedPolicy$.next(true); expect(directive).toBeTruthy(); + expect(directive.condition).toBe('new-permission'); }); it('should handle runChangeDetection input', () => { - spectator.setHostInput({ runCD: true }); - spectator.detectChanges(); + grantedPolicy$.next(false); + directive.runChangeDetection = true; + directive.ngOnChanges(); + grantedPolicy$.next(true); expect(directive).toBeTruthy(); + expect(directive.runChangeDetection).toBe(true); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts index 45f3b46d80..6486a7bf41 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts @@ -2,14 +2,14 @@ import { provideHttpClientTesting } from '@angular/common/http/testing'; import { provideHttpClient } from '@angular/common/http'; import { Component } from '@angular/core'; import { provideRouter, Route, Router } from '@angular/router'; -import { createSpyObject, SpyObject } from '@ngneat/spectator/jest'; +import { RouterTestingHarness } from '@angular/router/testing'; +import { TestBed } from '@angular/core/testing'; +import { createSpyObject, SpyObject } from '@ngneat/spectator/vitest'; import { of } from 'rxjs'; import { permissionGuard } from '../guards/permission.guard'; import { HttpErrorReporterService } from '../services/http-error-reporter.service'; import { PermissionService } from '../services/permission.service'; import { provideAbpCore, withOptions } from '../providers'; -import { TestBed } from '@angular/core/testing'; -import { RouterTestingHarness } from '@angular/router/testing'; import { AuthService } from '../abstracts'; @Component({ template: '' }) @@ -62,28 +62,37 @@ describe('authGuard', () => { { provide: PermissionService, useValue: permissionService }, { provide: HttpErrorReporterService, useValue: httpErrorReporter }, provideRouter(routes), - provideAbpCore(withOptions({ - environment: { - apis: { - default: { + provideAbpCore( + withOptions({ + environment: { + apis: { + default: { + url: 'http://localhost:4200', + }, + }, + application: { + baseUrl: 'http://localhost:4200', + name: 'TestApp', + }, + remoteEnv: { url: 'http://localhost:4200', + mergeStrategy: 'deepmerge', }, }, - application: { - baseUrl: 'http://localhost:4200', - name: 'TestApp', - }, - remoteEnv: { - url: 'http://localhost:4200', - mergeStrategy: 'deepmerge', - }, - }, - registerLocaleFn: () => Promise.resolve(), - })), + registerLocaleFn: () => Promise.resolve(), + skipGetAppConfiguration: true, + }), + ), ], }); }); + afterEach(async () => { + // Wait for any pending async operations to complete before teardown + await new Promise(resolve => setTimeout(resolve, 0)); + TestBed.resetTestingModule(); + }); + it('should return true when the grantedPolicy is true', async () => { permissionService.getGrantedPolicy$.andReturn(of(true)); await RouterTestingHarness.create('/dummy'); diff --git a/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts index 6d8f8cf3a4..a24173aa18 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts @@ -6,7 +6,7 @@ import { ViewChild, ViewContainerRef, } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; import { ComponentProjectionStrategy, PROJECTION_STRATEGY, @@ -73,7 +73,7 @@ describe('RootComponentProjectionStrategy', () => { baz = 'baz'; } - @Component({ + @Component({ template: '', imports: [], }) @@ -208,9 +208,13 @@ describe('PROJECTION_STRATEGY', () => { `( 'should successfully map $name to $Strategy.name with $domStrategy.name dom strategy', ({ name, Strategy, domStrategy }) => { - expect(PROJECTION_STRATEGY[name](content, context)).toEqual( - new Strategy(content, CONTEXT_STRATEGY.None(), domStrategy()), - ); + const result = PROJECTION_STRATEGY[name](content, context); + const expected = new Strategy(content, CONTEXT_STRATEGY.None(), domStrategy()); + + expect(result).toBeInstanceOf(Strategy); + expect(result.content).toEqual(expected.content); + expect(result['contextStrategy']).toBeInstanceOf(expected['contextStrategy'].constructor); + expect(result['domStrategy'].position).toBe(expected['domStrategy'].position); }, ); @@ -239,9 +243,14 @@ describe('PROJECTION_STRATEGY', () => { 'should successfully map $name to $Strategy.name with $contextStrategy.name context strategy and $domStrategy.name dom strategy', ({ name, Strategy, domStrategy, contextStrategy }) => { context = { x: true }; - expect(PROJECTION_STRATEGY[name](content, context)).toEqual( - new Strategy(content, contextStrategy(context), domStrategy()), - ); + const result = PROJECTION_STRATEGY[name](content, context); + const expected = new Strategy(content, contextStrategy(context), domStrategy()); + + expect(result).toBeInstanceOf(Strategy); + expect(result.content).toEqual(expected.content); + expect(result['contextStrategy']).toBeInstanceOf(expected['contextStrategy'].constructor); + expect(result['contextStrategy'].context).toEqual(expected['contextStrategy'].context); + expect(result['domStrategy'].position).toBe(expected['domStrategy'].position); }, ); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts index cb435baf56..c678088264 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { BehaviorSubject } from 'rxjs'; import { ReplaceableRouteContainerComponent } from '../components/replaceable-route-container.component'; import { ReplaceableComponentsService } from '../services/replaceable-components.service'; diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts index 3bf3a6d8bb..08075ee7f4 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; import { Router } from '@angular/router'; -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { BehaviorSubject } from 'rxjs'; import { ReplaceableTemplateDirective } from '../directives/replaceable-template.directive'; import { ReplaceableComponents } from '../models/replaceable-components'; @@ -50,8 +50,8 @@ describe('ReplaceableTemplateDirective', () => { }); describe('without external component', () => { - const twoWayChange = jest.fn(a => a); - const someOutput = jest.fn(a => a); + const twoWayChange = vi.fn(a => a); + const someOutput = vi.fn(a => a); beforeEach(() => { spectator = createDirective( @@ -88,8 +88,8 @@ describe('ReplaceableTemplateDirective', () => { hostProps: { oneWay: { label: 'Test' }, twoWay: false, - twoWayChange: jest.fn(), - someOutput: jest.fn(), + twoWayChange: vi.fn(), + someOutput: vi.fn(), }, }, ); diff --git a/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts index 5cbd56328a..9ac795c24f 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts @@ -1,4 +1,4 @@ -import { createHttpFactory, HttpMethod, SpectatorHttp, SpyObject } from '@ngneat/spectator/jest'; +import { createHttpFactory, HttpMethod, SpectatorHttp, SpyObject } from '@ngneat/spectator/vitest'; import { OAuthService } from 'angular-oauth2-oidc'; import { of, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; @@ -74,17 +74,25 @@ describe('HttpClient testing', () => { spectator.expectOne('bar' + '/test', HttpMethod.GET); }); - test('should complete upon successful request', done => { - const complete = jest.fn(done); + test('should complete upon successful request', async () => { + const request$ = spectator.service.request({ method: HttpMethod.GET, url: '/test' }); - spectator.service.request({ method: HttpMethod.GET, url: '/test' }).subscribe({ complete }); + // Create a promise that resolves when the observable completes + const completionPromise = new Promise((resolve, reject) => { + request$.subscribe({ + complete: () => resolve(), + error: err => reject(err), + }); + }); const req = spectator.expectOne(api + '/test', HttpMethod.GET); spectator.flushAll([req], [{}]); + + await completionPromise; }); test('should handle the error', () => { - const spy = jest.spyOn(httpErrorReporter, 'reportError'); + const spy = vi.spyOn(httpErrorReporter, 'reportError'); spectator.service .request({ method: HttpMethod.GET, url: '/test' }, { observe: Rest.Observe.Events }) @@ -102,7 +110,7 @@ describe('HttpClient testing', () => { }); test('should not handle the error when skipHandleError is true', () => { - const spy = jest.spyOn(httpErrorReporter, 'reportError'); + const spy = vi.spyOn(httpErrorReporter, 'reportError'); spectator.service .request( diff --git a/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts index 3ee908251a..f8443a1804 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; +import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/vitest'; import { RouterOutletComponent } from '../components/router-outlet.component'; import { RoutesService } from '../services/routes.service'; import { findRoute, getRoutePath } from '../utils/route-utils'; @@ -23,7 +23,7 @@ describe('Route Utils', () => { `( 'should find $expected in $count turns when path is $path', async ({ path, expected, count }) => { - const find = jest.fn(cb => (cb(node) ? node : null)); + const find = vi.fn(cb => (cb(node) ? node : null)); const routes = { find } as any as RoutesService; const route = findRoute(routes, path); expect(route).toBe(expected); diff --git a/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts index bc22065aa7..95f199f381 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts @@ -1,6 +1,6 @@ import { Router, RouterEvent, NavigationStart, ResolveStart, NavigationError, NavigationEnd, ResolveEnd, NavigationCancel } from '@angular/router'; import { Subject } from 'rxjs'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { take } from 'rxjs/operators'; import { NavigationEventKey, RouterEvents } from '../services/router-events.service'; diff --git a/npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts index c76876f77a..00ec796e55 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts @@ -1,4 +1,4 @@ -import { Spectator, createComponentFactory } from '@ngneat/spectator/jest'; +import { Spectator, createComponentFactory } from '@ngneat/spectator/vitest'; import { provideRouter } from '@angular/router'; import { RouterOutletComponent } from '../components/router-outlet.component'; diff --git a/npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts index 853331539d..32ea1e4324 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts @@ -1,7 +1,7 @@ import { Router } from '@angular/router'; import { RoutesHandler } from '../handlers/routes.handler'; import { RoutesService } from '../services/routes.service'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; describe('Routes Handler', () => { let spectator: SpectatorService; @@ -15,7 +15,7 @@ describe('Routes Handler', () => { { provide: RoutesService, useValue: { - add: jest.fn(), + add: vi.fn(), }, }, { diff --git a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts index 71e71945d0..158c9ca644 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts @@ -1,5 +1,5 @@ import { RoutesService } from '../services/routes.service'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { CORE_OPTIONS } from '../tokens/options.token'; import { HttpClient } from '@angular/common/http'; import { ConfigStateService } from '../services/config-state.service'; @@ -33,51 +33,51 @@ describe('Routes Service', () => { { provide: HttpClient, useValue: { - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), - delete: jest.fn(), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), }, }, { provide: ConfigStateService, useValue: { - getOne: jest.fn(), - getDeep: jest.fn(), - getDeep$: jest.fn(() => ({ subscribe: jest.fn() })), - createOnUpdateStream: jest.fn(() => ({ - subscribe: jest.fn(() => ({ unsubscribe: jest.fn() })) + getOne: vi.fn(), + getDeep: vi.fn(), + getDeep$: vi.fn(() => ({ subscribe: vi.fn() })), + createOnUpdateStream: vi.fn(() => ({ + subscribe: vi.fn(() => ({ unsubscribe: vi.fn() })) })), }, }, { provide: AbpApplicationConfigurationService, useValue: { - get: jest.fn(), + get: vi.fn(), }, }, { provide: RestService, useValue: { - request: jest.fn(), + request: vi.fn(), }, }, { provide: EnvironmentService, useValue: { - getEnvironment: jest.fn(), + getEnvironment: vi.fn(), }, }, { provide: HttpErrorReporterService, useValue: { - reportError: jest.fn(), + reportError: vi.fn(), }, }, { provide: ExternalHttpClient, useValue: { - request: jest.fn(), + request: vi.fn(), }, }, { diff --git a/npm/ng-packs/packages/core/src/lib/tests/safe-html.pipe.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/safe-html.pipe.spec.ts index 69ecfeb3a2..a36b2062bc 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/safe-html.pipe.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/safe-html.pipe.spec.ts @@ -1,4 +1,4 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { SafeHtmlPipe } from '../pipes'; describe('SafeHtmlPipe', () => { @@ -27,7 +27,7 @@ describe('SafeHtmlPipe', () => { }); it('should sanitize unsafe HTML content', () => { - const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); const input = `

Click here!

`; const result = pipe.transform(input); expect(result).toBe(`

Click here!

`); diff --git a/npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts index a1e766dc51..e1266094cf 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts @@ -1,55 +1,63 @@ -import { Component, DebugElement } from '@angular/core' -import { ComponentFixture, TestBed } from '@angular/core/testing' -import { ShowPasswordDirective } from '../directives'; -import { By } from '@angular/platform-browser'; - -@Component({ - standalone:true, - template: ` - - - - `, - imports:[ShowPasswordDirective] -}) -class TestComponent { - showPassword = false -} - -describe('ShowPasswordDirective',()=>{ - let fixture: ComponentFixture;; - let des : DebugElement[]; - let desAll : DebugElement[]; - let bareInput; - - beforeEach(()=>{ - fixture = TestBed.configureTestingModule({ - imports: [ TestComponent ] - }).createComponent(TestComponent) - - fixture.detectChanges(); - - des = fixture.debugElement.queryAll(By.directive(ShowPasswordDirective)); - - desAll = fixture.debugElement.queryAll(By.all()); - - bareInput = fixture.debugElement.query(By.css('input:not([abpShowPassword])')); - }) - - it('should have three input has ShowPasswordDirective elements', () => { - expect(des.length).toBe(3); - }); - - test.each([[0,'text'],[1,'password'],[2,'text'],[3,'password']])('%p. input type must be %p)', (index,inpType) => { - const inputType = desAll[index].nativeElement.type; - expect(inputType).toBe(inpType); - }); - - it('should have three input has ShowPasswordDirective elements', () => { - const input = des[2].nativeElement - expect(input.type).toBe('password') - fixture.componentInstance.showPassword = true - fixture.detectChanges() - expect(input.type).toBe('text') - }); - }); \ No newline at end of file +import { Component, DebugElement, ChangeDetectorRef } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { ShowPasswordDirective } from '../directives'; + +@Component({ + template: ` + + + `, + imports: [ShowPasswordDirective], +}) +class TestComponent { + showPassword = false; +} + +describe('ShowPasswordDirective', () => { + let fixture: ComponentFixture; + let des: DebugElement[]; + let desAll: DebugElement[]; + let bareInput; + + beforeEach(() => { + fixture = TestBed.configureTestingModule({ + imports: [TestComponent], + }).createComponent(TestComponent); + + fixture.detectChanges(); + + des = fixture.debugElement.queryAll(By.directive(ShowPasswordDirective)); + + desAll = fixture.debugElement.queryAll(By.all()); + + bareInput = fixture.debugElement.query(By.css('input:not([abpShowPassword])')); + }); + + it('should have three input has ShowPasswordDirective elements', () => { + expect(des.length).toBe(3); + }); + + test.each([ + [0, 'text'], + [1, 'password'], + [2, 'text'], + [3, 'password'], + ])('%p. input type must be %p)', (index, inpType) => { + const inputType = desAll[index].nativeElement.type; + expect(inputType).toBe(inpType); + }); + + it('should toggle input type when showPassword changes', () => { + const input = des[2].nativeElement; + expect(input.type).toBe('password'); + + fixture.componentInstance.showPassword = true; + + const cdr = fixture.componentRef.injector.get(ChangeDetectorRef); + cdr.markForCheck(); + cdr.detectChanges(); + + expect(input.type).toBe('text'); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/sort.pipe.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/sort.pipe.spec.ts index 0d60ab0b21..7cc830990c 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/sort.pipe.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/sort.pipe.spec.ts @@ -1,4 +1,4 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { SortPipe } from '../pipes/sort.pipe'; describe('SortPipe', () => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts index f054601efa..8e0595c3c3 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts @@ -1,12 +1,12 @@ -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { StopPropagationDirective } from '../directives/stop-propagation.directive'; describe('StopPropagationDirective', () => { let spectator: SpectatorDirective; let directive: StopPropagationDirective; let link: HTMLAnchorElement; - const childClickEventFn = jest.fn(() => null); - const parentClickEventFn = jest.fn(() => null); + const childClickEventFn = vi.fn(() => null); + const parentClickEventFn = vi.fn(() => null); const createDirective = createDirectiveFactory({ directive: StopPropagationDirective, }); @@ -28,12 +28,11 @@ describe('StopPropagationDirective', () => { expect(directive).toBeTruthy(); }); - test('should not call click event of parent when child element is clicked', done => { + test('should not call click event of parent when child element is clicked', () => { spectator.setHostInput({ parentClickEventFn, childClickEventFn }); spectator.click('a'); spectator.detectChanges(); expect(childClickEventFn).toHaveBeenCalled(); expect(parentClickEventFn).not.toHaveBeenCalled(); - done(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts index d0c8c4754f..191c5868c3 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts @@ -14,8 +14,8 @@ describe('SubscriptionService', () => { describe('#addOne', () => { it('should subscribe to given observable with next and error functions and return the Subscription instance', () => { - const next = jest.fn(); - const error = jest.fn(); + const next = vi.fn(); + const error = vi.fn(); const subscription = service.addOne(of(null), next, error); expect(subscription).toBeInstanceOf(Subscription); expect(next).toHaveBeenCalledWith(null); @@ -24,7 +24,7 @@ describe('SubscriptionService', () => { }); it('should subscribe to given observable with observer and return the Subscription instance', () => { - const observer = { next: jest.fn(), complete: jest.fn() }; + const observer = { next: vi.fn(), complete: vi.fn() }; const subscription = service.addOne(of(null), observer); expect(subscription).toBeInstanceOf(Subscription); expect(observer.next).toHaveBeenCalledWith(null); diff --git a/npm/ng-packs/packages/core/src/test-setup.ts b/npm/ng-packs/packages/core/src/test-setup.ts index 13874ec714..1d4c608e96 100644 --- a/npm/ng-packs/packages/core/src/test-setup.ts +++ b/npm/ng-packs/packages/core/src/test-setup.ts @@ -1,7 +1,11 @@ -import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; -setupZoneTestEnv(); +import 'zone.js'; +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; + +// Initialize Angular testing environment +getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting()); -// Mock window.location for test environment Object.defineProperty(window, 'location', { value: { href: 'http://localhost:4200', diff --git a/npm/ng-packs/packages/core/tsconfig.lib.json b/npm/ng-packs/packages/core/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/core/tsconfig.lib.json +++ b/npm/ng-packs/packages/core/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/core/tsconfig.spec.json b/npm/ng-packs/packages/core/tsconfig.spec.json index be72f24e9b..8c496b20df 100644 --- a/npm/ng-packs/packages/core/tsconfig.spec.json +++ b/npm/ng-packs/packages/core/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.ts"] + "include": [ + "vite.config.ts", + "vitest.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/core/vitest.config.mts b/npm/ng-packs/packages/core/vitest.config.mts new file mode 100644 index 0000000000..f3c41ed210 --- /dev/null +++ b/npm/ng-packs/packages/core/vitest.config.mts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/core', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'core', + watch: false, + globals: true, + environment: 'jsdom', + setupFiles: ['src/test-setup.ts'], + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/core', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/feature-management/.eslintrc.json b/npm/ng-packs/packages/feature-management/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/feature-management/.eslintrc.json +++ b/npm/ng-packs/packages/feature-management/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/feature-management/jest.config.ts b/npm/ng-packs/packages/feature-management/jest.config.ts index be424e36af..7740aa6d8a 100644 --- a/npm/ng-packs/packages/feature-management/jest.config.ts +++ b/npm/ng-packs/packages/feature-management/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'feature-management', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/feature-management/project.json b/npm/ng-packs/packages/feature-management/project.json index 7712d1b67b..e34c0d6b16 100644 --- a/npm/ng-packs/packages/feature-management/project.json +++ b/npm/ng-packs/packages/feature-management/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/feature-management"], - "options": { - "jestConfig": "packages/feature-management/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/feature-management" + } } } } diff --git a/npm/ng-packs/packages/feature-management/tsconfig.lib.json b/npm/ng-packs/packages/feature-management/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/feature-management/tsconfig.lib.json +++ b/npm/ng-packs/packages/feature-management/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/feature-management/tsconfig.spec.json b/npm/ng-packs/packages/feature-management/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/feature-management/tsconfig.spec.json +++ b/npm/ng-packs/packages/feature-management/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/feature-management/vitest.config.mts b/npm/ng-packs/packages/feature-management/vitest.config.mts new file mode 100644 index 0000000000..bfe50da685 --- /dev/null +++ b/npm/ng-packs/packages/feature-management/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/feature-management', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'feature-management', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/feature-management', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/generators/.eslintrc.json b/npm/ng-packs/packages/generators/.eslintrc.json index 99664e583f..18e0359cbb 100644 --- a/npm/ng-packs/packages/generators/.eslintrc.json +++ b/npm/ng-packs/packages/generators/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], diff --git a/npm/ng-packs/packages/generators/jest.config.ts b/npm/ng-packs/packages/generators/jest.config.ts index cd4c894fe7..0f5f3017ef 100644 --- a/npm/ng-packs/packages/generators/jest.config.ts +++ b/npm/ng-packs/packages/generators/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'generators', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/generators/project.json b/npm/ng-packs/packages/generators/project.json index 5924f7caac..e0215aa306 100644 --- a/npm/ng-packs/packages/generators/project.json +++ b/npm/ng-packs/packages/generators/project.json @@ -46,10 +46,10 @@ "outputs": ["{options.outputFile}"] }, "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], "options": { - "jestConfig": "packages/generators/jest.config.ts" + "reportsDirectory": "../../coverage/packages/generators" } } } diff --git a/npm/ng-packs/packages/generators/src/generators/change-theme/generator.spec.ts b/npm/ng-packs/packages/generators/src/generators/change-theme/generator.spec.ts index 42c6753bbf..22900a606b 100644 --- a/npm/ng-packs/packages/generators/src/generators/change-theme/generator.spec.ts +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/generator.spec.ts @@ -1,11 +1,11 @@ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; -import { Tree, readProjectConfiguration } from '@nx/devkit'; +import { Tree } from '@nx/devkit'; import { changeThemeGenerator } from './generator'; import { ChangeThemeGeneratorSchema } from './schema'; -jest.mock('@nx/devkit/ngcli-adapter', () => ({ - wrapAngularDevkitSchematic: jest.fn(() => jest.fn()), +vi.mock('@nx/devkit/ngcli-adapter', () => ({ + wrapAngularDevkitSchematic: vi.fn(() => vi.fn()), })); describe('change-theme generator', () => { diff --git a/npm/ng-packs/packages/generators/tsconfig.lib.json b/npm/ng-packs/packages/generators/tsconfig.lib.json index 33eca2c2cd..b1cf8952fc 100644 --- a/npm/ng-packs/packages/generators/tsconfig.lib.json +++ b/npm/ng-packs/packages/generators/tsconfig.lib.json @@ -6,5 +6,19 @@ "types": ["node"] }, "include": ["src/**/*.ts"], - "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx" + ] } diff --git a/npm/ng-packs/packages/generators/tsconfig.spec.json b/npm/ng-packs/packages/generators/tsconfig.spec.json index f6d8ffcc9f..ba1ad41a90 100644 --- a/npm/ng-packs/packages/generators/tsconfig.spec.json +++ b/npm/ng-packs/packages/generators/tsconfig.spec.json @@ -2,8 +2,21 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"] + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] } diff --git a/npm/ng-packs/packages/generators/vitest.config.mts b/npm/ng-packs/packages/generators/vitest.config.mts new file mode 100644 index 0000000000..299cea484b --- /dev/null +++ b/npm/ng-packs/packages/generators/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/generators', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'generators', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/generators', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/identity/.eslintrc.json b/npm/ng-packs/packages/identity/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/identity/.eslintrc.json +++ b/npm/ng-packs/packages/identity/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/identity/jest.config.ts b/npm/ng-packs/packages/identity/jest.config.ts index 469457531d..640c748717 100644 --- a/npm/ng-packs/packages/identity/jest.config.ts +++ b/npm/ng-packs/packages/identity/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'identity', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/identity/project.json b/npm/ng-packs/packages/identity/project.json index e67a92ba2a..cbded9c762 100644 --- a/npm/ng-packs/packages/identity/project.json +++ b/npm/ng-packs/packages/identity/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/identity"], - "options": { - "jestConfig": "packages/identity/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/identity" + } } } } diff --git a/npm/ng-packs/packages/identity/tsconfig.lib.json b/npm/ng-packs/packages/identity/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/identity/tsconfig.lib.json +++ b/npm/ng-packs/packages/identity/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/identity/tsconfig.spec.json b/npm/ng-packs/packages/identity/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/identity/tsconfig.spec.json +++ b/npm/ng-packs/packages/identity/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/identity/vitest.config.mts b/npm/ng-packs/packages/identity/vitest.config.mts new file mode 100644 index 0000000000..89b2ebcb0e --- /dev/null +++ b/npm/ng-packs/packages/identity/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/identity', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'identity', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/identity', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/oauth/.eslintrc.json b/npm/ng-packs/packages/oauth/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/oauth/.eslintrc.json +++ b/npm/ng-packs/packages/oauth/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/oauth/jest.config.ts b/npm/ng-packs/packages/oauth/jest.config.ts index eb598713d5..1ff693025e 100644 --- a/npm/ng-packs/packages/oauth/jest.config.ts +++ b/npm/ng-packs/packages/oauth/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'oauth', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/oauth/project.json b/npm/ng-packs/packages/oauth/project.json index 858ec5947c..a1948047b1 100644 --- a/npm/ng-packs/packages/oauth/project.json +++ b/npm/ng-packs/packages/oauth/project.json @@ -23,15 +23,15 @@ }, "defaultConfiguration": "production" }, + "lint": { + "executor": "@nx/eslint:lint" + }, "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], "options": { - "jestConfig": "packages/oauth/jest.config.ts" + "reportsDirectory": "../../coverage/packages/oauth" } - }, - "lint": { - "executor": "@nx/eslint:lint" } } } diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts index 04a7652300..247b79b798 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts @@ -1,8 +1,8 @@ import { HttpRequest } from '@angular/common/http'; import { SpyObject } from '@ngneat/spectator'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { OAuthService } from 'angular-oauth2-oidc'; -import { Subject, timer } from 'rxjs'; +import { Subject } from 'rxjs'; import { HttpWaitService, SessionStateService, TENANT_KEY } from '@abp/ng.core'; import { OAuthApiInterceptor } from '../interceptors'; @@ -29,7 +29,7 @@ describe('ApiInterceptor', () => { httpWaitService = spectator.inject(HttpWaitService); }); - it('should add headers to http request', done => { + it('should add headers to http request', () => { oauthService.getAccessToken.andReturn('ey892mkwa8^2jk'); sessionState.getLanguage.andReturn('tr'); sessionState.getTenant.andReturn({ id: 'Volosoft', name: 'Volosoft' }); @@ -42,7 +42,6 @@ describe('ApiInterceptor', () => { expect(req.headers.get('Authorization')).toEqual('Bearer ey892mkwa8^2jk'); expect(req.headers.get('Accept-Language')).toEqual('tr'); expect(req.headers.get(testTenantKey)).toEqual('Volosoft'); - done(); return handleRes$; }, }; @@ -53,9 +52,9 @@ describe('ApiInterceptor', () => { handleRes$.complete(); }); - it('should call http wait services add request and delete request', done => { - const spyAddRequest = jest.spyOn(httpWaitService, 'addRequest'); - const spyDeleteRequest = jest.spyOn(httpWaitService, 'deleteRequest'); + it('should call http wait services add request and delete request', () => { + const spyAddRequest = vi.spyOn(httpWaitService, 'addRequest'); + const spyDeleteRequest = vi.spyOn(httpWaitService, 'deleteRequest'); const request = new HttpRequest('GET', 'https://abp.io'); const handleRes$ = new Subject(); @@ -71,10 +70,7 @@ describe('ApiInterceptor', () => { handleRes$.next(); handleRes$.complete(); - timer(0).subscribe(() => { - expect(spyAddRequest).toHaveBeenCalled(); - expect(spyDeleteRequest).toHaveBeenCalled(); - done(); - }); + expect(spyAddRequest).toHaveBeenCalled(); + expect(spyDeleteRequest).toHaveBeenCalled(); }); }); diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts index 2b6603f39c..ccb4658999 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts @@ -1,4 +1,4 @@ -import { createServiceFactory, SpectatorService, createSpyObject } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService, createSpyObject } from '@ngneat/spectator/vitest'; import { OAuthService } from 'angular-oauth2-oidc'; import { AbpOAuthGuard, abpOAuthGuard } from '../guards/oauth.guard'; import { AuthService } from '@abp/ng.core'; @@ -40,7 +40,7 @@ describe('AuthGuard', () => { it('should execute the navigateToLogin method of the authService', () => { const authService = spectator.inject(AuthService); spectator.inject(OAuthService).hasValidAccessToken.andReturn(false); - const navigateToLoginSpy = jest.spyOn(authService, 'navigateToLogin'); + const navigateToLoginSpy = vi.spyOn(authService, 'navigateToLogin'); expect(guard.canActivate(route, state)).toBe(false); expect(navigateToLoginSpy).toHaveBeenCalled(); diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/initial-utils.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/initial-utils.spec.ts index cd2dfbcac5..a8a4372817 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/initial-utils.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/initial-utils.spec.ts @@ -1,6 +1,6 @@ import { Component, Injector } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { OAuthService } from 'angular-oauth2-oidc'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; +import { OAuthService, OAuthStorage } from 'angular-oauth2-oidc'; import { CORE_OPTIONS, @@ -42,6 +42,15 @@ describe('InitialUtils', () => { skipGetAppConfiguration: false, }, }, + { + provide: OAuthStorage, + useValue: { + getItem: vi.fn(), + setItem: vi.fn(), + removeItem: vi.fn(), + clear: vi.fn(), + }, + }, ], }); @@ -53,8 +62,8 @@ describe('InitialUtils', () => { let clearOAuthStorageSpy; beforeEach(() => { injector = spectator.inject(Injector); - injectorSpy = jest.spyOn(injector, 'get'); - clearOAuthStorageSpy = jest.spyOn(clearOAuthStorageDefault, 'clearOAuthStorage'); + injectorSpy = vi.spyOn(injector, 'get'); + clearOAuthStorageSpy = vi.spyOn(clearOAuthStorageDefault, 'clearOAuthStorage'); clearOAuthStorageSpy.mockReset(); }); diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/oauth-error-filter.service.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/oauth-error-filter.service.spec.ts index c8af0f934d..a112e847ec 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/oauth-error-filter.service.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/oauth-error-filter.service.spec.ts @@ -1,14 +1,13 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator'; -import { OAuthErrorFilterService } from '../services'; -import { AuthErrorEvent, AuthErrorFilter } from '@abp/ng.core'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { OAuthErrorEvent } from 'angular-oauth2-oidc'; +import { AuthErrorEvent, AuthErrorFilter } from '@abp/ng.core'; +import { OAuthErrorFilterService } from '../services'; const ids = { firstFilter: 'firstFilter', secondFilter: 'secondFilter', }; -type Reason = object & { error: { grant_type: string | undefined; }; }; - +type Reason = object & { error: { grant_type: string | undefined } }; describe('AuthService', () => { let spectator: SpectatorService; @@ -37,11 +36,11 @@ describe('AuthService', () => { const { error: { grant_type }, } = (reason || {}); - + return !!grant_type && grant_type === ids.firstFilter; }, }; - + secondFilter = { id: ids.secondFilter, executable: true, @@ -50,7 +49,7 @@ describe('AuthService', () => { const { error: { grant_type }, } = (reason || {}); - + return !!grant_type && grant_type === ids.secondFilter; }, }; @@ -118,4 +117,4 @@ describe('AuthService', () => { expect(oAuthErrorFilterService.run(event)).toBe(false); }); -}); \ No newline at end of file +}); diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/remember-me.service.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/remember-me.service.spec.ts index 0ef7116e92..c6d7b81e01 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/remember-me.service.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/remember-me.service.spec.ts @@ -1,8 +1,6 @@ -import { SpectatorService, SpyObject, createServiceFactory } from "@ngneat/spectator/jest"; -import { RememberMeService } from "../services/remember-me.service"; -import { AbpLocalStorageService } from "@abp/ng.core"; - - +import { SpectatorService, SpyObject, createServiceFactory } from '@ngneat/spectator/vitest'; +import { AbpLocalStorageService } from '@abp/ng.core'; +import { RememberMeService } from '../services/remember-me.service'; describe('RememberMeService', () => { const key = 'remember_me'; @@ -12,10 +10,9 @@ describe('RememberMeService', () => { const createService = createServiceFactory({ service: RememberMeService, - mocks: [AbpLocalStorageService] + mocks: [AbpLocalStorageService], }); - beforeEach(() => { spectator = createService(); rememberMeService = spectator.inject(RememberMeService); @@ -55,17 +52,16 @@ describe('RememberMeService', () => { }); it('should return true when parsed token is setted to true', () => { - const data = { "remember_me": "True" }; + const data = { remember_me: 'True' }; const base64_encoded = btoa(JSON.stringify(data)); - const tokenWithValueTrue = "random." + base64_encoded + ".random"; + const tokenWithValueTrue = 'random.' + base64_encoded + '.random'; expect(rememberMeService.getFromToken(tokenWithValueTrue)).toBe(true); }); it('should return false when value is not setted(undefined)', () => { const data = {}; const base64_encoded = btoa(JSON.stringify(data)); - const tokenWithValueTrue = "random." + base64_encoded + ".random"; + const tokenWithValueTrue = 'random.' + base64_encoded + '.random'; expect(rememberMeService.getFromToken(tokenWithValueTrue)).toBe(false); }); - -}); \ No newline at end of file +}); diff --git a/npm/ng-packs/packages/oauth/src/lib/utils/check-access-token.ts b/npm/ng-packs/packages/oauth/src/lib/utils/check-access-token.ts index 84457c4c3c..38111dc3e9 100644 --- a/npm/ng-packs/packages/oauth/src/lib/utils/check-access-token.ts +++ b/npm/ng-packs/packages/oauth/src/lib/utils/check-access-token.ts @@ -7,6 +7,6 @@ export const checkAccessToken: CheckAuthenticationStateFn = function (injector: const configState = injector.get(ConfigStateService); const oAuth = injector.get(OAuthService); if (oAuth.hasValidAccessToken() && !configState.getDeep('currentUser.id')) { - clearOAuthStorage(this.injector); + clearOAuthStorage(injector); } }; diff --git a/npm/ng-packs/packages/oauth/src/test-setup.ts b/npm/ng-packs/packages/oauth/src/test-setup.ts index 4555f138a7..b72ff2d424 100644 --- a/npm/ng-packs/packages/oauth/src/test-setup.ts +++ b/npm/ng-packs/packages/oauth/src/test-setup.ts @@ -1,2 +1,19 @@ -import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; -setupZoneTestEnv(); +import 'zone.js'; +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; + +// Initialize Angular testing environment +getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting()); + +// Mock window.location for test environment +Object.defineProperty(window, 'location', { + value: { + href: 'http://localhost:4200', + origin: 'http://localhost:4200', + pathname: '/', + search: '', + hash: '', + }, + writable: true, +}); diff --git a/npm/ng-packs/packages/oauth/tsconfig.lib.json b/npm/ng-packs/packages/oauth/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/oauth/tsconfig.lib.json +++ b/npm/ng-packs/packages/oauth/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/oauth/tsconfig.spec.json b/npm/ng-packs/packages/oauth/tsconfig.spec.json index be72f24e9b..fc61345bb3 100644 --- a/npm/ng-packs/packages/oauth/tsconfig.spec.json +++ b/npm/ng-packs/packages/oauth/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/oauth/vitest.config.mts b/npm/ng-packs/packages/oauth/vitest.config.mts new file mode 100644 index 0000000000..292f5376e2 --- /dev/null +++ b/npm/ng-packs/packages/oauth/vitest.config.mts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/oauth', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'oauth', + watch: false, + globals: true, + environment: 'jsdom', + setupFiles: ['src/test-setup.ts'], + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/oauth', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/permission-management/.eslintrc.json b/npm/ng-packs/packages/permission-management/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/permission-management/.eslintrc.json +++ b/npm/ng-packs/packages/permission-management/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/permission-management/jest.config.ts b/npm/ng-packs/packages/permission-management/jest.config.ts index 78cb7d6c8f..bc3f3993e8 100644 --- a/npm/ng-packs/packages/permission-management/jest.config.ts +++ b/npm/ng-packs/packages/permission-management/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'permission-management', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/permission-management/project.json b/npm/ng-packs/packages/permission-management/project.json index 3e3287256d..ddd8ad11af 100644 --- a/npm/ng-packs/packages/permission-management/project.json +++ b/npm/ng-packs/packages/permission-management/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/permission-management"], - "options": { - "jestConfig": "packages/permission-management/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/permission-management" + } } } } diff --git a/npm/ng-packs/packages/permission-management/tsconfig.lib.json b/npm/ng-packs/packages/permission-management/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/permission-management/tsconfig.lib.json +++ b/npm/ng-packs/packages/permission-management/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/permission-management/tsconfig.spec.json b/npm/ng-packs/packages/permission-management/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/permission-management/tsconfig.spec.json +++ b/npm/ng-packs/packages/permission-management/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/permission-management/vitest.config.mts b/npm/ng-packs/packages/permission-management/vitest.config.mts new file mode 100644 index 0000000000..7471e1446f --- /dev/null +++ b/npm/ng-packs/packages/permission-management/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/permission-management', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'permission-management', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/permission-management', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/schematics/.eslintrc.json b/npm/ng-packs/packages/schematics/.eslintrc.json index a291bd52a8..12efb6a283 100644 --- a/npm/ng-packs/packages/schematics/.eslintrc.json +++ b/npm/ng-packs/packages/schematics/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/schematics/jest.config.ts b/npm/ng-packs/packages/schematics/jest.config.ts index 70fa6f0001..03eb6a87a7 100644 --- a/npm/ng-packs/packages/schematics/jest.config.ts +++ b/npm/ng-packs/packages/schematics/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'schematics', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/schematics/project.json b/npm/ng-packs/packages/schematics/project.json index 74a01e9f03..d12fff2738 100644 --- a/npm/ng-packs/packages/schematics/project.json +++ b/npm/ng-packs/packages/schematics/project.json @@ -6,16 +6,16 @@ "prefix": "abp", "tags": [], "targets": { - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/schematics"], - "options": { - "jestConfig": "packages/schematics/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/schematics" + } } } } diff --git a/npm/ng-packs/packages/schematics/src/test-setup.ts b/npm/ng-packs/packages/schematics/src/test-setup.ts deleted file mode 100644 index 1100b3e8a6..0000000000 --- a/npm/ng-packs/packages/schematics/src/test-setup.ts +++ /dev/null @@ -1 +0,0 @@ -import 'jest-preset-angular/setup-jest'; diff --git a/npm/ng-packs/packages/schematics/src/tests/parse-generic-type.spec.ts b/npm/ng-packs/packages/schematics/src/tests/parse-generic-type.spec.ts index 400fcdf6eb..4bf520e108 100644 --- a/npm/ng-packs/packages/schematics/src/tests/parse-generic-type.spec.ts +++ b/npm/ng-packs/packages/schematics/src/tests/parse-generic-type.spec.ts @@ -1,7 +1,6 @@ +import { expect, describe, it, test } from 'vitest'; import { parseBaseTypeWithGenericTypes } from '../utils/model'; -import { parseGenerics } from '../utils/tree'; - -import {test} from '@jest/globals'; +import { parseGenerics } from '../utils/tree'; const cases: Array<[string, string[]]> = [ [ @@ -17,7 +16,7 @@ const cases: Array<[string, string[]]> = [ [ 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto', 'string', - 'Volo.Abp.Identity.IdentityUserDto' + 'Volo.Abp.Identity.IdentityUserDto', ], ], [ @@ -39,41 +38,38 @@ const cases: Array<[string, string[]]> = [ 'System.String', ], ], - [ - 'AuditedEntityWithUserDto', - ['AuditedEntityWithUserDto'], - ], + ['AuditedEntityWithUserDto', ['AuditedEntityWithUserDto']], ]; test.each(cases)('should parse %s', (inputStr, expected) => { - const parsed = parseBaseTypeWithGenericTypes(inputStr); - expect(parsed).toEqual(expected); -}) + const parsed = parseBaseTypeWithGenericTypes(inputStr); + expect(parsed).toEqual(expected); +}); describe('parseGenerics', () => { - - it('should work with simple type', function() { + it('should work with simple type', function () { const node = parseGenerics('System.String'); expect(node.data).toEqual('System.String'); expect(node.index).toBe(0); expect(node.parent).toBe(null); }); - it('should work with simple Array type', function() { + it('should work with simple Array type', function () { const node = parseGenerics('System.String[]'); expect(node.data).toEqual('System.String[]'); expect(node.index).toBe(0); expect(node.parent).toBe(null); }); - it('should work with simple', function() { + it('should work with simple', function () { const node = parseGenerics('Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'); expect(node.data).toEqual('Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'); expect(node.index).toBe(0); expect(node.parent).toBe(null); }); - it('should work with `Volo.Abp.Application.Dtos.AuditedEntityWithUserDto`', function() { - const type = 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'; + it('should work with `Volo.Abp.Application.Dtos.AuditedEntityWithUserDto`', function () { + const type = + 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'; const node = parseGenerics(type); expect(node.data).toEqual('Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'); @@ -83,37 +79,37 @@ describe('parseGenerics', () => { expect(child.parent).toBe(node); }); - - it('should work with `Volo.Abp.Application.Dtos.AuditedEntityWithUserDto`', function() { - const type = 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'; + it('should work with `Volo.Abp.Application.Dtos.AuditedEntityWithUserDto`', function () { + const type = + 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'; const node = parseGenerics(type); expect(node.data).toEqual('Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'); expect(node.children.length).toBe(2); expect(node.children[0].data).toEqual('System.string'); - expect(node.children[0].index).toBe(0) + expect(node.children[0].index).toBe(0); expect(node.children[1].data).toEqual('Volo.Abp.Identity.IdentityUserDto'); expect(node.children[1].index).toBe(1); - }); - it('should Volo.Abp.Application.Dtos.AuditedEntityWithUserDto>', function() { - const type = 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto>'; + it('should Volo.Abp.Application.Dtos.AuditedEntityWithUserDto>', function () { + const type = + 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto>'; const node = parseGenerics(type); expect(node.data).toEqual('Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'); expect(node.children.length).toBe(2); expect(node.children[0].data).toEqual('System.string'); - expect((node.children[0]).parent).toBe(node); + expect(node.children[0].parent).toBe(node); expect(node.children[1].data).toEqual('Volo.Abp.Identity.IdentityUserDto'); expect(node.children[1].children.length).toBe(1); expect(node.children[1].children[0].data).toEqual('System.Int'); - expect(node.children[1].children[0].parent).toBe(node.children[1]) - expect(node.children[1].children[0].index).toBe(0) - + expect(node.children[1].children[0].parent).toBe(node.children[1]); + expect(node.children[1].children[0].index).toBe(0); }); - it('should Volo.Abp.Application.Dtos.AuditedEntityWithUserDto,System.string>', function() { - const type = 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto,System.string>'; + it('should Volo.Abp.Application.Dtos.AuditedEntityWithUserDto,System.string>', function () { + const type = + 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto,System.string>'; const node = parseGenerics(type); expect(node.data).toEqual('Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'); expect(node.children.length).toBe(2); @@ -121,9 +117,5 @@ describe('parseGenerics', () => { expect(node.children[0].children.length).toBe(1); expect(node.children[0].children[0].data).toEqual('System.Int'); expect(node.children[1].data).toEqual('System.string'); - }); }); - - - diff --git a/npm/ng-packs/packages/schematics/tsconfig.spec.json b/npm/ng-packs/packages/schematics/tsconfig.spec.json index a42c4b02fb..fc61345bb3 100644 --- a/npm/ng-packs/packages/schematics/tsconfig.spec.json +++ b/npm/ng-packs/packages/schematics/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/schematics/vitest.config.mts b/npm/ng-packs/packages/schematics/vitest.config.mts new file mode 100644 index 0000000000..6398ad426d --- /dev/null +++ b/npm/ng-packs/packages/schematics/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/schematics', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'schematics', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/schematics', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/setting-management/.eslintrc.json b/npm/ng-packs/packages/setting-management/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/setting-management/.eslintrc.json +++ b/npm/ng-packs/packages/setting-management/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/setting-management/jest.config.ts b/npm/ng-packs/packages/setting-management/jest.config.ts index 17d04089d0..be1cc254ed 100644 --- a/npm/ng-packs/packages/setting-management/jest.config.ts +++ b/npm/ng-packs/packages/setting-management/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'setting-management', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/setting-management/project.json b/npm/ng-packs/packages/setting-management/project.json index 57f72234fb..fa07efdfc3 100644 --- a/npm/ng-packs/packages/setting-management/project.json +++ b/npm/ng-packs/packages/setting-management/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/setting-management"], - "options": { - "jestConfig": "packages/setting-management/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/setting-management" + } } } } diff --git a/npm/ng-packs/packages/setting-management/tsconfig.lib.json b/npm/ng-packs/packages/setting-management/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/setting-management/tsconfig.lib.json +++ b/npm/ng-packs/packages/setting-management/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/setting-management/tsconfig.spec.json b/npm/ng-packs/packages/setting-management/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/setting-management/tsconfig.spec.json +++ b/npm/ng-packs/packages/setting-management/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/setting-management/vitest.config.mts b/npm/ng-packs/packages/setting-management/vitest.config.mts new file mode 100644 index 0000000000..d1670d87fe --- /dev/null +++ b/npm/ng-packs/packages/setting-management/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/setting-management', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'setting-management', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/setting-management', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/tenant-management/.eslintrc.json b/npm/ng-packs/packages/tenant-management/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/tenant-management/.eslintrc.json +++ b/npm/ng-packs/packages/tenant-management/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/tenant-management/jest.config.ts b/npm/ng-packs/packages/tenant-management/jest.config.ts index e69152e9ab..a5d63bbaaf 100644 --- a/npm/ng-packs/packages/tenant-management/jest.config.ts +++ b/npm/ng-packs/packages/tenant-management/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'tenant-management', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/tenant-management/project.json b/npm/ng-packs/packages/tenant-management/project.json index 2394793380..9bbd10d632 100644 --- a/npm/ng-packs/packages/tenant-management/project.json +++ b/npm/ng-packs/packages/tenant-management/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/tenant-management"], - "options": { - "jestConfig": "packages/tenant-management/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/tenant-management" + } } } } diff --git a/npm/ng-packs/packages/tenant-management/tsconfig.lib.json b/npm/ng-packs/packages/tenant-management/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/tenant-management/tsconfig.lib.json +++ b/npm/ng-packs/packages/tenant-management/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/tenant-management/tsconfig.spec.json b/npm/ng-packs/packages/tenant-management/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/tenant-management/tsconfig.spec.json +++ b/npm/ng-packs/packages/tenant-management/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/tenant-management/vitest.config.mts b/npm/ng-packs/packages/tenant-management/vitest.config.mts new file mode 100644 index 0000000000..9125d40292 --- /dev/null +++ b/npm/ng-packs/packages/tenant-management/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/tenant-management', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'tenant-management', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/tenant-management', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/theme-basic/.eslintrc.json b/npm/ng-packs/packages/theme-basic/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/theme-basic/.eslintrc.json +++ b/npm/ng-packs/packages/theme-basic/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/theme-basic/jest.config.ts b/npm/ng-packs/packages/theme-basic/jest.config.ts index 5e30d9abd6..2c6efb6a7e 100644 --- a/npm/ng-packs/packages/theme-basic/jest.config.ts +++ b/npm/ng-packs/packages/theme-basic/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'theme-basic', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/theme-basic/project.json b/npm/ng-packs/packages/theme-basic/project.json index be0464cc18..ac155e0fe7 100644 --- a/npm/ng-packs/packages/theme-basic/project.json +++ b/npm/ng-packs/packages/theme-basic/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/theme-basic"], - "options": { - "jestConfig": "packages/theme-basic/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/theme-basic" + } } } } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/tests/lazy-style.handler.spec.ts b/npm/ng-packs/packages/theme-basic/src/lib/tests/lazy-style.handler.spec.ts index dd0591f9fd..847c3418a6 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/tests/lazy-style.handler.spec.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/tests/lazy-style.handler.spec.ts @@ -1,23 +1,59 @@ -import { LazyLoadService, LOADING_STRATEGY, LocalizationService } from '@abp/ng.core'; +import { DOCUMENT } from '@angular/common'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; +import { of, Subject } from 'rxjs'; +import { vi } from 'vitest'; + +import { LazyLoadService, LoadingStrategy, LocalizationService } from '@abp/ng.core'; import { DocumentDirHandlerService } from '@abp/ng.theme.shared'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; import { BOOTSTRAP, createLazyStyleHref, LazyStyleHandler } from '../handlers'; - -const currentLang$ = of({ payload: 'en' }); +import { LAZY_STYLES } from '../tokens/lazy-styles.token'; +import { setupComponentResources } from './utils'; describe('LazyStyleHandler', () => { let spectator: SpectatorService; let handler: LazyStyleHandler; - let lazyLoad: LazyLoadService; + let lazyLoad: any; + + beforeAll(async () => { + await setupComponentResources( + '../components/breadcrumb', + import.meta.url + ); + }); + + const dir$ = new Subject<'ltr' | 'rtl'>(); const createService = createServiceFactory({ service: LazyStyleHandler, providers: [ - DocumentDirHandlerService, + { + provide: DOCUMENT, + useValue: document, + }, + { + provide: LAZY_STYLES, + useValue: [BOOTSTRAP], + }, + { + provide: LazyLoadService, + useValue: { + loaded: new Map(), + load: vi.fn(() => of(null)), + remove: vi.fn(), + }, + }, + { + provide: DocumentDirHandlerService, + useValue: { + dir$, + }, + }, { provide: LocalizationService, - useValue: { currentLang: 'en', currentLang$ }, + useValue: { + currentLang: 'en', + currentLang$: of({ payload: 'en' }), + }, }, ], }); @@ -25,7 +61,7 @@ describe('LazyStyleHandler', () => { beforeEach(() => { spectator = createService(); handler = spectator.service; - lazyLoad = handler['lazyLoad']; + lazyLoad = spectator.inject(LazyLoadService); }); describe('#dir', () => { @@ -36,15 +72,19 @@ describe('LazyStyleHandler', () => { it('should set bootstrap to rtl', () => { const oldHref = createLazyStyleHref(BOOTSTRAP, 'ltr'); const newHref = createLazyStyleHref(BOOTSTRAP, 'rtl'); - lazyLoad.loaded.set(newHref, null); // avoid actual loading - const load = jest.spyOn(lazyLoad, 'load'); - const remove = jest.spyOn(lazyLoad, 'remove'); - const strategy = LOADING_STRATEGY.PrependAnonymousStyleToHead(newHref); + + lazyLoad.loaded.set(newHref, null); + + const loadSpy = vi.spyOn(lazyLoad, 'load'); + const removeSpy = vi.spyOn(lazyLoad, 'remove'); handler.dir = 'rtl'; - expect(load).toHaveBeenCalledWith(strategy); - expect(remove).toHaveBeenCalledWith(oldHref); + expect(loadSpy).toHaveBeenCalledTimes(1); + const [strategy] = loadSpy.mock.calls[0]; + expect((strategy as LoadingStrategy).path).toBe(newHref); + + expect(removeSpy).toHaveBeenCalledWith(oldHref); }); }); }); diff --git a/npm/ng-packs/packages/theme-basic/src/lib/tests/utils/index.ts b/npm/ng-packs/packages/theme-basic/src/lib/tests/utils/index.ts new file mode 100644 index 0000000000..73b11724ec --- /dev/null +++ b/npm/ng-packs/packages/theme-basic/src/lib/tests/utils/index.ts @@ -0,0 +1 @@ +export * from './setup-component-resources'; \ No newline at end of file diff --git a/npm/ng-packs/packages/theme-basic/src/lib/tests/utils/setup-component-resources.ts b/npm/ng-packs/packages/theme-basic/src/lib/tests/utils/setup-component-resources.ts new file mode 100644 index 0000000000..0795a5e32b --- /dev/null +++ b/npm/ng-packs/packages/theme-basic/src/lib/tests/utils/setup-component-resources.ts @@ -0,0 +1,54 @@ +import { readFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +/** + * Sets up component resource resolution for Angular component tests. + * This is needed when components have external templates or stylesheets. + * + * @param componentDirPath - The path to the component directory relative to the test file. + * For example: '../components/loader-bar' or './components/my-component' + * @param testFileUrl - The import.meta.url from the test file. Defaults to the caller's location. + * + * @example + * ```typescript + * + * import { setupComponentResources } from './utils'; + * + * beforeAll(() => setupComponentResources('../components/loader-bar', import.meta.url)); + * ``` + */ +export async function setupComponentResources( + componentDirPath: string, + testFileUrl: string = import.meta.url, +): Promise { + try { + if (typeof process !== 'undefined' && process.versions?.node) { + const { ɵresolveComponentResources: resolveComponentResources } = await import('@angular/core'); + + // Get the test file directory path + const testFileDir = dirname(fileURLToPath(testFileUrl)); + const componentDir = resolve(testFileDir, componentDirPath); + + await resolveComponentResources((url: string) => { + // For SCSS/SASS files, return empty CSS since jsdom can't parse SCSS + if (url.endsWith('.scss') || url.endsWith('.sass')) { + return Promise.resolve(''); + } + + // For other files (HTML, CSS, etc.), read the actual content + try { + // Resolve relative paths like './component.scss' or 'component.scss' + const normalizedUrl = url.replace(/^\.\//, ''); + const filePath = resolve(componentDir, normalizedUrl); + return Promise.resolve(readFileSync(filePath, 'utf-8')); + } catch (error) { + // If file not found, return empty string + return Promise.resolve(''); + } + }); + } + } catch (error) { + console.warn('Failed to set up component resource resolver:', error); + } +} diff --git a/npm/ng-packs/packages/theme-basic/src/test-setup.ts b/npm/ng-packs/packages/theme-basic/src/test-setup.ts index 4555f138a7..b349bcf687 100644 --- a/npm/ng-packs/packages/theme-basic/src/test-setup.ts +++ b/npm/ng-packs/packages/theme-basic/src/test-setup.ts @@ -1,2 +1,20 @@ -import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; -setupZoneTestEnv(); +import '@angular/compiler'; +import 'zone.js'; +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; + +// Initialize Angular testing environment +getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting()); + +// Mock window.location for test environment +Object.defineProperty(window, 'location', { + value: { + href: 'http://localhost:4200', + origin: 'http://localhost:4200', + pathname: '/', + search: '', + hash: '', + }, + writable: true, +}); diff --git a/npm/ng-packs/packages/theme-basic/tsconfig.lib.json b/npm/ng-packs/packages/theme-basic/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/theme-basic/tsconfig.lib.json +++ b/npm/ng-packs/packages/theme-basic/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/theme-basic/tsconfig.spec.json b/npm/ng-packs/packages/theme-basic/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/theme-basic/tsconfig.spec.json +++ b/npm/ng-packs/packages/theme-basic/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/theme-basic/vitest.config.mts b/npm/ng-packs/packages/theme-basic/vitest.config.mts new file mode 100644 index 0000000000..f76013d8f4 --- /dev/null +++ b/npm/ng-packs/packages/theme-basic/vitest.config.mts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/theme-basic', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'theme-basic', + watch: false, + globals: true, + environment: 'jsdom', + setupFiles: ['src/test-setup.ts'], + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/theme-basic', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/theme-shared/.eslintrc.json b/npm/ng-packs/packages/theme-shared/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/theme-shared/.eslintrc.json +++ b/npm/ng-packs/packages/theme-shared/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/theme-shared/jest.config.ts b/npm/ng-packs/packages/theme-shared/jest.config.ts index af6440fc3e..c03a94ad03 100644 --- a/npm/ng-packs/packages/theme-shared/jest.config.ts +++ b/npm/ng-packs/packages/theme-shared/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'theme-shared', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/theme-shared/project.json b/npm/ng-packs/packages/theme-shared/project.json index a77551187c..2bfa7e6574 100644 --- a/npm/ng-packs/packages/theme-shared/project.json +++ b/npm/ng-packs/packages/theme-shared/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/theme-shared"], - "options": { - "jestConfig": "packages/theme-shared/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/theme-shared" + } } } } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts index 6e60a77669..a5a1a4d112 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts @@ -6,7 +6,7 @@ import { ABP, LocalizationPipe } from '@abp/ng.core'; @Component({ selector: 'abp-breadcrumb-items', templateUrl: './breadcrumb-items.component.html', - imports: [ NgTemplateOutlet, RouterLink, LocalizationPipe], + imports: [NgTemplateOutlet, RouterLink, LocalizationPipe], }) export class BreadcrumbItemsComponent { @Input() items: Partial[] = []; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts index 5f84edac4f..d0d204dceb 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts @@ -1,31 +1,25 @@ import { ABP, - CORE_OPTIONS, LocalizationPipe, RouterOutletComponent, RoutesService, - LocalizationService, + provideAbpCore, + withOptions, + RestService, + AbpApplicationConfigurationService, + ConfigStateService, } from '@abp/ng.core'; -import { provideHttpClient } from '@angular/common/http'; -import { provideHttpClientTesting } from '@angular/common/http/testing'; import { RouterModule } from '@angular/router'; -import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; +import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/vitest'; +import { of } from 'rxjs'; import { BreadcrumbComponent, BreadcrumbItemsComponent } from '../components'; -import { OTHERS_GROUP } from '@abp/ng.core'; -import { SORT_COMPARE_FUNC } from '@abp/ng.core'; +import { setupComponentResources } from './utils'; const mockRoutes: ABP.Route[] = [ { name: '_::Identity', path: '/identity' }, { name: '_::Users', path: '/identity/users', parentName: '_::Identity' }, ]; -// Simple compare function that doesn't use inject() -const simpleCompareFunc = (a: any, b: any) => { - const aNumber = a.order || 0; - const bNumber = b.order || 0; - return aNumber - bNumber; -}; - describe('BreadcrumbComponent', () => { let spectator: SpectatorRouting; let routes: RoutesService; @@ -34,39 +28,58 @@ describe('BreadcrumbComponent', () => { component: RouterOutletComponent, stubsEnabled: false, detectChanges: false, + imports: [ + RouterModule, + LocalizationPipe, + BreadcrumbComponent, + BreadcrumbItemsComponent, + ], providers: [ - provideHttpClient(), - provideHttpClientTesting(), - { - provide: CORE_OPTIONS, - useValue: { + provideAbpCore( + withOptions({ environment: { apis: { default: { url: 'http://localhost:4200', }, }, + application: { + name: 'TestApp', + baseUrl: 'http://localhost:4200', + }, }, - } + registerLocaleFn: () => Promise.resolve(), + skipGetAppConfiguration: true, + }), + ), + { + provide: RestService, + useValue: { + request: vi.fn(), + handleError: vi.fn(), + }, }, - RoutesService, - LocalizationService, { - provide: OTHERS_GROUP, - useValue: 'AbpUi::OthersGroup', + provide: AbpApplicationConfigurationService, + useValue: { + get: vi.fn(), + }, }, { - provide: SORT_COMPARE_FUNC, - useValue: simpleCompareFunc, + provide: ConfigStateService, + useValue: { + getOne: vi.fn(), + getAll: vi.fn(() => ({})), + getAll$: vi.fn(() => of({})), + getDeep: vi.fn(), + getDeep$: vi.fn(() => of(undefined)), + createOnUpdateStream: vi.fn(() => ({ + subscribe: vi.fn(() => ({ unsubscribe: vi.fn() })) + })), + refreshAppState: vi.fn(), + }, }, ], - declarations: [], - imports: [ - RouterModule, - LocalizationPipe, - BreadcrumbComponent, - BreadcrumbItemsComponent, - ], routes: [ { path: '', @@ -85,6 +98,8 @@ describe('BreadcrumbComponent', () => { ], }); + beforeAll(() => setupComponentResources('../components/breadcrumb', import.meta.url)); + beforeEach(() => { spectator = createRouting(); routes = spectator.inject(RoutesService); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts index bf834d182d..2986e7c14e 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts @@ -1,4 +1,4 @@ -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { ButtonComponent } from '../components'; describe('ButtonComponent', () => { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts index fa474640f6..9990b33676 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts @@ -1,4 +1,4 @@ -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { CardComponent, CardBodyComponent, diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts index e2fba6d0da..4ea0946c01 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts @@ -1,4 +1,4 @@ -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { FormCheckboxComponent } from '../components/checkbox/checkbox.component'; describe('FormCheckboxComponent', () => { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts index 029becaa96..d03e69733d 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts @@ -1,27 +1,24 @@ import { CoreTestingModule } from '@abp/ng.core/testing'; -import { NgModule } from '@angular/core'; -import { fakeAsync, tick } from '@angular/core/testing'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; -import { timer } from 'rxjs'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; +import { firstValueFrom, timer } from 'rxjs'; import { ConfirmationComponent } from '../components'; import { Confirmation } from '../models'; import { ConfirmationService } from '../services'; import { CONFIRMATION_ICONS, DEFAULT_CONFIRMATION_ICONS } from '../tokens/confirmation-icons.token'; - -@NgModule({ - exports: [ConfirmationComponent], - declarations: [], - imports: [CoreTestingModule.withConfig(), ConfirmationComponent], - providers: [{ provide: CONFIRMATION_ICONS, useValue: DEFAULT_CONFIRMATION_ICONS }], -}) -export class MockModule {} +import { setupComponentResources } from './utils'; describe('ConfirmationService', () => { let spectator: SpectatorService; let service: ConfirmationService; + const createService = createServiceFactory({ service: ConfirmationService, - imports: [CoreTestingModule.withConfig(), MockModule], + imports: [CoreTestingModule.withConfig(), ConfirmationComponent], + providers: [{ provide: CONFIRMATION_ICONS, useValue: DEFAULT_CONFIRMATION_ICONS }], + }); + + beforeAll(async () => { + await setupComponentResources('../components/confirmation', import.meta.url); }); beforeEach(() => { @@ -33,16 +30,16 @@ describe('ConfirmationService', () => { clearElements(); }); - test('should display a confirmation popup', fakeAsync(() => { + test('should display a confirmation popup', async () => { service.show('_::MESSAGE', '_::TITLE'); - tick(); + await firstValueFrom(timer(10)); expect(selectConfirmationContent('.title')).toBe('TITLE'); expect(selectConfirmationContent('.message')).toBe('MESSAGE'); - })); + }); - test('should display HTML string in title, message, and buttons', fakeAsync(() => { + test('should display HTML string in title, message, and buttons', async () => { service.show( '_::MESSAGE', '_::TITLE', @@ -53,24 +50,24 @@ describe('ConfirmationService', () => { }, ); - tick(); + await firstValueFrom(timer(10)); expect(selectConfirmationContent('.custom-title')).toBe('TITLE'); expect(selectConfirmationContent('.custom-message')).toBe('MESSAGE'); expect(selectConfirmationContent('.custom-cancel')).toBe('CANCEL'); expect(selectConfirmationContent('.custom-yes')).toBe('YES'); - })); + }); - test('should display custom FA icon', fakeAsync(() => { + test('should display custom FA icon', async () => { service.show('_::MESSAGE', '_::TITLE', undefined, { icon: 'fa fa-info', }); - tick(); + await firstValueFrom(timer(10)); expect(selectConfirmationElement('.icon').className).toBe('icon fa fa-info'); - })); + }); - test('should display custom icon as html element', fakeAsync(() => { + test('should display custom icon as html element', async () => { const className = 'custom-icon'; const selector = '.' + className; @@ -78,12 +75,14 @@ describe('ConfirmationService', () => { iconTemplate: `I am icon`, }); - tick(); + await firstValueFrom(timer(10)); const element = selectConfirmationElement(selector); expect(element).toBeTruthy(); expect(element.innerHTML).toBe('I am icon'); - })); + }); + + test.each` type | selector | icon ${'info'} | ${'.info'} | ${'.fa-info-circle'} @@ -93,7 +92,7 @@ describe('ConfirmationService', () => { `('should display $type confirmation popup', async ({ type, selector, icon }) => { service[type]('_::MESSAGE', '_::TITLE'); - await timer(0).toPromise(); + await firstValueFrom(timer(10)); expect(selectConfirmationContent('.title')).toBe('TITLE'); expect(selectConfirmationContent('.message')).toBe('MESSAGE'); @@ -101,31 +100,18 @@ describe('ConfirmationService', () => { expect(selectConfirmationElement(icon)).toBeTruthy(); }); - // test('should close with ESC key', (done) => { - // service - // .info('', '') - // .pipe(take(1)) - // .subscribe((status) => { - // expect(status).toBe(Confirmation.Status.dismiss); - // done(); - // }); - // const escape = new KeyboardEvent('keyup', { key: 'Escape' }); - // document.dispatchEvent(escape); - // }); - - test('should close when click cancel button', done => { + test('should close when click cancel button', async () => { service.info('_::', '_::', { yesText: '_::Sure', cancelText: '_::Exit' }).subscribe(status => { expect(status).toBe(Confirmation.Status.reject); - done(); }); - timer(0).subscribe(() => { - expect(selectConfirmationContent('button#cancel')).toBe('Exit'); - expect(selectConfirmationContent('button#confirm')).toBe('Sure'); + await firstValueFrom(timer(10)); - (document.querySelector('button#cancel') as HTMLButtonElement).click(); - }); + expect(selectConfirmationContent('button#cancel')).toBe('Exit'); + expect(selectConfirmationContent('button#confirm')).toBe('Sure'); + + (document.querySelector('button#cancel') as HTMLButtonElement).click(); }); test.each` @@ -135,9 +121,9 @@ describe('ConfirmationService', () => { `( 'should call the listenToEscape method $count times when dismissible is $dismissible', ({ dismissible, count }) => { - const spy = jest.spyOn(service as any, 'listenToEscape'); + const spy = vi.spyOn(service as any, 'listenToEscape'); - service.info('_::', '_::', { dismissible }); + service.info('_::', '_::', { dismissible }); expect(spy).toHaveBeenCalledTimes(count); }, diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/ellipsis.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/ellipsis.directive.spec.ts index 727a1c5312..e9b0e45a0d 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/ellipsis.directive.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/ellipsis.directive.spec.ts @@ -1,4 +1,4 @@ -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { EllipsisDirective } from '../directives/ellipsis.directive'; describe('EllipsisDirective', () => { @@ -39,17 +39,61 @@ describe('EllipsisDirective', () => { expect(directive.title).toBe('test title'); }); - test('should have element innerText as title if not specified', () => { - spectator.setHostInput({ title: undefined }); + test('should add abp-ellipsis-inline class to element if width is given', () => { + expect(el).toHaveClass('abp-ellipsis-inline'); + }); +}); + +describe('EllipsisDirective when title is not specified', () => { + let spectator: SpectatorDirective; + let directive: EllipsisDirective; + let el: HTMLDivElement; + const createDirective = createDirectiveFactory({ + directive: EllipsisDirective, + }); + + beforeEach(() => { + spectator = createDirective( + '
test content
', + { + hostProps: { + title: undefined, + width: '100px', + }, + }, + ); + directive = spectator.directive; + el = spectator.query('div') as HTMLDivElement; + }); + + test('should have element innerText as title', () => { expect(directive.title).toBe(el.innerText); }); +}); - test('should add abp-ellipsis-inline class to element if width is given', () => { - expect(el).toHaveClass('abp-ellipsis-inline'); +describe('EllipsisDirective when width is not given', () => { + let spectator: SpectatorDirective; + let directive: EllipsisDirective; + let el: HTMLDivElement; + const createDirective = createDirectiveFactory({ + directive: EllipsisDirective, + }); + + beforeEach(() => { + spectator = createDirective( + '
test content
', + { + hostProps: { + title: 'test title', + width: undefined, + }, + }, + ); + directive = spectator.directive; + el = spectator.query('div') as HTMLDivElement; }); - test('should add abp-ellipsis class to element if width is not given', () => { - spectator.setHostInput({ width: undefined }); + test('should add abp-ellipsis class to element', () => { expect(el).toHaveClass('abp-ellipsis'); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts index 93b8d32f0f..d1cb9a5940 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts @@ -1,49 +1,84 @@ -import { CORE_OPTIONS, LocalizationPipe } from '@abp/ng.core'; -import { HttpClient, HttpClientModule } from '@angular/common/http'; -import { ElementRef, Renderer2 } from '@angular/core'; -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { DOCUMENT } from '@angular/common'; +import { Router } from '@angular/router'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; +import { Pipe, PipeTransform } from '@angular/core'; import { Subject } from 'rxjs'; +import { vi } from 'vitest'; + import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component'; +import { setupComponentResources } from './utils'; + +/** + * Mock pipe to avoid ABP DI chain + */ +@Pipe({ name: 'abpLocalization'}) +class MockLocalizationPipe implements PipeTransform { + transform(value: any): any { + return value; + } +} + +describe('HttpErrorWrapperComponent', () => { + let spectator: Spectator; + let createComponent: ReturnType>; -describe('ErrorComponent', () => { - let spectator: SpectatorHost; - const createHost = createHostFactory({ - component: HttpErrorWrapperComponent, - declarations: [], - mocks: [HttpClient], - providers: [ - { provide: CORE_OPTIONS, useValue: {} }, - { provide: Renderer2, useValue: { removeChild: () => null } }, - { - provide: ElementRef, - useValue: { nativeElement: document.createElement('div') }, - }, - ], - imports: [HttpClientModule, LocalizationPipe], + beforeAll(async () => { + await setupComponentResources( + '../components/http-error-wrapper', + import.meta.url, + ); }); - beforeEach(() => { - spectator = createHost( - '', - ); - spectator.component.destroy$ = new Subject(); - }); - - describe('#destroy', () => { - it('should be call when pressed the esc key', done => { - spectator.component.destroy$.subscribe(() => { - done(); - }); + beforeEach(() => { + if (!createComponent) { + createComponent = createComponentFactory({ + component: HttpErrorWrapperComponent, + detectChanges: false, - spectator.keyboard.pressEscape(); - }); + overrideComponents: [ + [ + HttpErrorWrapperComponent, + { + set: { + template: '
', + imports: [MockLocalizationPipe], + }, + }, + ], + ], - it('should be call when clicked the close button', done => { - spectator.component.destroy$.subscribe(() => { - done(); + providers: [ + { + provide: DOCUMENT, + useValue: document, + }, + { + provide: Router, + useValue: { + navigateByUrl: vi.fn(), + }, + }, + ], }); + } + + spectator = createComponent(); + + spectator.component.destroy$ = new Subject(); + spectator.component.title = '_::Oops!'; + spectator.component.details = '_::Sorry, an error has occured.'; + }); + + it('should create component', () => { + expect(spectator.component).toBeTruthy(); + }); + + it('should emit destroy$ when destroy is called', () => { + const spy = vi.fn(); + spectator.component.destroy$.subscribe(spy); + + spectator.component.destroy(); - spectator.click('#abp-close-button'); - }); + expect(spy).toHaveBeenCalled(); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts index 75726103d2..d57f75af28 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts @@ -2,46 +2,48 @@ import { HttpErrorReporterService } from '@abp/ng.core'; import { CoreTestingModule } from '@abp/ng.core/testing'; import { APP_BASE_HREF } from '@angular/common'; import { HttpErrorResponse, HttpHeaders } from '@angular/common/http'; -import { NgModule } from '@angular/core'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { OAuthService } from 'angular-oauth2-oidc'; import { of, Subject } from 'rxjs'; -import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component'; import { ErrorHandler } from '../handlers'; import { ConfirmationService } from '../services'; +import { CreateErrorComponentService } from '../services/create-error-component.service'; +import { RouterErrorHandlerService } from '../services/router-error-handler.service'; import { CUSTOM_ERROR_HANDLERS, HTTP_ERROR_CONFIG } from '../tokens/http-error.token'; import { CustomHttpErrorHandlerService } from '../models'; const customHandlerMock: CustomHttpErrorHandlerService = { priority: 100, - canHandle: jest.fn().mockReturnValue(true), - execute: jest.fn(), + canHandle: vi.fn().mockReturnValue(true), + execute: vi.fn(), }; const reporter$ = new Subject(); -@NgModule({ - exports: [HttpErrorWrapperComponent], - declarations: [], - imports: [CoreTestingModule, HttpErrorWrapperComponent], -}) -class MockModule {} - let spectator: SpectatorService; let service: ErrorHandler; let httpErrorReporter: HttpErrorReporterService; -const errorConfirmation: jest.Mock = jest.fn(() => of(null)); -const CONFIRMATION_BUTTONS = { - hideCancelBtn: true, - yesText: 'AbpAccount::Close', -}; +const errorConfirmation = vi.fn(() => of(null)); + describe('ErrorHandler', () => { const createService = createServiceFactory({ service: ErrorHandler, - imports: [CoreTestingModule.withConfig(), MockModule], + imports: [CoreTestingModule.withConfig()], mocks: [OAuthService], providers: [ + { + provide: RouterErrorHandlerService, + useValue: { + listen: vi.fn(), + }, + }, + { + provide: CreateErrorComponentService, + useValue: { + execute: vi.fn(), + }, + }, { provide: HttpErrorReporterService, useValue: { @@ -65,7 +67,10 @@ describe('ErrorHandler', () => { }, { provide: HTTP_ERROR_CONFIG, - useFactory: () => ({}), + useValue: { + skipHandledErrorCodes: [], + errorScreen: {}, + }, }, ], }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts index cf4eb4aac3..e6ae275a95 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts @@ -1,4 +1,4 @@ -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { FormInputComponent } from '../components/form-input/form-input.component'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts index e2904e33f1..a825cbf7db 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts @@ -1,26 +1,32 @@ -import { HttpWaitService, LOADER_DELAY, SubscriptionService } from '@abp/ng.core'; +import { HttpWaitService, LOADER_DELAY, RouterWaitService, SubscriptionService } from '@abp/ng.core'; import { HttpRequest } from '@angular/common/http'; -import { NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router'; -import { createComponentFactory, Spectator, SpyObject } from '@ngneat/spectator/jest'; -import { Subject, timer } from 'rxjs'; +import { NavigationStart, Router } from '@angular/router'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; +import { combineLatest, firstValueFrom, Subject, timer } from 'rxjs'; import { LoaderBarComponent } from '../components/loader-bar/loader-bar.component'; +import { setupComponentResources } from './utils'; describe('LoaderBarComponent', () => { let spectator: Spectator; let router: Router; + let createComponent: ReturnType>; const events$ = new Subject(); - const createComponent = createComponentFactory({ - component: LoaderBarComponent, - detectChanges: false, - providers: [ - SubscriptionService, - { provide: Router, useValue: { events: events$ } }, - { provide: LOADER_DELAY, useValue: 0 }, - ], - }); + beforeAll(() => setupComponentResources('../components/loader-bar', import.meta.url)); beforeEach(() => { + if (!createComponent) { + createComponent = createComponentFactory({ + component: LoaderBarComponent, + detectChanges: false, + providers: [ + SubscriptionService, + { provide: Router, useValue: { events: events$ } }, + { provide: LOADER_DELAY, useValue: 0 }, + ], + }); + } + spectator = createComponent({}); spectator.component.intervalPeriod = 1; spectator.component.stopDelay = 1; @@ -32,66 +38,127 @@ describe('LoaderBarComponent', () => { expect(spectator.component.color).toBe('#77b6ff'); }); - it('should increase the progressLevel', done => { + it('should increase the progressLevel', async () => { spectator.detectChanges(); const httpWaitService = spectator.inject(HttpWaitService); httpWaitService.addRequest(new HttpRequest('GET', 'test')); spectator.detectChanges(); - setTimeout(() => { - expect(spectator.component.progressLevel > 0).toBeTruthy(); - done(); - }, 10); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(spectator.component.progressLevel > 0).toBeTruthy(); }); - it('should be interval unsubscribed', done => { - const request = new HttpRequest('GET', 'test'); + it('should be interval unsubscribed', async () => { + const request = new HttpRequest('GET', 'test'); spectator.detectChanges(); const httpWaitService = spectator.inject(HttpWaitService); + + await firstValueFrom(combineLatest([ + httpWaitService.getLoading$(), + spectator.inject(RouterWaitService).getLoading$() + ])); + httpWaitService.addRequest(request); + spectator.detectChanges(); + + let attempts = 0; + while (spectator.component.interval.closed && attempts < 50) { + await new Promise(resolve => setTimeout(resolve, 10)); + spectator.detectChanges(); + attempts++; + } + expect(spectator.component.interval.closed).toBe(false); + httpWaitService.deleteRequest(request); - timer(400).subscribe(() => { - expect(spectator.component.interval.closed).toBe(true); - done(); - }); + spectator.detectChanges(); + + await firstValueFrom(timer(400)); + + expect(spectator.component.interval.closed).toBe(true); }); - it('should start and stop the loading with navigation', done => { + + it('should start and stop the loading with navigation', async () => { + spectator.detectChanges(); + const routerWaitService = spectator.inject(RouterWaitService); + + routerWaitService.setLoading(true); spectator.detectChanges(); - events$.next(new NavigationStart(1, 'test')); + + let attempts = 0; + while (spectator.component.interval.closed && attempts < 50) { + await new Promise(resolve => setTimeout(resolve, 10)); + spectator.detectChanges(); + attempts++; + } expect(spectator.component.interval.closed).toBe(false); - events$.next(new NavigationEnd(1, 'test', 'test')); - events$.next(new NavigationError(1, 'test', 'test')); + routerWaitService.setLoading(false); + spectator.detectChanges(); + + attempts = 0; + while (spectator.component.progressLevel !== 100 && attempts < 50) { + await new Promise(resolve => setTimeout(resolve, 10)); + spectator.detectChanges(); + attempts++; + } expect(spectator.component.progressLevel).toBe(100); - timer(2).subscribe(() => { - expect(spectator.component.progressLevel).toBe(0); - done(); - }); + await firstValueFrom(timer(spectator.component.stopDelay + 10)); + expect(spectator.component.progressLevel).toBe(0); }); - it('should stop the loading with navigation', done => { + it('should stop the loading with navigation', async () => { + spectator.detectChanges(); + const routerWaitService = spectator.inject(RouterWaitService); + + routerWaitService.setLoading(true); spectator.detectChanges(); - events$.next(new NavigationStart(1, 'test')); + + let attempts = 0; + while (spectator.component.interval.closed && attempts < 50) { + await new Promise(resolve => setTimeout(resolve, 10)); + spectator.detectChanges(); + attempts++; + } expect(spectator.component.interval.closed).toBe(false); - events$.next(new NavigationEnd(1, 'testend', 'testend')); + routerWaitService.setLoading(false); + spectator.detectChanges(); + + attempts = 0; + while (spectator.component.progressLevel !== 100 && attempts < 50) { + await new Promise(resolve => setTimeout(resolve, 10)); + spectator.detectChanges(); + attempts++; + } expect(spectator.component.progressLevel).toBe(100); - timer(2).subscribe(() => { - expect(spectator.component.progressLevel).toBe(0); - done(); - }); + await firstValueFrom(timer(spectator.component.stopDelay + 10)); + expect(spectator.component.progressLevel).toBe(0); }); describe('#startLoading', () => { - it('should return when isLoading is true', done => { + it('should return when isLoading is true', async () => { spectator.detectChanges(); + events$.next(new NavigationStart(1, 'test')); + spectator.detectChanges(); + + let attempts = 0; + while (spectator.component.interval.closed && attempts < 50) { + await new Promise(resolve => setTimeout(resolve, 10)); + spectator.detectChanges(); + attempts++; + } + events$.next(new NavigationStart(1, 'test')); - done(); + spectator.detectChanges(); + + expect(spectator.component).toBeTruthy(); }); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts index 91589f2155..f80388d478 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts @@ -1,4 +1,4 @@ -import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest'; +import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/vitest'; import { LoadingDirective } from '../directives'; import { LoadingComponent } from '../components'; import { Component } from '@angular/core'; @@ -29,10 +29,11 @@ describe('LoadingDirective', () => { expect(spectator.directive).toBeTruthy(); }); - it('should handle loading input', () => { - spectator.setHostInput({ loading: false }); - spectator.detectChanges(); + it('should handle loading input', async () => { + spectator.directive.loading = false; + await new Promise(resolve => setTimeout(resolve, 10)); expect(spectator.directive).toBeTruthy(); + expect(spectator.directive.loading).toBe(false); }); }); @@ -53,19 +54,19 @@ describe('LoadingDirective', () => { expect(spectator.directive.targetElement).toBe(mockTarget); }); - it('should handle delay input', () => { - spectator.setHostInput({ delay: 100 }); - spectator.detectChanges(); + it('should handle delay input', async () => { + spectator.directive.delay = 100; + await new Promise(resolve => setTimeout(resolve, 10)); expect(spectator.directive).toBeTruthy(); }); - it('should handle loading state changes', () => { - spectator.setHostInput({ loading: false }); - spectator.detectChanges(); + it('should handle loading state changes', async() => { + spectator.directive.loading = false; + await new Promise(resolve => setTimeout(resolve, 10)); expect(spectator.directive).toBeTruthy(); - spectator.setHostInput({ loading: true }); - spectator.detectChanges(); + spectator.directive.loading = true; + await new Promise(resolve => setTimeout(resolve, 10)); expect(spectator.directive).toBeTruthy(); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts index dc79c47a9c..0caef230ca 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts @@ -1,10 +1,11 @@ import { ConfirmationService } from '@abp/ng.theme.shared'; import { CoreTestingModule } from '@abp/ng.core/testing'; import { Component, EventEmitter, Input } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; import { Confirmation } from '@abp/ng.theme.shared'; -import { Subject, timer } from 'rxjs'; +import { firstValueFrom, Subject, timer } from 'rxjs'; import { ModalComponent } from '../components/modal/modal.component'; +import { setupComponentResources } from './utils'; @Component({ template: ` @@ -27,25 +28,34 @@ class TestHostComponent { } const mockConfirmation$ = new Subject(); -const disappearFn = jest.fn(); +const disappearFn = vi.fn(); describe('ModalComponent', () => { let spectator: Spectator; + let createComponent: ReturnType>; - const createComponent = createComponentFactory({ - component: TestHostComponent, - imports: [CoreTestingModule.withConfig()], - providers: [ - { - provide: ConfirmationService, - useValue: { - warn: jest.fn(() => mockConfirmation$), - }, - }, - ], - }); + beforeAll(() => setupComponentResources('../components/modal', import.meta.url)); beforeEach(() => { + // Create component factory in beforeEach to ensure beforeAll has run + if (!createComponent) { + createComponent = createComponentFactory({ + component: TestHostComponent, + imports: [ + CoreTestingModule.withConfig(), + ModalComponent, + ], + providers: [ + { + provide: ConfirmationService, + useValue: { + warn: vi.fn(() => mockConfirmation$), + }, + }, + ], + }); + } + spectator = createComponent(); disappearFn.mockClear(); }); @@ -71,10 +81,10 @@ describe('ModalComponent', () => { }); }); -async function wait0ms() { - await timer(0).toPromise(); +async function wait0ms() { + await firstValueFrom(timer(0)); } -async function wait300ms() { - await timer(300).toPromise(); +async function wait300ms() { + await firstValueFrom(timer(300)); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts index ae008d73ee..401da50070 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts @@ -1,23 +1,25 @@ -import { CoreTestingModule } from '@abp/ng.core/testing'; -import { NgModule } from '@angular/core'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { ContentProjectionService } from '@abp/ng.core'; +import { ComponentRef } from '@angular/core'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; import { ToastContainerComponent } from '../components/toast-container/toast-container.component'; -import { ToastComponent } from '../components/toast/toast.component'; import { ToasterService } from '../services/toaster.service'; -@NgModule({ - exports: [ToastContainerComponent], - declarations: [], - imports: [CoreTestingModule.withConfig(), ToastContainerComponent, ToastComponent], -}) -export class MockModule {} - describe('ToasterService', () => { let spectator: SpectatorService; let service: ToasterService; + const mockComponentRef = { + changeDetectorRef: { detectChanges: vi.fn() }, + instance: {} as ToastContainerComponent, + } as unknown as ComponentRef; + + const contentProjectionService = { + projectContent: vi.fn().mockReturnValue(mockComponentRef), + } satisfies Partial; + const createService = createServiceFactory({ service: ToasterService, - imports: [CoreTestingModule.withConfig(), MockModule], + providers: [{ provide: ContentProjectionService, useValue: contentProjectionService }], }); beforeEach(() => { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/utils/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/utils/index.ts new file mode 100644 index 0000000000..73b11724ec --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/utils/index.ts @@ -0,0 +1 @@ +export * from './setup-component-resources'; \ No newline at end of file diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/utils/setup-component-resources.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/utils/setup-component-resources.ts new file mode 100644 index 0000000000..0795a5e32b --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/utils/setup-component-resources.ts @@ -0,0 +1,54 @@ +import { readFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +/** + * Sets up component resource resolution for Angular component tests. + * This is needed when components have external templates or stylesheets. + * + * @param componentDirPath - The path to the component directory relative to the test file. + * For example: '../components/loader-bar' or './components/my-component' + * @param testFileUrl - The import.meta.url from the test file. Defaults to the caller's location. + * + * @example + * ```typescript + * + * import { setupComponentResources } from './utils'; + * + * beforeAll(() => setupComponentResources('../components/loader-bar', import.meta.url)); + * ``` + */ +export async function setupComponentResources( + componentDirPath: string, + testFileUrl: string = import.meta.url, +): Promise { + try { + if (typeof process !== 'undefined' && process.versions?.node) { + const { ɵresolveComponentResources: resolveComponentResources } = await import('@angular/core'); + + // Get the test file directory path + const testFileDir = dirname(fileURLToPath(testFileUrl)); + const componentDir = resolve(testFileDir, componentDirPath); + + await resolveComponentResources((url: string) => { + // For SCSS/SASS files, return empty CSS since jsdom can't parse SCSS + if (url.endsWith('.scss') || url.endsWith('.sass')) { + return Promise.resolve(''); + } + + // For other files (HTML, CSS, etc.), read the actual content + try { + // Resolve relative paths like './component.scss' or 'component.scss' + const normalizedUrl = url.replace(/^\.\//, ''); + const filePath = resolve(componentDir, normalizedUrl); + return Promise.resolve(readFileSync(filePath, 'utf-8')); + } catch (error) { + // If file not found, return empty string + return Promise.resolve(''); + } + }); + } + } catch (error) { + console.warn('Failed to set up component resource resolver:', error); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts index 9f263779e2..1fcd5c3591 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts @@ -1,8 +1,9 @@ import { AbpApplicationConfigurationService, ConfigStateService } from '@abp/ng.core'; import { CoreTestingModule } from '@abp/ng.core/testing'; +import { AbpApplicationLocalizationService } from '@abp/ng.core'; import { HttpClient } from '@angular/common/http'; import { Component, Injector } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; import { OAuthService } from 'angular-oauth2-oidc'; import { of } from 'rxjs'; import { getPasswordValidators, validatePassword } from '../utils'; @@ -32,6 +33,44 @@ describe('ValidationUtils', () => { 'Abp.Identity.Password.RequireDigit': 'True', }, }, + localization: { + values: {}, + languages: [], + currentCulture: { + cultureName: 'en', + displayName: 'English', + englishName: 'English', + threeLetterIsoLanguageName: 'eng', + twoLetterIsoLanguageName: 'en', + isRightToLeft: false, + name: 'en', + nativeName: 'English', + dateTimeFormat: { + calendarAlgorithmType: 'SolarCalendar', + dateTimeFormatLong: 'dddd, MMMM d, yyyy', + shortDatePattern: 'M/d/yyyy', + fullDateTimePattern: 'dddd, MMMM d, yyyy h:mm:ss tt', + dateSeparator: '/', + shortTimePattern: 'h:mm tt', + longTimePattern: 'h:mm:ss tt', + }, + }, + defaultResourceName: null, + resources: {}, + languagesMap: {}, + languageFilesMap: {}, + }, + }), + }, + }, + { + provide: AbpApplicationLocalizationService, + useValue: { + get: () => + of({ + resources: { + Default: { texts: {}, baseResources: [] }, + }, }), }, }, diff --git a/npm/ng-packs/packages/theme-shared/src/test-setup.ts b/npm/ng-packs/packages/theme-shared/src/test-setup.ts index 2cf1ac7191..3eaa53b84d 100644 --- a/npm/ng-packs/packages/theme-shared/src/test-setup.ts +++ b/npm/ng-packs/packages/theme-shared/src/test-setup.ts @@ -1,10 +1,28 @@ -import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; -setupZoneTestEnv(); - -const originalError = console.error; -console.error = (...args: any[]) => { - if (args[0]?.includes?.('ExpressionChangedAfterItHasBeenCheckedError')) { - return; - } - originalError.apply(console, args); -}; +import '@angular/compiler'; +import 'zone.js'; +import 'zone.js/testing'; +import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; +import { + ɵgetCleanupHook as getCleanupHook, + getTestBed +} from '@angular/core/testing'; + + +beforeEach(getCleanupHook(false)); +afterEach(getCleanupHook(true)); + +// Initialize Angular testing environment +getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting()); + + +// Mock window.location for test environment +Object.defineProperty(window, 'location', { + value: { + href: 'http://localhost:4200', + origin: 'http://localhost:4200', + pathname: '/', + search: '', + hash: '', + }, + writable: true, +}); diff --git a/npm/ng-packs/packages/theme-shared/tsconfig.lib.json b/npm/ng-packs/packages/theme-shared/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/theme-shared/tsconfig.lib.json +++ b/npm/ng-packs/packages/theme-shared/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/theme-shared/tsconfig.spec.json b/npm/ng-packs/packages/theme-shared/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/theme-shared/tsconfig.spec.json +++ b/npm/ng-packs/packages/theme-shared/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/theme-shared/vitest.config.mts b/npm/ng-packs/packages/theme-shared/vitest.config.mts new file mode 100644 index 0000000000..d9496b26fd --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/vitest.config.mts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/theme-shared', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'theme-shared', + watch: false, + globals: true, + environment: 'jsdom', + setupFiles: ['src/test-setup.ts'], + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/theme-shared', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/vitest.config.mts b/npm/ng-packs/vitest.config.mts new file mode 100644 index 0000000000..f239d0bfce --- /dev/null +++ b/npm/ng-packs/vitest.config.mts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + projects: [ + './packages/core/vitest.config.mts', + './packages/theme-basic/vitest.config.mts', + './packages/theme-shared/vitest.config.mts', + './packages/oauth/vitest.config.mts', + './packages/generators/vitest.config.mts', + './packages/schematics/vitest.config.mts', + ], + }, +}); \ No newline at end of file