Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Istanbul Coverage only for changed Files #2385

Merged
merged 16 commits into from Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5,555 changes: 5,555 additions & 0 deletions examples/react-istanbul-coverage/package-lock.json

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions examples/react-istanbul-coverage/package.json
@@ -0,0 +1,27 @@
{
"name": "vitest-coverage",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest --coverage"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@testing-library/react": "^13.4.0",
"@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8",
"@vitejs/plugin-react": "^2.2.0",
"@vitest/coverage-istanbul": "latest",
"@vitest/ui": "latest",
"jsdom": "^20.0.2",
"vite": "latest",
"vitest": "latest"
}
}
1 change: 1 addition & 0 deletions examples/react-istanbul-coverage/public/vite.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions examples/react-istanbul-coverage/src/App.css
@@ -0,0 +1,41 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}
34 changes: 34 additions & 0 deletions examples/react-istanbul-coverage/src/App.jsx
@@ -0,0 +1,34 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import './App.css'

function App() {
const [count, setCount] = useState(0)

return (
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</div>
)
}

export default App
10 changes: 10 additions & 0 deletions examples/react-istanbul-coverage/src/App.test.jsx
@@ -0,0 +1,10 @@
import { render, screen } from "@testing-library/react";
import { expect, it } from "vitest";
import App from "./App";

it('should render heading', () => {
render(<App />)
expect(screen.getByRole('heading', {name: /Vite \+ React/}))
const codeElement = screen.getByText(/src\/App.jsx/i);
expect(codeElement).toBeDefined()
})
14 changes: 14 additions & 0 deletions examples/react-istanbul-coverage/src/Component.jsx
@@ -0,0 +1,14 @@
import { Children, useState } from "react"

export const Component = ({children}) => {
const [isExpanded, setIsExpanded] = useState(false)

const handleToggle = () => {
setIsExpanded(prev => !prev)
}

return <div>
<button onClick={handleToggle}>{isExpanded ? 'Collapse' : 'Expand'}</button>
{isExpanded && children}
</div>
}
9 changes: 9 additions & 0 deletions examples/react-istanbul-coverage/src/Component.test.jsx
@@ -0,0 +1,9 @@
import { render, screen } from "@testing-library/react";
import { expect, it } from "vitest";
import { Component } from "./Component";

it('should not render content', () => {
render(<Component>Content</Component>)
expect(screen.getByRole('button', {name: /Expand/}))
expect(screen.queryByText(/Content/)).toBeNull()
})
1 change: 1 addition & 0 deletions examples/react-istanbul-coverage/src/assets/react.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 70 additions & 0 deletions examples/react-istanbul-coverage/src/index.css
@@ -0,0 +1,70 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;

color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;

font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}

a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}

body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}

h1 {
font-size: 3.2em;
line-height: 1.1;
}

button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
10 changes: 10 additions & 0 deletions examples/react-istanbul-coverage/src/main.jsx
@@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
14 changes: 14 additions & 0 deletions examples/react-istanbul-coverage/vite.config.js
@@ -0,0 +1,14 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
test: {
coverage: {
provider: 'istanbul',
watchAll: false,
},
environment: 'jsdom',
},
})
5 changes: 5 additions & 0 deletions packages/coverage-istanbul/src/provider.ts
Expand Up @@ -105,6 +105,11 @@ export class IstanbulCoverageProvider implements CoverageProvider {
return map
}, {})

if (!this.ctx.isFirstRun && !this.ctx.config.coverage.watchAll && this.ctx.changedTests.size > 0) {
const changedTests = Array.from(this.ctx.changedTests)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changedTests cannot be used for resolving changed files, as changed tests can import all kinds of files. Instead, if we really want to include only the changed files, we'll need to see if Vite exposes names of the changed files in HMR-api, or something similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your feedback. I look into more options for this.

mergedCoverage.filter(key => key === changedTests[0].replace('.test', ''))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes that the tested file only imports files with similar names, right? So after you make change to math.test.ts, you will only see math.tsin report. You won't see random-number-generator.ts that it might import as well.

Also making changes to math.spec.ts breaks this logic as the hard-coded .test pattern is used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is correct.

}

if (this.options.all)
await this.includeUntestedFiles(mergedCoverage)

Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/defaults.ts
Expand Up @@ -37,6 +37,7 @@ const coverageConfigDefaults = {
// default extensions used by c8, plus '.vue' and '.svelte'
// see https://github.com/istanbuljs/schema/blob/master/default-extension.js
extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx', '.vue', '.svelte'],
watchAll: true,
} as ResolvedCoverageOptions

export const fakeTimersDefaults = {
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/node/core.ts
Expand Up @@ -419,7 +419,6 @@ export class Vitest {

this.snapshot.clear()
const files = Array.from(this.changedTests)
this.changedTests.clear()

if (this.coverageProvider && this.config.coverage.cleanOnRerun)
await this.coverageProvider.clean()
Expand All @@ -429,6 +428,7 @@ export class Vitest {
await this.runFiles(files)

await this.coverageProvider?.reportCoverage()
this.changedTests.clear()

if (!this.config.browser)
await this.report('onWatcherStart')
Expand Down
6 changes: 6 additions & 0 deletions packages/vitest/src/types/coverage.ts
Expand Up @@ -142,6 +142,12 @@ export interface BaseCoverageOptions {
* Whether to include all files, including the untested ones into report
*/
all?: boolean
/**
* Runs coverage for all files. If 'false', only for the changed file.
*
* @default true
*/
watchAll?: boolean
}

export interface CoverageIstanbulOptions extends BaseCoverageOptions {
Expand Down