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(ui): add html coverage #3071

Merged
merged 20 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions packages/ui/client/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ declare module '@vue/runtime-core' {
export interface GlobalComponents {
CodeMirror: typeof import('./components/CodeMirror.vue')['default']
ConnectionOverlay: typeof import('./components/ConnectionOverlay.vue')['default']
Coverage: typeof import('./components/Coverage.vue')['default']
Dashboard: typeof import('./components/Dashboard.vue')['default']
DashboardEntry: typeof import('./components/dashboard/DashboardEntry.vue')['default']
DetailsPanel: typeof import('./components/DetailsPanel.vue')['default']
Expand Down
40 changes: 40 additions & 0 deletions packages/ui/client/components/Coverage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script setup lang="ts">
defineProps<{
src: string
}>()
</script>

<template>
<div h="full" flex="~ col">
<div
p="3"
h-10
flex="~ gap-2"
items-center
bg-header
border="b base"
>
<div class="i-carbon:folder-details-reference" />
<span
pl-1
font-bold
text-sm
flex-auto
ws-nowrap
overflow-hidden
truncate
>Coverage</span>
</div>
<div flex-auto py-1 bg-white>
<iframe id="vitest-ui-coverage" :src="src" />
</div>
</div>
</template>

<style>
#vitest-ui-coverage {
width: 100%;
height: calc(100vh - 42px);
border: none;
}
</style>
44 changes: 37 additions & 7 deletions packages/ui/client/components/Navigation.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
<script setup lang="ts">
import { hasFailedSnapshot } from '@vitest/ws-client'
import { currentModule, dashboardVisible, showDashboard } from '../composables/navigation'
import {
coverageEnabled,
coverageVisible,
currentModule,
dashboardVisible,
disableCoverage,
showCoverage,
showDashboard,
} from '../composables/navigation'
import { client, findById } from '../composables/client'
import type { Task } from '#types'
import type { File, Task } from '#types'
import { isDark, toggleDark } from '~/composables'
import { files, isReport, runAll } from '~/composables/client'
import { activeFileId } from '~/composables/params'
Expand All @@ -16,10 +24,21 @@ function onItemClick(task: Task) {
showDashboard(false)
}
const toggleMode = computed(() => isDark.value ? 'light' : 'dark')
async function onRunAll(files?: File[]) {
if (coverageEnabled.value) {
disableCoverage.value = true
await nextTick()
if (coverageEnabled.value) {
showDashboard(true)
await nextTick()
}
}
await runAll(files)
}
</script>

<template>
<TasksList border="r base" :tasks="files" :on-item-click="onItemClick" :group-by-type="true" @run="runAll">
<TasksList border="r base" :tasks="files" :on-item-click="onItemClick" :group-by-type="true" @run="onRunAll">
<template #header="{ filteredTests }">
<img w-6 h-6 src="/favicon.svg" alt="Vitest logo">
<span font-light text-sm flex-1>Vitest</span>
Expand All @@ -30,25 +49,36 @@ const toggleMode = computed(() => isDark.value ? 'light' : 'dark')
title="Show dashboard"
class="!animate-100ms"
animate-count-1
icon="i-carbon-dashboard"
icon="i-carbon:dashboard"
@click="showDashboard(true)"
/>
<IconButton
v-if="coverageEnabled"
v-show="!coverageVisible"
v-tooltip.bottom="'Coverage'"
:disabled="disableCoverage"
title="Show coverage"
class="!animate-100ms"
animate-count-1
icon="i-carbon:folder-details-reference"
@click="showCoverage()"
/>
<IconButton
v-if="(failedSnapshot && !isReport)"
v-tooltip.bottom="'Update all failed snapshot(s)'"
icon="i-carbon-result-old"
icon="i-carbon:result-old"
@click="updateSnapshot()"
/>
<IconButton
v-if="!isReport"
v-tooltip.bottom="filteredTests ? (filteredTests.length === 0 ? 'No test to run (clear filter)' : 'Rerun filtered') : 'Rerun all'"
:disabled="filteredTests?.length === 0"
icon="i-carbon-play"
icon="i-carbon:play"
@click="runAll(filteredTests)"
/>
<IconButton
v-tooltip.bottom="`Toggle to ${toggleMode} mode`"
icon="dark:i-carbon-moon i-carbon-sun"
icon="dark:i-carbon-moon i-carbon:sun"
@click="toggleDark()"
/>
</div>
Expand Down
11 changes: 10 additions & 1 deletion packages/ui/client/components/Suites.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
<script setup lang="ts">
import { hasFailedSnapshot } from '@vitest/ws-client'
import { coverageEnabled, disableCoverage } from '../composables/navigation'
import { client, current, isReport, runCurrent } from '~/composables/client'

const name = computed(() => current.value?.name.split(/\//g).pop())

const failedSnapshot = computed(() => current.value?.tasks && hasFailedSnapshot(current.value?.tasks))
const updateSnapshot = () => current.value && client.rpc.updateSnapshot(current.value)

async function onRunCurrent() {
if (coverageEnabled.value) {
disableCoverage.value = true
await nextTick()
}
await runCurrent()
}
</script>

<template>
Expand All @@ -25,7 +34,7 @@ const updateSnapshot = () => current.value && client.rpc.updateSnapshot(current.
v-if="!isReport"
v-tooltip.bottom="'Rerun file'"
icon="i-carbon-play"
@click="runCurrent()"
@click="onRunCurrent()"
/>
</div>
</template>
Expand Down
33 changes: 32 additions & 1 deletion packages/ui/client/composables/navigation.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,47 @@
import { client, findById } from './client'
import { client, config, findById, testRunState } from './client'
import { activeFileId } from './params'
import type { File } from '#types'

export const currentModule = ref<File>()
export const dashboardVisible = ref(true)
export const coverageVisible = ref(false)
export const disableCoverage = ref(true)
export const coverage = computed(() => config.value?.coverage)
export const coverageEnabled = computed(() => {
if (!config.value?.api?.port)
return false

const cov = coverage.value
return cov?.enabled && cov.reporter.map(([reporterName]) => reporterName).includes('html')
})
export const coverageUrl = computed(() => {
if (coverageEnabled.value) {
const url = `${window.location.protocol}//${window.location.hostname}:${config.value!.api!.port!}`
const idx = coverage.value!.reportsDirectory.lastIndexOf('/')
return `${url}/${coverage.value!.reportsDirectory.slice(idx + 1)}/index.html`
}

return undefined
})
watch(testRunState, (state) => {
disableCoverage.value = state === 'running'
}, { immediate: true })
export function initializeNavigation() {
const file = activeFileId.value
if (file && file.length > 0) {
const current = findById(file)
if (current) {
currentModule.value = current
dashboardVisible.value = false
coverageVisible.value = false
}
else {
watchOnce(
() => client.state.getFiles(),
() => {
currentModule.value = findById(file)
dashboardVisible.value = false
coverageVisible.value = false
},
)
}
Expand All @@ -29,8 +52,16 @@ export function initializeNavigation() {

export function showDashboard(show: boolean) {
dashboardVisible.value = show
coverageVisible.value = false
if (show) {
currentModule.value = undefined
activeFileId.value = ''
}
}

export function showCoverage() {
coverageVisible.value = true
dashboardVisible.value = false
currentModule.value = undefined
activeFileId.value = ''
}
3 changes: 2 additions & 1 deletion packages/ui/client/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
// @ts-expect-error missing types
import { Pane, Splitpanes } from 'splitpanes'
import { initializeNavigation } from '../composables/navigation'
import { coverageUrl, coverageVisible, initializeNavigation } from '../composables/navigation'

const dashboardVisible = initializeNavigation()
const mainSizes = reactive([33, 67])
Expand Down Expand Up @@ -39,6 +39,7 @@ const resizeMain = () => {
<Pane :size="mainSizes[1]">
<transition>
<Dashboard v-if="dashboardVisible" key="summary" />
<Coverage v-else-if="coverageVisible" key="coverage" :src="coverageUrl" />
<Splitpanes v-else key="detail" @resized="onModuleResized">
<Pane :size="detailSizes[0]">
<Suites />
Expand Down
16 changes: 14 additions & 2 deletions packages/ui/node/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { fileURLToPath } from 'url'
import { resolve } from 'pathe'
import { basename, resolve } from 'pathe'
import sirv from 'sirv'
import type { Plugin } from 'vite'

export default (base = '/__vitest__/') => {
export default (base = '/__vitest__/', resolveCoverageFolder?: () => string | undefined) => {
sheremet-va marked this conversation as resolved.
Show resolved Hide resolved
let coveragePath: string | undefined
let coverageFolder: string | undefined
return <Plugin>{
name: 'vitest:ui',
apply: 'serve',
configResolved() {
coverageFolder = resolveCoverageFolder?.() ?? undefined
coveragePath = coverageFolder ? `/${basename(coverageFolder)}/` : undefined
if (coveragePath && base === coveragePath)
throw new Error(`The base path and the coverage path cannot be the same: ${base}`)
},
async configureServer(server) {
coverageFolder && server.middlewares.use(coveragePath!, sirv(coverageFolder, {
sheremet-va marked this conversation as resolved.
Show resolved Hide resolved
single: true,
dev: true,
}))
const clientDist = resolve(fileURLToPath(import.meta.url), '../client')
server.middlewares.use(base, sirv(clientDist, {
single: true,
Expand Down
20 changes: 18 additions & 2 deletions packages/vitest/src/node/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { UserConfig as ViteConfig, Plugin as VitePlugin } from 'vite'
import { normalize, relative, resolve } from 'pathe'
import { toArray } from '@vitest/utils'
import { resolveModule } from 'local-pkg'
import { configDefaults } from '../../defaults'
import { configDefaults, coverageConfigDefaults } from '../../defaults'
import type { ResolvedConfig, UserConfig } from '../../types'
import { deepMerge, notNullish, removeUndefinedValues } from '../../utils'
import { ensurePackageInstalled } from '../pkg'
Expand All @@ -23,7 +23,23 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t

async function UIPlugin() {
await ensurePackageInstalled('@vitest/ui', getRoot())
return (await import('@vitest/ui')).default(options.uiBase)
const resolveCoverageFolder = () => {
const uiOptions = options as ResolvedConfig
const enabled = uiOptions.api?.port
&& uiOptions.coverage?.enabled
&& uiOptions.coverage.reporter.some((reporter) => {
if (typeof reporter === 'string')
return reporter === 'html'

return reporter.length && reporter.includes('html')
})

// reportsDirectory not resolved yet
return enabled
? resolve(getRoot(), uiOptions.coverage.reportsDirectory || coverageConfigDefaults.reportsDirectory)
: undefined
}
return (await import('@vitest/ui')).default(options.uiBase, resolveCoverageFolder)
}

async function BrowserPlugin() {
Expand Down
2 changes: 1 addition & 1 deletion test/core/test/__snapshots__/mocked.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`mocked function which fails on toReturnWith > just one call 1`] = `
"expected \\"spy\\" to return with: 2 at least once
Expand Down
2 changes: 1 addition & 1 deletion test/core/test/__snapshots__/nested-suite.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`a > b > c > d > e > very deep > msg 1`] = `"hi"`;
2 changes: 1 addition & 1 deletion test/core/test/__snapshots__/serialize.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`error serialize > Should skip circular references to prevent hit the call stack limit 1`] = `
{
Expand Down
2 changes: 1 addition & 1 deletion test/core/test/__snapshots__/snapshot.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`multiline 1`] = `
"
Expand Down
2 changes: 1 addition & 1 deletion test/core/test/__snapshots__/suite.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`suite name > snapshot 1`] = `
{
Expand Down
2 changes: 1 addition & 1 deletion test/core/test/moved-snapshot.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`snapshot is stored close to file 1`] = `"moved snapshot"`;