Skip to content

Commit d9cc81d

Browse files
authoredNov 18, 2024
feat(ui): allow run individual tests/suites from the UI (#6641)
1 parent 511b73c commit d9cc81d

File tree

6 files changed

+78
-14
lines changed

6 files changed

+78
-14
lines changed
 

‎packages/ui/client/components/explorer/ExplorerItem.vue

+19-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Task, TaskState } from '@vitest/runner'
33
import { hasFailedSnapshot } from '@vitest/ws-client'
44
import { Tooltip as VueTooltip } from 'floating-vue'
55
import { nextTick } from 'vue'
6-
import { client, isReport, runFiles } from '~/composables/client'
6+
import { client, isReport, runFiles, runTask } from '~/composables/client'
77
import { showSource } from '~/composables/codemirror'
88
import { explorerTree } from '~/composables/explorer'
99
import { escapeHtml, highlightRegex } from '~/composables/explorer/state'
@@ -24,6 +24,7 @@ const {
2424
disableTaskLocation,
2525
onItemClick,
2626
projectNameColor,
27+
state,
2728
} = defineProps<{
2829
taskId: string
2930
name: string
@@ -73,7 +74,13 @@ async function onRun(task: Task) {
7374
disableCoverage.value = true
7475
await nextTick()
7576
}
76-
await runFiles([task.file])
77+
78+
if (type === 'file') {
79+
await runFiles([task.file])
80+
}
81+
else {
82+
await runTask(task)
83+
}
7784
}
7885
7986
function updateSnapshot(task: Task) {
@@ -108,6 +115,14 @@ const gridStyles = computed(() => {
108115
} ${gridColumns.join(' ')};`
109116
})
110117
118+
const runButtonTitle = computed(() => {
119+
return type === 'file'
120+
? 'Run current file'
121+
: type === 'suite'
122+
? 'Run all tests in this suite'
123+
: 'Run current test'
124+
})
125+
111126
const escapedName = computed(() => escapeHtml(name))
112127
const highlighted = computed(() => {
113128
const regex = highlightRegex.value
@@ -219,12 +234,11 @@ const projectNameTextColor = computed(() => {
219234
</VueTooltip>
220235
<IconButton
221236
v-if="!isReport"
222-
v-tooltip.bottom="'Run current test'"
237+
v-tooltip.bottom="runButtonTitle"
223238
data-testid="btn-run-test"
224-
title="Run current test"
239+
:title="runButtonTitle"
225240
icon="i-carbon:play-filled-alt"
226241
text-green5
227-
:disabled="type !== 'file'"
228242
@click.prevent.stop="onRun(task)"
229243
/>
230244
</div>

‎packages/ui/client/composables/client/index.ts

+26-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import type { WebSocketStatus } from '@vueuse/core'
2-
import type { File, SerializedConfig, TaskResultPack } from 'vitest'
2+
import type { File, SerializedConfig, Task, TaskResultPack } from 'vitest'
33
import type { BrowserRunnerState } from '../../../types'
44
import { createFileTask } from '@vitest/runner/utils'
55
import { createClient, getTasks } from '@vitest/ws-client'
66
import { reactive as reactiveVue } from 'vue'
77
import { explorerTree } from '~/composables/explorer'
88
import { isFileNode } from '~/composables/explorer/utils'
9+
import { isSuite as isTaskSuite } from '~/utils/task'
910
import { ui } from '../../composables/api'
1011
import { ENTRY_URL, isReport } from '../../constants'
1112
import { parseError } from '../error'
@@ -65,7 +66,21 @@ export const isConnecting = computed(() => status.value === 'CONNECTING')
6566
export const isDisconnected = computed(() => status.value === 'CLOSED')
6667

6768
export function runAll() {
68-
return runFiles(client.state.getFiles()/* , true */)
69+
return runFiles(client.state.getFiles())
70+
}
71+
72+
function clearTaskResult(task: Task) {
73+
delete task.result
74+
const node = explorerTree.nodes.get(task.id)
75+
if (node) {
76+
node.state = undefined
77+
node.duration = undefined
78+
if (isTaskSuite(task)) {
79+
for (const t of task.tasks) {
80+
clearTaskResult(t)
81+
}
82+
}
83+
}
6984
}
7085

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

99114
explorerTree.startRun()
100115

101-
return client.rpc.rerun(useFiles.map(i => i.filepath))
116+
return client.rpc.rerun(useFiles.map(i => i.filepath), true)
117+
}
118+
119+
export function runTask(task: Task) {
120+
clearTaskResult(task)
121+
122+
explorerTree.startRun()
123+
124+
return client.rpc.rerunTask(task.id)
102125
}
103126

104127
export function runCurrent() {

‎packages/vitest/src/api/setup.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,11 @@ export function setup(ctx: Vitest, _server?: ViteDevServer) {
7272
}
7373
return fs.writeFile(id, content, 'utf-8')
7474
},
75-
async rerun(files) {
76-
await ctx.rerunFiles(files)
75+
async rerun(files, resetTestNamePattern) {
76+
await ctx.rerunFiles(files, undefined, true, resetTestNamePattern)
77+
},
78+
async rerunTask(id) {
79+
await ctx.rerunTask(id)
7780
},
7881
getConfig() {
7982
return ctx.getRootTestProject().serializedConfig

‎packages/vitest/src/api/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ export interface WebSocketHandlers {
4444
) => Promise<TransformResultWithSource | undefined>
4545
readTestFile: (id: string) => Promise<string | null>
4646
saveTestFile: (id: string, content: string) => Promise<void>
47-
rerun: (files: string[]) => Promise<void>
47+
rerun: (files: string[], resetTestNamePattern?: boolean) => Promise<void>
48+
rerunTask: (id: string) => Promise<void>
4849
updateSnapshot: (file?: File) => Promise<void>
4950
getUnhandledErrors: () => unknown[]
5051
}

‎packages/vitest/src/node/core.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { CancelReason, File, TaskResultPack } from '@vitest/runner'
22
import type { Writable } from 'node:stream'
33
import type { ViteDevServer } from 'vite'
44
import type { defineWorkspace } from 'vitest/config'
5+
import type { RunnerTask, RunnerTestSuite } from '../public'
56
import type { SerializedCoverageConfig } from '../runtime/config'
67
import type { ArgumentsType, OnServerRestartHandler, OnTestsRerunHandler, ProvidedContext, UserConsoleLog } from '../types/general'
78
import type { ProcessPool, WorkspaceSpec } from './pool'
@@ -687,7 +688,11 @@ export class Vitest {
687688
await Promise.all(this.projects.map(p => p._initBrowserServer()))
688689
}
689690

690-
async rerunFiles(files: string[] = this.state.getFilepaths(), trigger?: string, allTestsRun = true) {
691+
async rerunFiles(files: string[] = this.state.getFilepaths(), trigger?: string, allTestsRun = true, resetTestNamePattern = false) {
692+
if (resetTestNamePattern) {
693+
this.configOverride.testNamePattern = undefined
694+
}
695+
691696
if (this.filenamePattern) {
692697
const filteredFiles = await this.globTestFiles([this.filenamePattern])
693698
files = files.filter(file => filteredFiles.some(f => f[1] === file))
@@ -702,11 +707,29 @@ export class Vitest {
702707
await this.report('onWatcherStart', this.state.getFiles(files))
703708
}
704709

710+
private isSuite(task: RunnerTask): task is RunnerTestSuite {
711+
return Object.hasOwnProperty.call(task, 'tasks')
712+
}
713+
714+
async rerunTask(id: string) {
715+
const task = this.state.idMap.get(id)
716+
if (!task) {
717+
throw new Error(`Task ${id} was not found`)
718+
}
719+
await this.changeNamePattern(
720+
task.name,
721+
[task.file.filepath],
722+
this.isSuite(task) ? 'rerun suite' : 'rerun test',
723+
)
724+
}
725+
705726
async changeProjectName(pattern: string) {
706727
if (pattern === '') {
707728
delete this.configOverride.project
708729
}
709-
else { this.configOverride.project = pattern }
730+
else {
731+
this.configOverride.project = pattern
732+
}
710733

711734
this.projects = this.resolvedProjects.filter(p => p.getName() === pattern)
712735
const files = (await this.globTestSpecs()).map(spec => spec.moduleId)

‎test/ui/test/ui.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ test.describe('standalone', () => {
172172

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

177177
// check results
178178
await page.getByText('PASS (1)').click()

0 commit comments

Comments
 (0)
Please sign in to comment.