Skip to content

Commit

Permalink
fix: improve coverage collection for unit tests (#909)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rangoo94 authored Oct 17, 2023
1 parent bd2c05d commit 68ad197
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 14 deletions.
47 changes: 47 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -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'
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ Dockerfile
**/dist
*.log

# generated
/coverage
packages/*/coverage
**/.eslintcache
11 changes: 9 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ yarn-debug.log*
yarn-error.log*
*.swp

# cache
.eslintcache
# generated
/coverage
packages/*/coverage
**/.eslintcache
42 changes: 38 additions & 4 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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('<rootDir>')) {
return pattern.replace('<rootDir>', 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('|')})$).*$`,
],
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 21 additions & 3 deletions packages/e2e-tests/pages/CreateTriggerPage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {Page} from '@playwright/test';
import {setTimeout as timeout} from 'node:timers/promises';

import type {TriggerData} from '../types';

Expand Down Expand Up @@ -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<void> {
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"]`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const ResourceTriggerSelect: FC<ResourceTriggerSelectProps> = ({...props}) => {
{testsData.length > 0 ? (
<OptGroup label="Tests">
{testsData.map(item => (
<Option key={item.name}>
<Option key={item.name} title={item.name}>
<StyledResourceOptionWrapper>
<ExecutorIcon type={getTestExecutorIcon(executors, item.type)} />
<Text className="regular middle">{item.name}</Text>
Expand All @@ -71,7 +71,7 @@ const ResourceTriggerSelect: FC<ResourceTriggerSelectProps> = ({...props}) => {
{testSuitesData.length > 0 ? (
<OptGroup label="Test Suites">
{testSuitesData.map(item => (
<Option key={item.name}>
<Option key={item.name} title={item.name}>
<StyledResourceOptionWrapper>
<TestSuitesIcon fill={Colors.slate100} />
<Text className="regular middle">{item.name}</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const TestModal: React.FC<TestModalProps> = props => {
>
<Select onChange={setTestValue} value={testValue} showSearch placeholder="Select a test..." size="middle">
{allTestsData.map(item => (
<Option value={item.name} key={item.name}>
<Option value={item.name} key={item.name} title={item.name}>
<StyledOptionWrapper>
<ExecutorIcon type={item.type} />
<Text className="regular middle">{item.name}</Text>
Expand Down

0 comments on commit 68ad197

Please sign in to comment.