From 68ad197c6754b31fc0f1baeacdf3678d8c4a15d9 Mon Sep 17 00:00:00 2001 From: Dawid Rusnak Date: Tue, 17 Oct 2023 10:56:55 +0200 Subject: [PATCH] fix: improve coverage collection for unit tests (#909) * fix: improve coverage collection for unit tests * feat: send Test Coverage results to the CodeClimate * fixup codeclimate-action version * chore: add CodeClimate configuration * fixup codeclimate configuration * fix: add a title for autocomplete options * fix: Triggers E2E tests * fixup adjust codeclimate configuration --- .codeclimate.yml | 47 +++++++++++++++++++ .dockerignore | 3 ++ .github/workflows/test.yml | 11 ++++- .gitignore | 6 ++- jest.config.js | 42 +++++++++++++++-- package.json | 1 + packages/e2e-tests/pages/CreateTriggerPage.ts | 24 ++++++++-- .../ResourceTriggerSelect.tsx | 4 +- .../SettingsTests/Modals/TestModal.tsx | 2 +- 9 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 .codeclimate.yml diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 000000000..b7e3f00d6 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,47 @@ +version: '2' + +checks: + method-complexity: + config: + threshold: 13 + method-lines: + config: + threshold: 100 + similar-code: + config: + threshold: 45 + identical-code: + config: + threshold: 25 + +plugins: + nodesecurity: + enabled: true + git-legal: + enabled: true + fixme: + enabled: true + config: + strings: + - FIXME + - TODO + shellcheck: + enabled: true + structure: + enabled: true + +exclude_patterns: + - '**/node_modules/' + - '**/dist/' + - '**/build/' + - '**/coverage/' + - '**/playwright-report/' + - 'packages/e2e-tests/' + - '**/spec/' + - '**/*.d.ts' + - '**/*.spec.ts' + - '**/*.spec.tsx' + - '**/.prettierrc.js' + - '**/commitlint.config.js' + - '**/jest.config.js' + - '**/craco.config.js' diff --git a/.dockerignore b/.dockerignore index dfebb65dc..b25ed0b1e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -17,4 +17,7 @@ Dockerfile **/dist *.log +# generated +/coverage +packages/*/coverage **/.eslintcache diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b574f83d..7ad1e0a8a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,5 +42,12 @@ jobs: run: npm install --legacy-peer-deps - name: Testing dashboard - if: always() - run: npm run test + run: npm run test:coverage + + - name: Send coverage to CodeClimate + uses: paambaati/codeclimate-action@v5 + env: + CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} + with: + coverageLocations: | + ${{github.workspace}}/coverage/lcov.info:lcov diff --git a/.gitignore b/.gitignore index 5c461bf46..d2816edb3 100644 --- a/.gitignore +++ b/.gitignore @@ -21,5 +21,7 @@ yarn-debug.log* yarn-error.log* *.swp -# cache -.eslintcache +# generated +/coverage +packages/*/coverage +**/.eslintcache diff --git a/jest.config.js b/jest.config.js index b956dc031..ad828dcc6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,7 @@ const {dirname, resolve, join} = require('node:path'); const {readdirSync, existsSync} = require('node:fs'); +const micromatch = require('micromatch'); +const glob = require('glob'); function readConfig(filePath) { const absFilePath = resolve(filePath); @@ -14,13 +16,45 @@ function readConfig(filePath) { return result; } +function escapeRegexWord(filePath) { + return filePath.replace(/[^a-z0-9]/gi, $ => `\\${$}`); +} + const packages = readdirSync(join(__dirname, 'packages')).filter(name => existsSync(join(__dirname, 'packages', name, 'jest.config.js')) ); +const projects = packages.map(name => ({ + ...readConfig(join(__dirname, 'packages', name, 'jest.config.js')), + rootDir: join(__dirname, 'packages', name), +})); + +const files = glob.sync(`${join(__dirname, 'packages')}/**`, {ignore: '**/node_modules/**'}); + +const coveragePatterns = projects.map(project => + (project.collectCoverageFrom || []).map(pattern => { + if (pattern.includes('')) { + return pattern.replace('', project.rootDir); + } + if (/^!?\//.test(pattern)) { + return pattern; + } + return pattern.startsWith('!') ? `!${project.rootDir}/${pattern.substring(1)}` : `${project.rootDir}/${pattern}`; + }) +); + +const coverageFiles = coveragePatterns.flatMap(patterns => + files.filter(filePath => micromatch.isMatch(filePath, patterns)) +); + module.exports = { - projects: packages.map(name => ({ - ...readConfig(join(__dirname, 'packages', name, 'jest.config.js')), - rootDir: join(__dirname, 'packages', name), - })), + projects, + // Hack to inherit coverage patterns from projects + collectCoverageFrom: ['**/*.{ts,tsx}'], + coveragePathIgnorePatterns: [ + `^(?!${escapeRegexWord(__dirname)}\\/${coverageFiles + .map(filePath => filePath.replace(`${__dirname}/`, '')) + .map(escapeRegexWord) + .join('|')})$).*$`, + ], }; diff --git a/package.json b/package.json index ebda4585c..9df02ff9f 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "start": "npm run -w @testkube/web start", "build": "npm run --workspaces --if-present build", "test": "npx jest", + "test:coverage": "npx jest --collect-coverage --coverage-reporters lcov", "test:watch": "npx jest --watch --detectOpenHandles", "test:update": "npm run -w @testkube/web test:update", "e2e": "npm run -w @testkube/e2e-tests e2e", diff --git a/packages/e2e-tests/pages/CreateTriggerPage.ts b/packages/e2e-tests/pages/CreateTriggerPage.ts index 4e2b49151..24652fdbe 100644 --- a/packages/e2e-tests/pages/CreateTriggerPage.ts +++ b/packages/e2e-tests/pages/CreateTriggerPage.ts @@ -1,4 +1,5 @@ import type {Page} from '@playwright/test'; +import {setTimeout as timeout} from 'node:timers/promises'; import type {TriggerData} from '../types'; @@ -65,12 +66,29 @@ export class CreateTriggerPage { if (testSelector.name) { await this.page.click(`xpath=//div[@data-test="triggers-add-modal-action-switch"]//div[@title="BY NAME"]`); await this.page.click(`xpath=//input[@id="add-trigger-form_testNameSelector"]`); - await this.page.click( - `xpath=//div[@class="rc-virtual-list"]//div[contains(@class,"option-content")]//span[text()="${testSelector.name}"]` - ); + await this.scrollSelectionTo(testSelector.name, 'testNameSelector'); } } + async scrollSelectionTo(value: string | number, inputName: string): Promise { + const scrollSelector = `#add-trigger-form_${inputName}_list ~ .rc-virtual-list .rc-virtual-list-holder`; + await this.page.locator(scrollSelector).waitFor(); + await timeout(100); + await this.page.evaluate(` + const container = document.querySelector(${JSON.stringify(scrollSelector)}); + const scroll = (to) => { + if (!container || to > container.scrollHeight || container.querySelector('.rc-virtual-list-holder-inner div[title="${value}"]')) { + return; + } + container.scrollTop = to; + to += container.clientHeight; + setTimeout(() => scroll(to), 50); + }; + scroll(0); + `); + await this.page.click(`#add-trigger-form_${inputName}_list ~ .rc-virtual-list div[title="${value}"]`); + } + async clickCreateButton() { await this.page.click(`xpath=//button[@data-test="webhooks-add-modal-next:second"]`); } diff --git a/packages/web/src/components/organisms/TriggersFormItems/ResourceTriggerSelect/ResourceTriggerSelect.tsx b/packages/web/src/components/organisms/TriggersFormItems/ResourceTriggerSelect/ResourceTriggerSelect.tsx index 39695c5c6..d6dd2df37 100644 --- a/packages/web/src/components/organisms/TriggersFormItems/ResourceTriggerSelect/ResourceTriggerSelect.tsx +++ b/packages/web/src/components/organisms/TriggersFormItems/ResourceTriggerSelect/ResourceTriggerSelect.tsx @@ -59,7 +59,7 @@ const ResourceTriggerSelect: FC = ({...props}) => { {testsData.length > 0 ? ( {testsData.map(item => ( -