[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

refactor(ui): split module graph details view (#10385)

authored by

Hiroshi Ogawa and committed by
GitHub
(May 21, 2026, 9:17 AM +0200) ff32d8f2 c22cfb65

+68 -115
+14 -115
packages/ui/client/components/FileDetails.vue
··· 1 1 <script setup lang="ts"> 2 2 import type { RunnerTask, RunnerTestCase } from 'vitest' 3 - import type { ModuleGraph } from '~/composables/module-graph' 4 3 import type { Params } from '~/composables/params' 5 - import { debouncedWatch } from '@vueuse/core' 6 - import { computed, nextTick, ref } from 'vue' 4 + import { computed, ref } from 'vue' 7 5 import DetailsHeaderButtons from '~/components/DetailsHeaderButtons.vue' 8 6 import { 9 7 browserState, ··· 15 13 import { tagsDefinitions } from '~/composables/client/state' 16 14 import { explorerTree } from '~/composables/explorer' 17 15 import { hasFailedSnapshot } from '~/composables/explorer/collector' 18 - import { getModuleGraph } from '~/composables/module-graph' 19 16 import { selectedTest, viewMode } from '~/composables/params' 20 17 import { getBadgeNameColor, getBadgeTextColor } from '~/utils/task' 18 + import FileDetailsModuleGraph from './FileDetailsModuleGraph.vue' 21 19 import IconButton from './IconButton.vue' 22 20 import StatusIcon from './StatusIcon.vue' 23 21 import ViewConsoleOutput from './views/ViewConsoleOutput.vue' 24 22 import ViewEditor from './views/ViewEditor.vue' 25 - import ViewModuleGraph from './views/ViewModuleGraph.vue' 26 23 import ViewReport from './views/ViewReport.vue' 27 24 import ViewTestReport from './views/ViewTestReport.vue' 28 25 29 - const graph = ref<ModuleGraph>({ nodes: [], links: [] }) 30 26 const draft = ref(false) 31 - const hasGraphBeenDisplayed = ref(false) 32 - const loadingModuleGraph = ref(false) 33 - const currentFilepath = ref<string | undefined>(undefined) 34 - const hideNodeModules = ref(true) 35 27 36 28 const test = computed(() => { 37 29 return selectedTest.value 38 30 ? client.state.idMap.get(selectedTest.value) as RunnerTestCase 39 31 : undefined 40 - }) 41 - 42 - const graphData = computed(() => { 43 - const c = current.value 44 - if (!c || !c.filepath) { 45 - return 46 - } 47 - 48 - return { 49 - filepath: c.filepath, 50 - projectName: c.file.projectName || '', 51 - } 52 32 }) 53 33 54 34 const failedSnapshot = computed(() => { ··· 69 49 } 70 50 71 51 function changeViewMode(view: Params['view']) { 72 - if (view === 'graph') { 73 - hasGraphBeenDisplayed.value = true 74 - } 75 - 76 52 viewMode.value = view 77 53 } 78 54 const consoleCount = computed(() => { ··· 83 59 draft.value = value 84 60 } 85 61 86 - const nodeModuleRegex = /[/\\]node_modules[/\\]/ 87 - 88 - async function loadModuleGraph(force = false) { 89 - if ( 90 - loadingModuleGraph.value 91 - || (graphData.value?.filepath === currentFilepath.value && !force) 92 - ) { 93 - return 94 - } 95 - 96 - loadingModuleGraph.value = true 97 - 98 - await nextTick() 99 - 100 - try { 101 - const gd = graphData.value 102 - if (!gd) { 103 - loadingModuleGraph.value = false 104 - return 105 - } 106 - 107 - if ( 108 - force 109 - || !currentFilepath.value 110 - || gd.filepath !== currentFilepath.value 111 - || (!graph.value.nodes.length && !graph.value.links.length) 112 - ) { 113 - let moduleGraph = await client.rpc.getModuleGraph( 114 - gd.projectName, 115 - gd.filepath, 116 - ) 117 - // remove node_modules from the graph when enabled 118 - if (hideNodeModules.value) { 119 - moduleGraph = { 120 - ...moduleGraph, 121 - inlined: moduleGraph.inlined.filter( 122 - n => !nodeModuleRegex.test(n), 123 - ), 124 - externalized: moduleGraph.externalized.filter( 125 - n => !nodeModuleRegex.test(n), 126 - ), 127 - } 128 - } 129 - graph.value = getModuleGraph( 130 - moduleGraph, 131 - gd.filepath, 132 - ) 133 - currentFilepath.value = gd.filepath 134 - } 135 - changeViewMode('graph') 136 - } 137 - finally { 138 - await new Promise(resolve => setTimeout(resolve, 100)) 139 - loadingModuleGraph.value = false 140 - } 141 - } 142 - 143 - debouncedWatch( 144 - () => [graphData.value, viewMode.value, hideNodeModules.value] as const, 145 - ([, vm, hide], old) => { 146 - if (vm === 'graph') { 147 - // only force reload when hide is changed 148 - loadModuleGraph(old && hide !== old[2]) 149 - } 150 - }, 151 - { debounce: 100, immediate: true }, 152 - ) 153 - 62 + const projectName = computed(() => current.value?.file.projectName || '') 154 63 const projectNameColor = computed(() => { 155 - const projectName = current.value?.file.projectName || '' 156 - return explorerTree.colors.get(projectName) || getBadgeNameColor(current.value?.file.projectName) 64 + const projectNameValue = projectName.value 65 + return explorerTree.colors.get(projectNameValue) || getBadgeNameColor(projectNameValue) 157 66 }) 158 67 159 68 const projectNameTextColor = computed(() => getBadgeTextColor(projectNameColor.value)) ··· 260 169 :class="{ 'tab-button-active': viewMode === 'graph' }" 261 170 @click="changeViewMode('graph')" 262 171 > 263 - <span 264 - v-if="loadingModuleGraph" 265 - class="block w-1.4em h-1.4em i-carbon:circle-dash animate-spin animate-2s" 266 - /> 267 - <span 268 - v-else 269 - class="block w-1.4em h-1.4em i-carbon:chart-relationship" 270 - /> 172 + <span class="block w-1.4em h-1.4em i-carbon:chart-relationship" /> 271 173 Module Graph 272 174 </button> 273 175 <button ··· 297 199 </div> 298 200 299 201 <div flex flex-col flex-1 overflow="hidden"> 300 - <div v-if="hasGraphBeenDisplayed" :flex-1="viewMode === 'graph' && ''"> 301 - <ViewModuleGraph 302 - v-show="viewMode === 'graph' && !loadingModuleGraph" 303 - v-model="hideNodeModules" 304 - :graph="graph" 305 - data-testid="graph" 306 - :project-name="current.file.projectName || ''" 307 - /> 308 - </div> 202 + <FileDetailsModuleGraph 203 + v-if="viewMode === 'graph'" 204 + :key="`graph:${current.id}`" 205 + :file="current" 206 + :project-name="projectName" 207 + /> 309 208 <ViewEditor 310 - v-if="viewMode === 'editor'" 311 - :key="current.id" 209 + v-else-if="viewMode === 'editor'" 210 + :key="`editor:${current.id}`" 312 211 :file="current" 313 212 data-testid="editor" 314 213 @draft="onDraft"
+54
packages/ui/client/components/FileDetailsModuleGraph.vue
··· 1 + <script setup lang="ts"> 2 + import type { RunnerTestFile } from 'vitest' 3 + import { useAsyncState } from '@vueuse/core' 4 + import { computed, ref } from 'vue' 5 + import { client } from '~/composables/client' 6 + import { getModuleGraph } from '~/composables/module-graph' 7 + import ViewModuleGraph from './views/ViewModuleGraph.vue' 8 + 9 + const props = defineProps<{ 10 + file: RunnerTestFile 11 + projectName: string 12 + }>() 13 + 14 + const hideNodeModules = ref(true) 15 + const NODE_MODULES_RE = /[/\\]node_modules[/\\]/ 16 + 17 + const { state: graphData, isLoading } = useAsyncState( 18 + () => client.rpc.getModuleGraph( 19 + props.projectName, 20 + props.file.filepath, 21 + ), 22 + undefined, 23 + ) 24 + 25 + const graph = computed(() => { 26 + if (!graphData.value) { 27 + return { nodes: [], links: [] } 28 + } 29 + let moduleGraph = graphData.value 30 + if (hideNodeModules.value) { 31 + moduleGraph = { 32 + ...moduleGraph, 33 + inlined: moduleGraph.inlined.filter(n => !NODE_MODULES_RE.test(n)), 34 + externalized: moduleGraph.externalized.filter(n => !NODE_MODULES_RE.test(n)), 35 + } 36 + } 37 + return getModuleGraph(moduleGraph, props.file.filepath) 38 + }) 39 + </script> 40 + 41 + <template> 42 + <div flex-1 overflow-hidden> 43 + <div v-if="isLoading" h-full flex items-center justify-center op-70> 44 + Loading module graph... 45 + </div> 46 + <ViewModuleGraph 47 + v-else 48 + v-model="hideNodeModules" 49 + data-testid="graph" 50 + :graph="graph" 51 + :project-name="props.projectName" 52 + /> 53 + </div> 54 + </template>