diff --git a/.github/changes-filter.yaml b/.github/changes-filter.yaml index b103ce3bf..46aa5bf04 100644 --- a/.github/changes-filter.yaml +++ b/.github/changes-filter.yaml @@ -16,6 +16,7 @@ frontend-tests: frontend: - "src/frontend/**" - "**/typescript_test.yml" + - "**/jest_test.yml" docs: - "docs/**" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8639f4065..b7cc1b2b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,6 +163,12 @@ jobs: with: python-versions: ${{ inputs.python-versions || '["3.10"]' }} + test-frontend-unit: + needs: [path-filter, set-ci-condition] + name: Run Frontend Unit Tests + if: ${{ needs.path-filter.outputs.frontend == 'true' && needs.set-ci-condition.outputs.should-run-tests == 'true' }} + uses: ./.github/workflows/jest_test.yml + test-frontend: needs: [path-filter, set-ci-condition] name: Run Frontend Tests @@ -195,6 +201,7 @@ jobs: needs: [ test-backend, + test-frontend-unit, test-frontend, lint-backend, test-docs-build, diff --git a/.github/workflows/jest_test.yml b/.github/workflows/jest_test.yml new file mode 100644 index 000000000..e61e46aa4 --- /dev/null +++ b/.github/workflows/jest_test.yml @@ -0,0 +1,74 @@ +name: Run Frontend Jest Unit Tests + +on: + workflow_call: + inputs: + ref: + description: "(Optional) ref to checkout" + required: false + type: string + workflow_dispatch: + inputs: + ref: + description: "(Optional) ref to checkout" + required: false + type: string + +env: + NODE_VERSION: "21" + +jobs: + jest-unit-tests: + name: Frontend Jest Unit Tests + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + checks: write + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || github.ref }} + + - name: Setup Node.js Environment + uses: actions/setup-node@v4 + id: setup-node + with: + node-version: ${{ env.NODE_VERSION }} + cache: "npm" + cache-dependency-path: ./src/frontend/package-lock.json + + - name: Run Frontend Unit Tests + run: make test_frontend_ci + + - name: Publish Test Results + uses: mikepenz/action-junit-report@v5 + if: always() + with: + report_paths: 'src/frontend/test-results/junit.xml' + check_name: 'Frontend Unit Test Results' + fail_on_failure: true + require_tests: true + + - name: Add Coverage PR Comment + uses: MishaKav/jest-coverage-comment@main + if: github.event_name == 'pull_request' + with: + coverage-summary-path: src/frontend/coverage/coverage-summary.json + title: Frontend Unit Test Coverage Report + summary-title: Coverage Summary + badge-title: Coverage + hide-comment: false + create-new-comment: false + hide-summary: false + junitxml-title: Unit Test Results + junitxml-path: src/frontend/test-results/junit.xml + + - name: Upload Coverage Reports + uses: actions/upload-artifact@v4 + if: always() + with: + name: frontend-coverage-report + path: src/frontend/coverage/ + retention-days: 30 \ No newline at end of file diff --git a/.github/workflows/typescript_test.yml b/.github/workflows/typescript_test.yml index 8127378ac..30fb061c0 100644 --- a/.github/workflows/typescript_test.yml +++ b/.github/workflows/typescript_test.yml @@ -271,7 +271,6 @@ jobs: node-version: ${{ env.NODE_VERSION }} cache: "npm" cache-dependency-path: ./src/frontend/package-lock.json - - name: Install Frontend Dependencies run: npm ci working-directory: ./src/frontend diff --git a/Makefile.frontend b/Makefile.frontend index 71da65a05..9b2eb1512 100644 --- a/Makefile.frontend +++ b/Makefile.frontend @@ -104,7 +104,7 @@ test_frontend_verbose: frontend_deps_check ## run frontend tests with verbose ou # Run frontend tests in CI mode (no watch, with coverage) test_frontend_ci: frontend_deps_check ## run frontend tests in CI mode @echo "Running frontend tests in CI mode..." - @cd $(FRONTEND_DIR) && npx jest --ci --coverage --watchAll=false + @cd $(FRONTEND_DIR) && CI=true npx jest --ci --coverage --watchAll=false # Clean test cache and run tests test_frontend_clean: frontend_deps_check ## clean test cache and run tests @@ -186,7 +186,7 @@ help_frontend: ## show frontend help @echo "" @echo "Code Quality:" @echo " format_frontend - Format frontend code" - @echo "" + @echo "" @echo "Testing:" @echo " tests_frontend - Run frontend Playwright e2e tests" @echo " test_frontend - Run frontend Jest unit tests" @@ -203,4 +203,4 @@ help_frontend: ## show frontend help @echo " test_frontend_file path - Run tests for specific file" @echo " test_frontend_pattern pattern - Run tests matching pattern" @echo " test_frontend_snapshots - Update Jest snapshots" - @echo " test_frontend_config - Show Jest configuration" \ No newline at end of file + @echo " test_frontend_config - Show Jest configuration" \ No newline at end of file diff --git a/src/frontend/jest.config.js b/src/frontend/jest.config.js index 3052f488e..720b5f828 100644 --- a/src/frontend/jest.config.js +++ b/src/frontend/jest.config.js @@ -17,4 +17,39 @@ module.exports = { moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"], // Ignore node_modules except for packages that need transformation transformIgnorePatterns: ["node_modules/(?!(.*\\.mjs$|@testing-library))"], + + // Coverage configuration + collectCoverage: process.env.CI === "true", + coverageDirectory: "coverage", + coverageReporters: ["text", "lcov", "html", "json-summary"], + coveragePathIgnorePatterns: ["/node_modules/", "/tests/"], + coverageThreshold: { + global: { + branches: 50, + functions: 50, + lines: 50, + statements: 50, + }, + }, + + // CI-specific configuration + ...(process.env.CI === "true" && { + reporters: [ + "default", + [ + "jest-junit", + { + outputDirectory: "test-results", + outputName: "junit.xml", + ancestorSeparator: " › ", + uniqueOutputName: "false", + suiteNameTemplate: "{filepath}", + classNameTemplate: "{classname}", + titleTemplate: "{title}", + }, + ], + ], + maxWorkers: "50%", + verbose: true, + }), }; diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 99e941b8b..83e283285 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -115,6 +115,7 @@ "eslint": "^9.5.0", "jest": "^30.0.3", "jest-environment-jsdom": "^30.0.2", + "jest-junit": "^16.0.0", "postcss": "^8.4.38", "prettier": "^3.3.2", "prettier-plugin-organize-imports": "^3.2.4", @@ -13252,6 +13253,45 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-junit": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz", + "integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/jest-junit/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-junit/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/jest-leak-detector": { "version": "30.0.2", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.2.tgz", @@ -16684,9 +16724,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "devOptional": true, "license": "MIT", - "optional": true, - "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -22140,6 +22179,13 @@ "node": ">=8" } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true, + "license": "MIT" + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index 05790cba9..b2e5209af 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -140,6 +140,7 @@ "eslint": "^9.5.0", "jest": "^30.0.3", "jest-environment-jsdom": "^30.0.2", + "jest-junit": "^16.0.0", "postcss": "^8.4.38", "prettier": "^3.3.2", "prettier-plugin-organize-imports": "^3.2.4",