mirror of
https://github.com/hanzoai/vitest.git
synced 2026-09-24 17:04:25 +00:00
153 lines
4.7 KiB
TypeScript
153 lines
4.7 KiB
TypeScript
import type { RunnerTestFile as File, RunnerTask as Task } from 'vitest'
|
|
import type { FileTreeNode, Filter, FilteredTests, SortUIType, TreeFilterState, UITaskTreeNode } from './types'
|
|
import { useLocalStorage } from '@vueuse/core'
|
|
import { computed, reactive, ref, shallowRef } from 'vue'
|
|
import { availableProjects } from '~/composables/client'
|
|
import { caseInsensitiveMatch } from '~/utils/task'
|
|
// importing from the source because we need to bundle it
|
|
import { createTagsFilter } from '../../../../vitest/src/runtime/runner/utils/tags'
|
|
import { config } from '../client'
|
|
import { explorerTree } from './index'
|
|
|
|
export const uiFiles = shallowRef<FileTreeNode[]>([])
|
|
export const uiEntries = shallowRef<UITaskTreeNode[]>([])
|
|
export const openedTreeItems = useLocalStorage<string[]>(
|
|
'vitest-ui_task-tree-opened',
|
|
[],
|
|
{ shallow: true },
|
|
)
|
|
export const ALL_PROJECTS = '__vitest_ui_all_projects__'
|
|
export const openedTreeItemsSet = computed(() => new Set(openedTreeItems.value))
|
|
export const treeFilter = useLocalStorage<TreeFilterState>(
|
|
'vitest-ui_task-tree-filter',
|
|
{
|
|
expandAll: undefined,
|
|
failed: false,
|
|
success: false,
|
|
skipped: false,
|
|
slow: false,
|
|
onlyTests: false,
|
|
search: '',
|
|
project: ALL_PROJECTS,
|
|
projectSort: undefined,
|
|
},
|
|
)
|
|
export const projectSort = ref<SortUIType>(treeFilter.value.projectSort || 'default')
|
|
export const currentProject = shallowRef(treeFilter.value?.project || ALL_PROJECTS)
|
|
export const enableProjects = computed(() => availableProjects.value.length > 1)
|
|
export const disableClearProjects = computed(() => currentProject.value === ALL_PROJECTS)
|
|
export const currentProjectName = computed(() => {
|
|
return !enableProjects.value || currentProject.value === ALL_PROJECTS ? undefined : currentProject.value
|
|
})
|
|
export const search = ref<string>(treeFilter.value.search)
|
|
const tagExpressionsCache = new Map<string, { error?: string; matcher: (tags: string[]) => boolean }>()
|
|
export const searchMatcher = computed(() => {
|
|
if (search.value.startsWith('tag:')) {
|
|
if (!config.value.tags) { // config is not loaded yet
|
|
return { matcher: () => true }
|
|
}
|
|
const tagQuery = search.value.slice(4).trim()
|
|
let filter = tagExpressionsCache.get(tagQuery)
|
|
if (!filter) {
|
|
filter = createSafeFilter(tagQuery)
|
|
tagExpressionsCache.set(tagQuery, filter)
|
|
}
|
|
return {
|
|
matcher: (task: Task) => filter.matcher(task.tags || []),
|
|
error: filter.error,
|
|
}
|
|
}
|
|
return {
|
|
matcher: (task: Task) => search.value === '' || caseInsensitiveMatch(task.name, search.value),
|
|
}
|
|
})
|
|
|
|
function createSafeFilter(
|
|
query: string,
|
|
) {
|
|
if (!query) {
|
|
return { matcher: () => true }
|
|
}
|
|
try {
|
|
return { matcher: createTagsFilter([query], config.value.tags || []) }
|
|
}
|
|
catch (error: any) {
|
|
return { matcher: () => false, error: error.message }
|
|
}
|
|
}
|
|
const htmlEntities: Record<string, string> = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
'\'': ''',
|
|
}
|
|
export function escapeHtml(str: string) {
|
|
return str.replace(/[&<>"']/g, m => htmlEntities[m])
|
|
}
|
|
export const highlightRegex = computed(() => {
|
|
const searchString = search.value.toLowerCase()
|
|
return searchString.length ? new RegExp(`(${escapeHtml(searchString)})`, 'gi') : null
|
|
})
|
|
export const isFiltered = computed(() => search.value.trim() !== '')
|
|
export const filter = reactive<Filter>({
|
|
failed: treeFilter.value.failed,
|
|
success: treeFilter.value.success,
|
|
skipped: treeFilter.value.skipped,
|
|
slow: treeFilter.value.slow,
|
|
onlyTests: treeFilter.value.onlyTests,
|
|
})
|
|
export const isFilteredByStatus = computed(() => {
|
|
if (filter.failed) {
|
|
return true
|
|
}
|
|
|
|
if (filter.success) {
|
|
return true
|
|
}
|
|
|
|
if (filter.skipped) {
|
|
return true
|
|
}
|
|
|
|
if (filter.slow) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
})
|
|
export const filteredFiles = shallowRef<File[]>([])
|
|
export const initialized = ref(false)
|
|
export const shouldShowExpandAll = computed(() => {
|
|
const expandAll = treeFilter.value.expandAll
|
|
const opened = openedTreeItems.value
|
|
|
|
if (opened.length > 0) {
|
|
return expandAll !== true
|
|
}
|
|
|
|
return expandAll !== false
|
|
})
|
|
export const testsTotal = computed<FilteredTests>(() => {
|
|
const filtered = isFiltered.value
|
|
const filteredByStatus = isFilteredByStatus.value
|
|
const project = currentProjectName.value
|
|
const onlyTests = filter.onlyTests
|
|
const failed = explorerTree.summary.filesFailed
|
|
const success = explorerTree.summary.filesSuccess
|
|
const skipped = explorerTree.summary.filesSkipped
|
|
const running = explorerTree.summary.filesRunning
|
|
const files = filteredFiles.value
|
|
return explorerTree.collectTestsTotal(
|
|
filtered || filteredByStatus || !!project,
|
|
onlyTests,
|
|
files,
|
|
{
|
|
failed,
|
|
success,
|
|
skipped,
|
|
running,
|
|
},
|
|
)
|
|
})
|