Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0e6ac18
fix(ui): expand file matches during search
hi-ogawa Sep 14, 2026
c7b0d34
docs(ui): clarify explorer state
hi-ogawa Sep 14, 2026
52c14f3
docs(ui): explain expanded entry collection
hi-ogawa Sep 14, 2026
197c5e7
docs(ui): clarify expanded entry rebuild
hi-ogawa Sep 14, 2026
bb02b38
docs(ui): describe expanded entry emission
hi-ogawa Sep 14, 2026
1351d11
docs(ui): note expanded entry refactor
hi-ogawa Sep 14, 2026
f2587f3
docs(ui): explain filter traversal state
hi-ogawa Sep 14, 2026
daef937
docs(ui): move expanded entry todo
hi-ogawa Sep 14, 2026
065a112
docs(ui): note filter state ownership
hi-ogawa Sep 14, 2026
08f0f3a
refactor(ui): simplify explorer filtering
hi-ogawa Sep 14, 2026
394dc13
refactor(ui): pass filter context explicitly
hi-ogawa Sep 14, 2026
8cfccab
refactor(ui): replace explorer generators
hi-ogawa Sep 14, 2026
a0b705f
refactor(ui): share explorer subtree updates
hi-ogawa Sep 14, 2026
0ec87ca
refactor(ui): remove explorer generators
hi-ogawa Sep 14, 2026
ae4388e
refactor(ui): clarify test accumulator
hi-ogawa Sep 14, 2026
342ae4a
refactor(ui): clarify node accumulator
hi-ogawa Sep 14, 2026
aecfe76
Merge branch 'main' into ui-filter-arrays
hi-ogawa Sep 15, 2026
fdb76e0
refactor(ui): clarify visit results
hi-ogawa Sep 15, 2026
d0435da
docs(ui): preserve expanded entry todo
hi-ogawa Sep 15, 2026
937b08e
Merge branch 'ui-filter-arrays' into ui-explorer-projection
hi-ogawa Sep 15, 2026
72c6887
Merge upstream/main into ui-explorer-projection
hi-ogawa Sep 29, 2026
53bdaa0
Merge upstream/main into ui-explorer-projection
hi-ogawa Oct 2, 2026
633ab4d
refactor(ui): remove unused explorer filter exports
hi-ogawa Oct 2, 2026
c6766ab
fix(ui): inherit suite matches when expanding a node
hi-ogawa Oct 2, 2026
6412c04
docs(ui): document replaceSubtreeEntries
hi-ogawa Oct 2, 2026
839447a
refactor(ui): replace explorer subtree by descendant ids
hi-ogawa Oct 2, 2026
897dc4c
refactor(ui): move replaceSubtreeEntries to explorer utils
hi-ogawa Oct 2, 2026
880aa77
refactor(ui): include the root row in filtered subtrees
hi-ogawa Oct 2, 2026
a7afdf8
refactor(ui): drop the expand fallback for filtered-out nodes
hi-ogawa Oct 2, 2026
af23561
refactor(ui): unexport FilterNodeContext
hi-ogawa Oct 2, 2026
0e40366
refactor(ui): handle onlyTests in matchesNode
hi-ogawa Oct 2, 2026
2f36b6a
docs(ui): document explorer filter functions
hi-ogawa Oct 2, 2026
2fc9a86
docs(ui): drop redundant runFilter params
hi-ogawa Oct 2, 2026
58b7ab8
refactor(ui): pass root tasks to getSortedRootTasks explicitly
hi-ogawa Oct 2, 2026
b4b6ea5
refactor(ui): annotate explorer filter return types
hi-ogawa Oct 2, 2026
ecb2ac7
refactor(ui): rename filter context tasks to taskIdMap
hi-ogawa Oct 2, 2026
fee04c4
refactor(ui): move expanded writes out of filterTreeNode
hi-ogawa Oct 2, 2026
f0be12e
refactor(ui): reuse runFilter in runExpandAll
hi-ogawa Oct 2, 2026
49f9bc4
refactor(ui): inline filterAll into runFilter
hi-ogawa Oct 2, 2026
0dfffba
test(ui): cover nested tests for suite search matches
hi-ogawa Oct 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 3 additions & 38 deletions packages/ui/client/composables/explorer/collapse.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { UITaskTreeNode } from '~/composables/explorer/types'
import { explorerTree } from '~/composables/explorer/index'
import { openedTreeItems, treeFilter, uiEntries } from '~/composables/explorer/state'
import { isFileNode, isParentNode } from '~/composables/explorer/utils'
import { isFileNode, isParentNode, replaceSubtreeEntries } from '~/composables/explorer/utils'

