Skip to content

Commit 45dfc95

Browse files
authored
feat(ui): replace navigation tree with test explorer (#5907)
1 parent 486fd11 commit 45dfc95

34 files changed

+2221
-733
lines changed

packages/ui/client/auto-imports.d.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,6 @@ declare global {
4646
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
4747
const effectScope: typeof import('vue')['effectScope']
4848
const extendRef: typeof import('@vueuse/core')['extendRef']
49-
const filesFailed: typeof import('./composables/summary')['filesFailed']
50-
const filesIgnore: typeof import('./composables/summary')['filesIgnore']
51-
const filesRunning: typeof import('./composables/summary')['filesRunning']
52-
const filesSkipped: typeof import('./composables/summary')['filesSkipped']
53-
const filesSnapshotFailed: typeof import('./composables/summary')['filesSnapshotFailed']
54-
const filesSuccess: typeof import('./composables/summary')['filesSuccess']
55-
const filesTodo: typeof import('./composables/summary')['filesTodo']
56-
const finished: typeof import('./composables/summary')['finished']
5749
const getCurrentBrowserIframe: typeof import('./composables/api')['getCurrentBrowserIframe']
5850
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
5951
const getCurrentScope: typeof import('vue')['getCurrentScope']
@@ -128,22 +120,14 @@ declare global {
128120
const syncRef: typeof import('@vueuse/core')['syncRef']
129121
const syncRefs: typeof import('@vueuse/core')['syncRefs']
130122
const templateRef: typeof import('@vueuse/core')['templateRef']
131-
const tests: typeof import('./composables/summary')['tests']
132-
const testsFailed: typeof import('./composables/summary')['testsFailed']
133-
const testsIgnore: typeof import('./composables/summary')['testsIgnore']
134-
const testsSkipped: typeof import('./composables/summary')['testsSkipped']
135-
const testsSuccess: typeof import('./composables/summary')['testsSuccess']
136-
const testsTodo: typeof import('./composables/summary')['testsTodo']
137123
const throttledRef: typeof import('@vueuse/core')['throttledRef']
138124
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
139-
const time: typeof import('./composables/summary')['time']
140125
const toRaw: typeof import('vue')['toRaw']
141126
const toReactive: typeof import('@vueuse/core')['toReactive']
142127
const toRef: typeof import('vue')['toRef']
143128
const toRefs: typeof import('vue')['toRefs']
144129
const toValue: typeof import('vue')['toValue']
145130
const toggleDark: typeof import('./composables/dark')['toggleDark']
146-
const totalTests: typeof import('./composables/summary')['totalTests']
147131
const triggerRef: typeof import('vue')['triggerRef']
148132
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
149133
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']

packages/ui/client/components.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ declare module 'vue' {
1515
DashboardEntry: typeof import('./components/dashboard/DashboardEntry.vue')['default']
1616
DetailsPanel: typeof import('./components/DetailsPanel.vue')['default']
1717
ErrorEntry: typeof import('./components/dashboard/ErrorEntry.vue')['default']
18+
Explorer: typeof import('./components/explorer/Explorer.vue')['default']
19+
ExplorerItem: typeof import('./components/explorer/ExplorerItem.vue')['default']
1820
FileDetails: typeof import('./components/FileDetails.vue')['default']
21+
FilterStatus: typeof import('./components/FilterStatus.vue')['default']
1922
IconAction: typeof import('./components/IconAction.vue')['default']
2023
IconButton: typeof import('./components/IconButton.vue')['default']
2124
Modal: typeof import('./components/Modal.vue')['default']
@@ -25,9 +28,6 @@ declare module 'vue' {
2528
RouterLink: typeof import('vue-router')['RouterLink']
2629
RouterView: typeof import('vue-router')['RouterView']
2730
StatusIcon: typeof import('./components/StatusIcon.vue')['default']
28-
TaskItem: typeof import('./components/TaskItem.vue')['default']
29-
TasksList: typeof import('./components/TasksList.vue')['default']
30-
TaskTree: typeof import('./components/TaskTree.vue')['default']
3131
TestFilesEntry: typeof import('./components/dashboard/TestFilesEntry.vue')['default']
3232
TestsEntry: typeof import('./components/dashboard/TestsEntry.vue')['default']
3333
TestsFilesContainer: typeof import('./components/dashboard/TestsFilesContainer.vue')['default']
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<script setup lang="ts">
2+
defineProps<{ label: string }>()
3+
const modelValue = defineModel<boolean | null>()
4+
</script>
5+
6+
<template>
7+
<label
8+
class="font-light text-sm checkbox flex items-center cursor-pointer py-1 text-sm w-full gap-y-1 mb-1px"
9+
v-bind="$attrs"
10+
@click.prevent="modelValue = !modelValue"
11+
>
12+
<span
13+
:class="[
14+
modelValue ? 'i-carbon:checkbox-checked-filled' : 'i-carbon:checkbox',
15+
]"
16+
text-lg
17+
aria-hidden="true"
18+
/>
19+
<input
20+
v-model="modelValue"
21+
type="checkbox"
22+
sr-only
23+
>
24+
<span flex-1 ms-2 select-none>{{ label }}</span>
25+
</label>
26+
</template>
27+
28+
<style>
29+
.checkbox:focus-within {
30+
outline: none;
31+
@apply focus-base border-b-1 !mb-none;
32+
}
33+
</style>

packages/ui/client/components/IconButton.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ defineProps<{
1818
class="w-1.4em h-1.4em flex" :class="[{ 'bg-gray-500:35 op100': active }]"
1919
>
2020
<slot>
21-
<div :class="icon" ma />
21+
<span :class="icon" ma block />
2222
</slot>
2323
</button>
2424
</template>
Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<script setup lang="ts">
2-
import { hasFailedSnapshot } from '@vitest/ws-client'
32
import { Tooltip as VueTooltip } from 'floating-vue'
43
import type { File, Task } from 'vitest'
54
import {
@@ -9,22 +8,20 @@ import {
98
currentModule,
109
dashboardVisible,
1110
disableCoverage,
12-
openedTreeItems,
1311
showCoverage,
1412
showDashboard,
1513
} from '~/composables/navigation'
16-
import { client, files, findById, isReport, runAll } from '~/composables/client'
14+
import { client, findById, isReport, runAll, runFiles } from '~/composables/client'
1715
import { isDark, toggleDark } from '~/composables'
1816
import { activeFileId } from '~/composables/params'
17+
import { explorerTree } from '~/composables/explorer'
18+
import { initialized, shouldShowExpandAll } from '~/composables/explorer/state'
1919
20-
const failedSnapshot = computed(
21-
() => files.value && hasFailedSnapshot(files.value),
22-
)
2320
function updateSnapshot() {
2421
return client.rpc.updateSnapshot()
2522
}
2623
27-
const toggleMode = computed(() => (isDark.value ? 'light' : 'dark'))
24+
const toggleMode = computed(() => isDark.value ? 'light' : 'dark')
2825
2926
function onItemClick(task: Task) {
3027
activeFileId.value = task.file.id
@@ -41,46 +38,42 @@ async function onRunAll(files?: File[]) {
4138
await nextTick()
4239
}
4340
}
44-
await runAll(files)
41+
if (files?.length) {
42+
await runFiles(files)
43+
}
44+
else {
45+
await runAll()
46+
}
4547
}
4648
4749
function collapseTests() {
48-
openedTreeItems.value = []
50+
explorerTree.collapseAllNodes()
4951
}
5052
5153
function expandTests() {
52-
files.value.forEach((file) => {
53-
if (!openedTreeItems.value.includes(file.id)) {
54-
openedTreeItems.value.push(file.id)
55-
}
56-
})
54+
explorerTree.expandAllNodes()
5755
}
5856
</script>
5957

6058
<template>
6159
<!-- TODO: have test tree so the folders are also nested: test -> filename -> suite -> test -->
62-
<TasksList
63-
border="r base"
64-
:tasks="files"
65-
:on-item-click="onItemClick"
66-
:group-by-type="true"
67-
:nested="true"
68-
@run="onRunAll"
69-
>
70-
<template #header="{ filteredTests }">
60+
<Explorer border="r base" :on-item-click="onItemClick" :nested="true" @run="onRunAll">
61+
<template #header="{ filteredFiles }">
7162
<img w-6 h-6 src="/favicon.svg" alt="Vitest logo">
7263
<span font-light text-sm flex-1>Vitest</span>
7364
<div class="flex text-lg">
7465
<IconButton
75-
v-show="openedTreeItems.length > 0"
66+
v-show="!shouldShowExpandAll"
7667
v-tooltip.bottom="'Collapse tests'"
7768
title="Collapse tests"
69+
:disabled="!initialized"
7870
icon="i-carbon:collapse-all"
7971
@click="collapseTests()"
8072
/>
8173
<IconButton
82-
v-show="openedTreeItems.length === 0"
74+
v-show="shouldShowExpandAll"
8375
v-tooltip.bottom="'Expand tests'"
76+
:disabled="!initialized"
8477
title="Expand tests"
8578
icon="i-carbon:expand-all"
8679
@click="expandTests()"
@@ -101,10 +94,7 @@ function expandTests() {
10194
>
10295
<div class="i-carbon:folder-off ma" />
10396
<template #popper>
104-
<div
105-
class="op100 gap-1 p-y-1"
106-
grid="~ items-center cols-[1.5em_1fr]"
107-
>
97+
<div class="op100 gap-1 p-y-1" grid="~ items-center cols-[1.5em_1fr]">
10898
<div class="i-carbon:information-square w-1.5em h-1.5em" />
10999
<div>Coverage enabled but missing html reporter.</div>
110100
<div style="grid-column: 2">
@@ -125,23 +115,18 @@ function expandTests() {
125115
@click="showCoverage()"
126116
/>
127117
<IconButton
128-
v-if="failedSnapshot && !isReport"
118+
v-if="(explorerTree.summary.failedSnapshot && !isReport)"
129119
v-tooltip.bottom="'Update all failed snapshot(s)'"
130120
icon="i-carbon:result-old"
131-
@click="updateSnapshot()"
121+
:disabled="!explorerTree.summary.failedSnapshotEnabled"
122+
@click="explorerTree.summary.failedSnapshotEnabled && updateSnapshot()"
132123
/>
133124
<IconButton
134125
v-if="!isReport"
135-
v-tooltip.bottom="
136-
filteredTests
137-
? filteredTests.length === 0
138-
? 'No test to run (clear filter)'
139-
: 'Rerun filtered'
140-
: 'Rerun all'
141-
"
142-
:disabled="filteredTests?.length === 0"
126+
v-tooltip.bottom="filteredFiles ? (filteredFiles.length === 0 ? 'No test to run (clear filter)' : 'Rerun filtered') : 'Rerun all'"
127+
:disabled="filteredFiles?.length === 0"
143128
icon="i-carbon:play"
144-
@click="onRunAll(filteredTests)"
129+
@click="onRunAll(filteredFiles)"
145130
/>
146131
<IconButton
147132
v-tooltip.bottom="`Toggle to ${toggleMode} mode`"
@@ -150,5 +135,5 @@ function expandTests() {
150135
/>
151136
</div>
152137
</template>
153-
</TasksList>
138+
</Explorer>
154139
</template>

packages/ui/client/components/ProgressBar.vue

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<script setup lang="ts">
2-
import { files } from '~/composables/client'
3-
import { filesFailed, filesSuccess, finished } from '~/composables/summary'
2+
import { explorerTree } from '~/composables/explorer'
3+
import { finished } from '~/composables/client/state'
44
55
const { width } = useWindowSize()
66
const classes = computed(() => {
7-
// if there is no files, then in progress and gray
8-
if (files.value.length === 0) {
7+
// if there are no files, then in progress and gray
8+
if (explorerTree.summary.files === 0) {
99
return '!bg-gray-4 !dark:bg-gray-7 in-progress'
1010
}
1111
else if (!finished.value) {
@@ -14,25 +14,22 @@ const classes = computed(() => {
1414
1515
return null
1616
})
17-
const total = computed(() => files.value.length)
18-
const pass = computed(() => filesSuccess.value.length)
19-
const failed = computed(() => filesFailed.value.length)
2017
2118
const widthPass = computed(() => {
22-
const t = unref(total)
23-
return t > 0 ? (width.value * pass.value) / t : 0
19+
const t = explorerTree.summary.files
20+
return t > 0 ? (width.value * explorerTree.summary.filesSuccess / t) : 0
2421
})
2522
const widthFailed = computed(() => {
26-
const t = unref(total)
27-
return t > 0 ? (width.value * failed.value) / t : 0
23+
const t = explorerTree.summary.files
24+
return t > 0 ? (width.value * explorerTree.summary.filesFailed / t) : 0
2825
})
2926
const pending = computed(() => {
30-
const t = unref(total)
31-
return t - failed.value - pass.value
27+
const t = explorerTree.summary.files
28+
return t - explorerTree.summary.filesFailed - explorerTree.summary.filesSuccess
3229
})
3330
const widthPending = computed(() => {
34-
const t = unref(total)
35-
return t > 0 ? (width.value * pending.value) / t : 0
31+
const t = explorerTree.summary.files
32+
return t > 0 ? (width.value * pending.value / t) : 0
3633
})
3734
</script>
3835

0 commit comments

Comments
 (0)