Skip to content

Commit

Permalink
feat(ui): allow run individual tests/suites from the UI (#6641)
Browse files Browse the repository at this point in the history
  • Loading branch information
userquin authored Nov 18, 2024
1 parent 511b73c commit d9cc81d
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 14 deletions.
24 changes: 19 additions & 5 deletions packages/ui/client/components/explorer/ExplorerItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Task, TaskState } from '@vitest/runner'
import { hasFailedSnapshot } from '@vitest/ws-client'
import { Tooltip as VueTooltip } from 'floating-vue'
import { nextTick } from 'vue'
import { client, isReport, runFiles } from '~/composables/client'
import { client, isReport, runFiles, runTask } from '~/composables/client'
import { showSource } from '~/composables/codemirror'
import { explorerTree } from '~/composables/explorer'
import { escapeHtml, highlightRegex } from '~/composables/explorer/state'
Expand All @@ -24,6 +24,7 @@ const {
disableTaskLocation,
onItemClick,
projectNameColor,
state,
} = defineProps<{
taskId: string
name: string
Expand Down Expand Up @@ -73,7 +74,13 @@ async function onRun(task: Task) {
disableCoverage.value = true
await nextTick()
}
await runFiles([task.file])
if (type === 'file') {
await runFiles([task.file])
}
else {
await runTask(task)
}
}
function updateSnapshot(task: Task) {
Expand Down Expand Up @@ -108,6 +115,14 @@ const gridStyles = computed(() => {
} ${gridColumns.join(' ')};`
})
const runButtonTitle = computed(() => {
return type === 'file'
? 'Run current file'
: type === 'suite'
? 'Run all tests in this suite'
: 'Run current test'
})
const escapedName = computed(() => escapeHtml(name))
const highlighted = computed(() => {
const regex = highlightRegex.value
Expand Down Expand Up @@ -219,12 +234,11 @@ const projectNameTextColor = computed(() => {
</VueTooltip>
<IconButton
v-if="!isReport"
v-tooltip.bottom="'Run current test'"
v-tooltip.bottom="runButtonTitle"
data-testid="btn-run-test"
title="Run current test"
:title="runButtonTitle"
icon="i-carbon:play-filled-alt"
text-green5
:disabled="type !== 'file'"
@click.prevent.stop="onRun(task)"
/>
</div>
Expand Down
29 changes: 26 additions & 3 deletions packages/ui/client/composables/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { WebSocketStatus } from '@vueuse/core'
import type { File, SerializedConfig, TaskResultPack } from 'vitest'
import type { File, SerializedConfig, Task, TaskResultPack } from 'vitest'
import type { BrowserRunnerState } from '../../../types'
import { createFileTask } from '@vitest/runner/utils'
import { createClient, getTasks } from '@vitest/ws-client'
import { reactive as reactiveVue } from 'vue'
import { explorerTree } from '~/composables/explorer'
import { isFileNode } from '~/composables/explorer/utils'
import { isSuite as isTaskSuite } from '~/utils/task'
import { ui } from '../../composables/api'
import { ENTRY_URL, isReport } from '../../constants'
import { parseError } from '../error'
Expand Down Expand Up @@ -65,7 +66,21 @@ export const isConnecting = computed(() => status.value === 'CONNECTING')
export const isDisconnected = computed(() => status.value === 'CLOSED')

export function runAll() {
return runFiles(client.state.getFiles()/* , true */)
return runFiles(client.state.getFiles())
}

function clearTaskResult(task: Task) {
delete task.result
const node = explorerTree.nodes.get(task.id)
if (node) {
node.state = undefined
node.duration = undefined
if (isTaskSuite(task)) {
for (const t of task.tasks) {
clearTaskResult(t)
}
}
}
}

function clearResults(useFiles: File[]) {
Expand Down Expand Up @@ -98,7 +113,15 @@ export function runFiles(useFiles: File[]) {

explorerTree.startRun()

return client.rpc.rerun(useFiles.map(i => i.filepath))
return client.rpc.rerun(useFiles.map(i => i.filepath), true)
}

export function runTask(task: Task) {
clearTaskResult(task)

explorerTree.startRun()

return client.rpc.rerunTask(task.id)
}

export function runCurrent() {
Expand Down
7 changes: 5 additions & 2 deletions packages/vitest/src/api/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ export function setup(ctx: Vitest, _server?: ViteDevServer) {
}
return fs.writeFile(id, content, 'utf-8')
},
async rerun(files) {
await ctx.rerunFiles(files)
async rerun(files, resetTestNamePattern) {
await ctx.rerunFiles(files, undefined, true, resetTestNamePattern)
},
async rerunTask(id) {
await ctx.rerunTask(id)
},
getConfig() {
return ctx.getRootTestProject().serializedConfig
Expand Down
3 changes: 2 additions & 1 deletion packages/vitest/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export interface WebSocketHandlers {
) => Promise<TransformResultWithSource | undefined>
readTestFile: (id: string) => Promise<string | null>
saveTestFile: (id: string, content: string) => Promise<void>
rerun: (files: string[]) => Promise<void>
rerun: (files: string[], resetTestNamePattern?: boolean) => Promise<void>
rerunTask: (id: string) => Promise<void>
updateSnapshot: (file?: File) => Promise<void>
getUnhandledErrors: () => unknown[]
}
Expand Down
27 changes: 25 additions & 2 deletions packages/vitest/src/node/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { CancelReason, File, TaskResultPack } from '@vitest/runner'
import type { Writable } from 'node:stream'
import type { ViteDevServer } from 'vite'
import type { defineWorkspace } from 'vitest/config'
import type { RunnerTask, RunnerTestSuite } from '../public'
import type { SerializedCoverageConfig } from '../runtime/config'
import type { ArgumentsType, OnServerRestartHandler, OnTestsRerunHandler, ProvidedContext, UserConsoleLog } from '../types/general'
import type { ProcessPool, WorkspaceSpec } from './pool'
Expand Down Expand Up @@ -687,7 +688,11 @@ export class Vitest {
await Promise.all(this.projects.map(p => p._initBrowserServer()))
}

async rerunFiles(files: string[] = this.state.getFilepaths(), trigger?: string, allTestsRun = true) {
async rerunFiles(files: string[] = this.state.getFilepaths(), trigger?: string, allTestsRun = true, resetTestNamePattern = false) {
if (resetTestNamePattern) {
this.configOverride.testNamePattern = undefined
}

if (this.filenamePattern) {
const filteredFiles = await this.globTestFiles([this.filenamePattern])
files = files.filter(file => filteredFiles.some(f => f[1] === file))
Expand All @@ -702,11 +707,29 @@ export class Vitest {
await this.report('onWatcherStart', this.state.getFiles(files))
}

private isSuite(task: RunnerTask): task is RunnerTestSuite {
return Object.hasOwnProperty.call(task, 'tasks')
}

async rerunTask(id: string) {
const task = this.state.idMap.get(id)
if (!task) {
throw new Error(`Task ${id} was not found`)
}
await this.changeNamePattern(
task.name,
[task.file.filepath],
this.isSuite(task) ? 'rerun suite' : 'rerun test',
)
}

async changeProjectName(pattern: string) {
if (pattern === '') {
delete this.configOverride.project
}
else { this.configOverride.project = pattern }
else {
this.configOverride.project = pattern
}

this.projects = this.resolvedProjects.filter(p => p.getName() === pattern)
const files = (await this.globTestSpecs()).map(spec => spec.moduleId)
Expand Down
2 changes: 1 addition & 1 deletion test/ui/test/ui.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ test.describe('standalone', () => {

// run single file
await page.getByText('fixtures/sample.test.ts').hover()
await page.getByRole('button', { name: 'Run current test' }).click()
await page.getByRole('button', { name: 'Run current file' }).click()

// check results
await page.getByText('PASS (1)').click()
Expand Down

0 comments on commit d9cc81d

Please sign in to comment.