Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
Something went wrong. Try again.
12 kB · 351 lines
Vue
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352<script setup lang="ts">import type { Editor, EditorFromTextArea, LineWidget, TextMarker } from 'codemirror'import type { Experimental, ExternalResult, TransformResultWithSource } from 'vitest'import type { ModuleType } from '~/composables/module-graph'import { asyncComputed, onKeyStroke } from '@vueuse/core'import { Tooltip as VueTooltip } from 'floating-vue'import { join, relative } from 'pathe'import { computed } from 'vue'import { client, config } from '~/composables/client'import { currentModule } from '~/composables/navigation'import { formatPreciseTime, formatTime, getDurationClass, getImportDurationType,} from '~/utils/task'import Badge from './Badge.vue'import CodeMirrorContainer from './CodeMirrorContainer.vue'import IconButton from './IconButton.vue'
const props = defineProps<{ id: string projectName: string type: ModuleType canUndo: boolean}>()
const emit = defineEmits<{ (e: 'close'): void (e: 'select', id: string, type: ModuleType): void (e: 'back'): void}>()
const result = asyncComputed<TransformResultWithSource | ExternalResult | undefined>(() => { if (!currentModule.value?.id) { return undefined } if (props.type === 'inline') { return client.rpc.getTransformResult(props.projectName, props.id, currentModule.value.id) } if (props.type === 'external') { return client.rpc.getExternalResult(props.id, currentModule.value.id) }})const durations = computed(() => { const importDurations = currentModule.value?.importDurations || {} return importDurations[props.id] || importDurations[join('/@fs/', props.id)] || {}})const ext = computed(() => props.id?.split(/\./g).pop() || 'js')
const source = computed(() => result.value?.source?.trim() || '')const isCached = computed(() => { if (!result.value || !('code' in result.value) || !config.value.fsModuleCache) { return undefined } const index = result.value.code.lastIndexOf('vitestCache=') return index !== -1})
const code = computed(() => { if (!result.value || !('code' in result.value)) { return null } return ( result.value.code .replace(/\/\/# sourceMappingURL=.*\n/, '') .replace(/\/\/# sourceMappingSource=.*\n/, '') .replace(/\/\/# vitestCache=.*\n?/, '') .trim() || '' )})const sourceMap = computed(() => { if (!result.value || !('map' in result.value)) { return { mappings: '', } } return { mappings: result.value?.map?.mappings ?? '', version: (result.value?.map as any)?.version, }})
const widgetElements: HTMLDivElement[] = []const markers: TextMarker[] = []const lineWidgets: LineWidget[] = []
function onMousedown(editor: Editor, e: MouseEvent) { const lineCh = editor.coordsChar({ left: e.clientX, top: e.clientY }) const markers = editor.findMarksAt(lineCh) if (markers.length !== 1) { return } const resolvedUrl = markers[0].title if (resolvedUrl) { const type = markers[0].attributes?.['data-external'] === 'true' ? 'external' : 'inline' emit('select', resolvedUrl, type) }}
function buildShadowImportsHtml(imports: Experimental.UntrackedModuleDefinitionDiagnostic[]) { const shadowImportsDiv = document.createElement('div') shadowImportsDiv.classList.add('mb-5') const root = config.value.root if (!root) { return }
imports.forEach(({ resolvedId, totalTime, external }) => { const importDiv = document.createElement('div') importDiv.append(document.createTextNode('import '))
const sourceDiv = document.createElement('span') const url = relative(root, resolvedId) sourceDiv.textContent = `"/${url}"` sourceDiv.className = 'hover:underline decoration-gray cursor-pointer select-none' importDiv.append(sourceDiv) sourceDiv.addEventListener('click', () => { emit('select', resolvedId, external ? 'external' : 'inline') })
const timeElement = document.createElement('span') timeElement.textContent = ` ${formatTime(totalTime)}` const durationClass = getDurationClass(totalTime) if (durationClass) { timeElement.classList.add(durationClass) } importDiv.append(timeElement)
shadowImportsDiv.append(importDiv) }) return shadowImportsDiv}
function createDurationDiv(duration: number) { const timeElement = document.createElement('div') timeElement.className = 'flex ml-2' timeElement.textContent = formatTime(duration) const durationClass = getDurationClass(duration) if (durationClass) { timeElement.classList.add(durationClass) } return timeElement}
function markImportDurations(codemirror: EditorFromTextArea) { lineWidgets.forEach((lw) => lw.clear()) lineWidgets.length = 0
widgetElements.forEach((el) => el.remove()) widgetElements.length = 0
markers.forEach((m) => m.clear()) markers.length = 0
if (result.value && 'modules' in result.value) { codemirror.off('mousedown', onMousedown) codemirror.on('mousedown', onMousedown)
const untrackedModules = result.value.untrackedModules
if (untrackedModules?.length) { const importDiv = buildShadowImportsHtml(untrackedModules) if (!importDiv) { return } widgetElements.push(importDiv) lineWidgets.push(codemirror.addLineWidget(0, importDiv, { above: true })) }
result.value.modules?.forEach((diagnostic) => { const start = { line: diagnostic.start.line - 1, ch: diagnostic.start.column, } const end = { line: diagnostic.end.line - 1, ch: diagnostic.end.column, } const marker = codemirror.markText(start, end, { title: diagnostic.resolvedId, attributes: { 'data-external': String(diagnostic.external === true), }, className: 'hover:underline decoration-red cursor-pointer select-none', }) markers.push(marker) const timeElement = createDurationDiv(diagnostic.totalTime + (diagnostic.transformTime || 0)) if (!untrackedModules?.length) { timeElement.classList.add('-mt-5') } widgetElements.push(timeElement) codemirror.addWidget( { line: diagnostic.end.line - 1, ch: diagnostic.end.column + 1, }, timeElement, false, ) }) }}
function goBack() { emit('back')}
onKeyStroke('Escape', () => { emit('close')})// TODO: sourcemap https://evanw.github.io/source-map-visualization/</script>
<template> <div class="w-350 max-w-screen h-full flex flex-col"> <div class="p-4 relative"> <div class="flex justify-between"> <p> <IconButton v-if="canUndo" v-tooltip.bottom="'Go Back'" icon="i-carbon-arrow-left" class="flex-inline" @click="goBack()" /> Module Info <VueTooltip class="inline cursor-help"> <Badge type="custom" class="ml-1" :style="{ backgroundColor: `var(--color-node-${type})` }" > {{ type }} </Badge> <template #popper> This is module is {{ type === 'external' ? 'externalized' : 'inlined' }}. <template v-if="type === 'external'"> It means that the module was not processed by Vite plugins, but instead was directly imported by the environment. </template> <template v-else> It means that the module was processed by Vite plugins. </template> </template> </VueTooltip> <VueTooltip v-if="isCached === true" class="inline cursor-help"> <Badge type="tip" class="ml-2"> cached </Badge> <template #popper> This module is cached on the file system under `fsModuleCachePath` ("node_modules/.vitest-cache" by default). </template> </VueTooltip> <VueTooltip v-if="isCached === false" class="inline cursor-help"> <Badge type="warning" class="ml-2"> not cached </Badge> <template #popper> <p> This module is not cached on the file system. It might be the first test run after cache invalidation or </p> <p> it was excluded manually via `experimental_defineCacheKeyGenerator`, or it cannot be cached (modules with `import.meta.glob`, for example). </p> </template> </VueTooltip> </p> <div class="mr-8 flex gap-2 items-center"> <VueTooltip v-if="durations.selfTime != null && durations.external !== true" class="inline cursor-help" > <Badge :type="getImportDurationType(durations.selfTime)"> self: {{ formatTime(durations.selfTime) }} </Badge> <template #popper> It took {{ formatPreciseTime(durations.selfTime) }} to import this module, excluding static imports. </template> </VueTooltip> <VueTooltip v-if="durations.totalTime != null" class="inline cursor-help"> <Badge :type="getImportDurationType(durations.totalTime)"> total: {{ formatTime(durations.totalTime) }} </Badge> <template #popper> It took {{ formatPreciseTime(durations.totalTime) }} to import the whole module, including static imports. </template> </VueTooltip> <VueTooltip v-if="result && 'transformTime' in result && result.transformTime" class="inline cursor-help" > <Badge :type="getImportDurationType(result.transformTime)"> transform: {{ formatTime(result.transformTime) }} </Badge> <template #popper> It took {{ formatPreciseTime(result.transformTime) }} to transform this module by Vite plugins. </template> </VueTooltip> </div> </div> <p class="op50 font-mono text-sm"> {{ id }} </p> <IconButton icon="i-carbon-close" class="absolute top-5px right-5px text-2xl" @click="emit('close')" /> </div> <div v-if="!result" class="p-5">No transform result found for this module.</div> <template v-else> <div class="grid grid-rows-[min-content_auto] overflow-hidden flex-auto" :class="{ 'cols-2': code != null }" > <div class="px-3 py-1 bg-overlay border-base border-b border-t border-r">Source</div> <div v-if="code != null" class="px-3 py-1 bg-overlay border-base border-b border-t"> Transformed </div> <CodeMirrorContainer :key="id" class="h-full" :model-value="source" read-only :options="{ lineNumbers: true }" :mode="ext" @codemirror="markImportDurations($event)" /> <CodeMirrorContainer v-if="code != null" class="h-full" :model-value="code" read-only :options="{ lineNumbers: true }" mode="js" /> </div> <div v-if="sourceMap.mappings !== ''"> <div class="px-3 py-1 bg-overlay border-base border-b border-t"> Source map (v{{ sourceMap.version }}) </div> <CodeMirrorContainer :model-value="sourceMap.mappings" read-only :options="{ lineNumbers: true }" /> </div> </template> </div></template>