/**
* Collapse all nodes: all children collapsed.
Expand All @@ -24,9 +24,10 @@ export function runCollapseNode(id: string) {
return
}

node.expanded = false
const treeItems = new Set(openedTreeItems.value)
treeItems.delete(node.id)
const entries = [...collectCollapseNode(node)]
const entries = replaceSubtreeEntries(uiEntries.value, node, [node])
openedTreeItems.value = Array.from(treeItems)
// Keep expandAll state as it is: collapsing individual shouldn't prevent collapsing all the nodes ("collapse all" button)
// There is a watcher on composable search.ts to reset to undefined expandAll if there are no opened items
Expand Down Expand Up @@ -68,39 +69,3 @@ function collapseAllNodes(nodes: UITaskTreeNode[]) {
}
}
}

function* collectChildNodes(node: UITaskTreeNode, itself: boolean): Generator<string> {
if (itself) {
yield node.id
}

if (isParentNode(node)) {
for (let i = 0; i < node.tasks.length; i++) {
yield* collectChildNodes(node.tasks[i], true)
}
}
}

function* collectCollapseNode(node: UITaskTreeNode) {
const id = node.id
// collect children to remove from the list
const childNodes = new Set<string>(collectChildNodes(node, false))
for (let i = 0; i < uiEntries.value.length; i++) {
const child = uiEntries.value[i]
// collapse current node and return it
if (child.id === id) {
child.expanded = false
yield child
continue
}

// remove children from the list
if (childNodes.has(child.id)) {
childNodes.delete(child.id)
continue
}

// return the node
yield child
}
}
18 changes: 8 additions & 10 deletions packages/ui/client/composables/explorer/collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type {
SearchMatcher,
} from '~/composables/explorer/types'
import { toArray } from '@vitest/utils/helpers'
import { client, findById } from '~/composables/client'
import { client, config, findById } from '~/composables/client'
import { testRunState } from '~/composables/client/state'
import { expandNodesOnEndRun } from '~/composables/explorer/expand'
import { runFilter, testMatcher } from '~/composables/explorer/filter'
Expand Down Expand Up @@ -128,14 +128,10 @@ export function runCollect(
doRunFilter(search, filter, end)
}

function* collectRunningTodoTests() {
yield* uiEntries.value.filter(isRunningTestNode)
}

function updateRunningTodoTests() {
const idMap = client.state.idMap
let task: Task | undefined
for (const test of collectRunningTodoTests()) {
for (const test of uiEntries.value.filter(isRunningTestNode)) {
// lookup the parent
task = idMap.get(test.parentId)
if (task && isSuite(task) && task.mode === 'todo') {
Expand Down Expand Up @@ -372,7 +368,7 @@ function collectTests(file: File, search: SearchMatcher = () => true, filter?: F
} satisfies CollectFilteredTests

for (const t of testsCollector(file)) {
if (!filter || testMatcher(t, search, filter)) {
if (!filter || testMatcher(t, search, filter, config.value.slowTestThreshold)) {
data.total++
if (isSlowTestTask(t)) {
data.slow++
Expand Down Expand Up @@ -448,15 +444,17 @@ export function collectTestsTotalData(
return filesSummary
}

function* testsCollector(suite: Arrayable<Task>): Generator<Test> {
function testsCollector(suite: Arrayable<Task>, collectedTests: Test[] = []) {
const arraySuites = toArray(suite)
let s: Task
for (let i = 0; i < arraySuites.length; i++) {
s = arraySuites[i]
if (s.type === 'test') {
yield s
collectedTests.push(s)
} else {
yield* testsCollector(s.tasks)
testsCollector(s.tasks, collectedTests)
}
}

return collectedTests
}
52 changes: 13 additions & 39 deletions packages/ui/client/composables/explorer/expand.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { Filter, SearchMatcher, UITaskTreeNode } from '~/composables/explorer/types'
import { findById } from '~/composables/client'
import { filterAll, filterNode } from '~/composables/explorer/filter'
import { client, config } from '~/composables/client'
import { filterNode, runFilter } from '~/composables/explorer/filter'
import { explorerTree } from '~/composables/explorer/index'
import { filteredFiles, openedTreeItems, treeFilter, uiEntries } from '~/composables/explorer/state'
import { openedTreeItems, treeFilter, uiEntries } from '~/composables/explorer/state'
import {
createOrUpdateNode,
createOrUpdateSuiteTask,
isFileNode,
isParentNode,
replaceSubtreeEntries,
} from '~/composables/explorer/utils'

/**
Expand Down Expand Up @@ -46,11 +47,15 @@ export function runExpandNode(id: string, search: SearchMatcher, filter: Filter)

const treeItems = new Set(openedTreeItems.value)
treeItems.add(node.id)
// collect children
// the first node is itself only when it is a file
const children = new Set(filterNode(node, search, filter))
const subtree = filterNode(node, {
nodes: explorerTree.nodes,
taskIdMap: client.state.idMap,
search,
filter,
slowTestThreshold: config.value.slowTestThreshold,
})

const entries = [...collectExpandedNode(node, children)]
const entries = replaceSubtreeEntries(uiEntries.value, node, subtree)
openedTreeItems.value = Array.from(treeItems)
// Keep expandAll state as it is: expanding individual shouldn't prevent expanding all the nodes ("expand all" button)
// There is a watcher on composable search.ts to reset to undefined expandAll if there are no opened items
Expand Down Expand Up @@ -81,11 +86,9 @@ export function runExpandNode(id: string, search: SearchMatcher, filter: Filter)
*/
export function runExpandAll(search: SearchMatcher, filter: Filter) {
expandAllNodes(explorerTree.root.tasks, false)
const entries = [...filterAll(search, filter)]
treeFilter.value.expandAll = false
openedTreeItems.value = []
uiEntries.value = entries
filteredFiles.value = entries.filter(isFileNode).map((f) => findById(f.id)!)
runFilter(search, filter)
}

export function expandNodesOnEndRun(ids: Set<string>, end: boolean) {
Expand Down Expand Up @@ -113,32 +116,3 @@ function expandAllNodes(nodes: UITaskTreeNode[], updateState: boolean) {
openedTreeItems.value = []
}
}

/**
* Build the complete next explorer entry list by emitting an expanded node and its filtered
* subtree at the node's current position.
*
* `children` contains only entries produced by filtering the expanded subtree. This function
* walks the complete current `uiEntries`. When it reaches `node`, it emits the node unless
* `children` already contains it, followed by the children. Unrelated entries are copied
* unchanged, while existing entries with the same IDs are skipped to avoid duplicates.
*
* TODO: Make this a pure splice over explicit entries and keep expansion state changes in
* `runExpandNode`.
*/
function* collectExpandedNode(node: UITaskTreeNode, children: Set<UITaskTreeNode>) {
const id = node.id
const ids = new Set(Array.from(children).map((n) => n.id))

for (const child of uiEntries.value) {
if (child.id === id) {
child.expanded = true
if (!ids.has(child.id)) {
yield node
}
yield* children
} else if (!ids.has(child.id)) {
yield child
}
}
}
Loading
Loading