Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
xxadev
vscode
提交
7a7d11fc
V
vscode
项目概览
xxadev
/
vscode
与 Fork 源项目一致
从无法访问的项目Fork
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
V
vscode
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
未验证
提交
7a7d11fc
编写于
1月 08, 2021
作者:
C
Connor Peet
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'testing-group-by-result'
上级
e3509b62
5f203603
变更
17
展开全部
隐藏空白更改
内联
并排
Showing
17 changed file
with
1726 addition
and
517 deletion
+1726
-517
src/vs/base/common/iterator.ts
src/vs/base/common/iterator.ts
+10
-2
src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts
...sting/browser/explorerProjections/hierarchalByLocation.ts
+238
-0
src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName.ts
...b/testing/browser/explorerProjections/hierarchalByName.ts
+135
-0
src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes.ts
...ib/testing/browser/explorerProjections/hierarchalNodes.ts
+154
-0
src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts
...ench/contrib/testing/browser/explorerProjections/index.ts
+92
-0
src/vs/workbench/contrib/testing/browser/explorerProjections/locationStore.ts
...trib/testing/browser/explorerProjections/locationStore.ts
+80
-0
src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts
...contrib/testing/browser/explorerProjections/nodeHelper.ts
+67
-0
src/vs/workbench/contrib/testing/browser/explorerProjections/stateByLocation.ts
...ib/testing/browser/explorerProjections/stateByLocation.ts
+316
-0
src/vs/workbench/contrib/testing/browser/explorerProjections/stateByName.ts
...ontrib/testing/browser/explorerProjections/stateByName.ts
+296
-0
src/vs/workbench/contrib/testing/browser/explorerProjections/stateNodes.ts
...contrib/testing/browser/explorerProjections/stateNodes.ts
+39
-0
src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
.../workbench/contrib/testing/browser/testExplorerActions.ts
+81
-8
src/vs/workbench/contrib/testing/browser/testExplorerTree.ts
src/vs/workbench/contrib/testing/browser/testExplorerTree.ts
+4
-0
src/vs/workbench/contrib/testing/browser/testing.contribution.ts
...workbench/contrib/testing/browser/testing.contribution.ts
+3
-0
src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
.../workbench/contrib/testing/browser/testingExplorerView.ts
+165
-507
src/vs/workbench/contrib/testing/common/constants.ts
src/vs/workbench/contrib/testing/common/constants.ts
+13
-0
src/vs/workbench/contrib/testing/common/testService.ts
src/vs/workbench/contrib/testing/common/testService.ts
+5
-0
src/vs/workbench/contrib/testing/common/testServiceImpl.ts
src/vs/workbench/contrib/testing/common/testServiceImpl.ts
+28
-0
未找到文件。
src/vs/base/common/iterator.ts
浏览文件 @
7a7d11fc
...
...
@@ -22,8 +22,8 @@ export namespace Iterable {
return
iterable
||
_empty
;
}
export
function
isEmpty
<
T
>
(
iterable
:
Iterable
<
T
>
):
boolean
{
return
iterable
[
Symbol
.
iterator
]().
next
().
done
===
true
;
export
function
isEmpty
<
T
>
(
iterable
:
Iterable
<
T
>
|
undefined
|
null
):
boolean
{
return
!
iterable
||
iterable
[
Symbol
.
iterator
]().
next
().
done
===
true
;
}
export
function
first
<
T
>
(
iterable
:
Iterable
<
T
>
):
T
|
undefined
{
...
...
@@ -61,6 +61,14 @@ export namespace Iterable {
}
}
export
function
*
concatNested
<
T
>
(
iterables
:
Iterable
<
Iterable
<
T
>>
):
Iterable
<
T
>
{
for
(
const
iterable
of
iterables
)
{
for
(
const
element
of
iterable
)
{
yield
element
;
}
}
}
/**
* Consumes `atMost` elements from iterable and returns the consumed elements,
* and an iterable for the rest of the elements.
...
...
src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts
0 → 100644
浏览文件 @
7a7d11fc
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
ICompressedTreeElement
}
from
'
vs/base/browser/ui/tree/compressedObjectTreeModel
'
;
import
{
ObjectTree
}
from
'
vs/base/browser/ui/tree/objectTree
'
;
import
{
Emitter
}
from
'
vs/base/common/event
'
;
import
{
FuzzyScore
}
from
'
vs/base/common/filters
'
;
import
{
Iterable
}
from
'
vs/base/common/iterator
'
;
import
{
Disposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
Position
}
from
'
vs/editor/common/core/position
'
;
import
{
IWorkspaceFolder
,
IWorkspaceFoldersChangeEvent
}
from
'
vs/platform/workspace/common/workspace
'
;
import
{
ITestTreeElement
,
ITestTreeProjection
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections
'
;
import
{
HierarchicalElement
,
HierarchicalFolder
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes
'
;
import
{
locationsEqual
,
TestLocationStore
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections/locationStore
'
;
import
{
TestSubscriptionListener
}
from
'
vs/workbench/contrib/testing/browser/testingCollectionService
'
;
import
{
InternalTestItem
,
TestDiffOpType
,
TestsDiff
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
/**
* Projection that lists tests in their traditional tree view.
*/
export
class
HierarchicalByLocationProjection
extends
Disposable
implements
ITestTreeProjection
{
private
readonly
updateEmitter
=
new
Emitter
<
void
>
();
private
lastHadMultipleFolders
=
true
;
private
newlyRenderedNodes
=
new
Set
<
HierarchicalElement
|
HierarchicalFolder
>
();
private
updatedNodes
=
new
Set
<
HierarchicalElement
|
HierarchicalFolder
>
();
private
removedNodes
=
new
Set
<
HierarchicalElement
|
HierarchicalFolder
>
();
private
readonly
locations
=
new
TestLocationStore
<
HierarchicalElement
>
();
/**
* Map of item IDs to test item objects.
*/
protected
readonly
items
=
new
Map
<
string
,
HierarchicalElement
>
();
/**
* Root folders
*/
protected
readonly
folders
=
new
Map
<
string
,
HierarchicalFolder
>
();
/**
* @inheritdoc
*/
public
readonly
onUpdate
=
this
.
updateEmitter
.
event
;
constructor
(
listener
:
TestSubscriptionListener
)
{
super
();
this
.
_register
(
listener
.
onDiff
(([
folder
,
diff
])
=>
this
.
applyDiff
(
folder
,
diff
)));
this
.
_register
(
listener
.
onFolderChange
(
this
.
applyFolderChange
,
this
));
for
(
const
[
folder
,
collection
]
of
listener
.
workspaceFolderCollections
)
{
const
queue
=
[
collection
.
rootNodes
];
while
(
queue
.
length
)
{
for
(
const
id
of
queue
.
pop
()
!
)
{
const
node
=
collection
.
getNodeById
(
id
)
!
;
const
item
=
this
.
createItem
(
node
,
folder
.
folder
);
this
.
storeItem
(
item
);
queue
.
push
(
node
.
children
);
}
}
}
for
(
const
folder
of
this
.
folders
.
values
())
{
this
.
newlyRenderedNodes
.
add
(
folder
);
}
}
private
applyFolderChange
(
evt
:
IWorkspaceFoldersChangeEvent
)
{
for
(
const
folder
of
evt
.
removed
)
{
const
existing
=
this
.
folders
.
get
(
folder
.
uri
.
toString
());
if
(
existing
)
{
this
.
folders
.
delete
(
folder
.
uri
.
toString
());
this
.
removedNodes
.
add
(
existing
);
}
this
.
updateEmitter
.
fire
();
}
}
/**
* @inheritdoc
*/
public
getTestAtPosition
(
uri
:
URI
,
position
:
Position
)
{
return
this
.
locations
.
getTestAtPosition
(
uri
,
position
);
}
/**
* @inheritdoc
*/
private
applyDiff
(
folder
:
IWorkspaceFolder
,
diff
:
TestsDiff
)
{
for
(
const
op
of
diff
)
{
switch
(
op
[
0
])
{
case
TestDiffOpType
.
Add
:
{
const
item
=
this
.
createItem
(
op
[
1
],
folder
);
this
.
storeItem
(
item
);
this
.
newlyRenderedNodes
.
add
(
item
);
break
;
}
case
TestDiffOpType
.
Update
:
{
const
item
=
op
[
1
];
const
existing
=
this
.
items
.
get
(
item
.
id
);
if
(
!
existing
)
{
break
;
}
const
locationChanged
=
!
locationsEqual
(
existing
.
location
,
item
.
item
.
location
);
if
(
locationChanged
)
{
this
.
locations
.
remove
(
existing
);
}
existing
.
update
(
item
,
this
.
addUpdated
);
if
(
locationChanged
)
{
this
.
locations
.
add
(
existing
);
}
this
.
addUpdated
(
existing
);
break
;
}
case
TestDiffOpType
.
Remove
:
{
const
toRemove
=
this
.
items
.
get
(
op
[
1
]);
if
(
!
toRemove
)
{
break
;
}
this
.
deleteItem
(
toRemove
);
toRemove
.
parentItem
.
children
.
delete
(
toRemove
);
this
.
removedNodes
.
add
(
toRemove
);
const
queue
:
Iterable
<
HierarchicalElement
>
[]
=
[[
toRemove
]];
while
(
queue
.
length
)
{
for
(
const
item
of
queue
.
pop
()
!
)
{
this
.
unstoreItem
(
item
);
this
.
newlyRenderedNodes
.
delete
(
item
);
}
}
}
}
}
for
(
const
[
key
,
folder
]
of
this
.
folders
)
{
if
(
folder
.
children
.
size
===
0
)
{
this
.
removedNodes
.
add
(
folder
);
this
.
folders
.
delete
(
key
);
}
}
if
(
diff
.
length
!==
0
)
{
this
.
updateEmitter
.
fire
();
}
}
/**
* @inheritdoc
*/
public
applyTo
(
tree
:
ObjectTree
<
ITestTreeElement
,
FuzzyScore
>
)
{
const
firstFolder
=
Iterable
.
first
(
this
.
folders
.
values
());
if
(
!
this
.
lastHadMultipleFolders
&&
this
.
folders
.
size
!==
1
)
{
tree
.
setChildren
(
null
,
Iterable
.
map
(
this
.
folders
.
values
(),
this
.
renderNode
));
this
.
lastHadMultipleFolders
=
true
;
}
else
if
(
this
.
lastHadMultipleFolders
&&
this
.
folders
.
size
===
1
)
{
tree
.
setChildren
(
null
,
Iterable
.
map
(
firstFolder
!
.
children
,
this
.
renderNode
));
this
.
lastHadMultipleFolders
=
false
;
}
else
{
for
(
const
node
of
this
.
updatedNodes
)
{
if
(
tree
.
hasElement
(
node
))
{
tree
.
rerender
(
node
);
}
}
const
alreadyUpdatedChildren
=
new
Set
<
HierarchicalElement
|
HierarchicalFolder
|
null
>
();
for
(
const
nodeList
of
[
this
.
newlyRenderedNodes
,
this
.
removedNodes
])
{
for
(
let
{
parentItem
,
children
}
of
nodeList
)
{
if
(
!
alreadyUpdatedChildren
.
has
(
parentItem
))
{
if
(
!
this
.
lastHadMultipleFolders
&&
parentItem
===
firstFolder
)
{
tree
.
setChildren
(
null
,
Iterable
.
map
(
firstFolder
.
children
,
this
.
renderNode
));
}
else
{
const
pchildren
:
Iterable
<
HierarchicalElement
|
HierarchicalFolder
>
=
parentItem
?.
children
??
this
.
folders
.
values
();
tree
.
setChildren
(
parentItem
,
Iterable
.
map
(
pchildren
,
this
.
renderNode
));
}
alreadyUpdatedChildren
.
add
(
parentItem
);
}
for
(
const
child
of
children
)
{
alreadyUpdatedChildren
.
add
(
child
);
}
}
}
}
this
.
newlyRenderedNodes
.
clear
();
this
.
removedNodes
.
clear
();
this
.
updatedNodes
.
clear
();
}
protected
createItem
(
item
:
InternalTestItem
,
folder
:
IWorkspaceFolder
):
HierarchicalElement
{
const
parent
=
item
.
parent
?
this
.
items
.
get
(
item
.
parent
)
!
:
this
.
getOrCreateFolderElement
(
folder
);
return
new
HierarchicalElement
(
item
,
parent
);
}
protected
deleteItem
(
item
:
HierarchicalElement
)
{
// no-op
}
protected
getOrCreateFolderElement
(
folder
:
IWorkspaceFolder
)
{
let
f
=
this
.
folders
.
get
(
folder
.
uri
.
toString
());
if
(
!
f
)
{
f
=
new
HierarchicalFolder
(
folder
);
this
.
newlyRenderedNodes
.
add
(
f
);
this
.
folders
.
set
(
folder
.
uri
.
toString
(),
f
);
}
return
f
;
}
protected
readonly
addUpdated
=
(
item
:
ITestTreeElement
)
=>
{
const
cast
=
item
as
HierarchicalElement
|
HierarchicalFolder
;
if
(
!
this
.
newlyRenderedNodes
.
has
(
cast
))
{
this
.
updatedNodes
.
add
(
cast
);
}
};
private
readonly
renderNode
=
(
node
:
HierarchicalElement
|
HierarchicalFolder
):
ICompressedTreeElement
<
ITestTreeElement
>
=>
{
return
{
element
:
node
,
incompressible
:
true
,
children
:
Iterable
.
map
(
node
.
children
,
this
.
renderNode
),
};
};
private
unstoreItem
(
item
:
HierarchicalElement
)
{
this
.
items
.
delete
(
item
.
test
.
id
);
this
.
locations
.
add
(
item
);
}
protected
storeItem
(
item
:
HierarchicalElement
)
{
item
.
parentItem
.
children
.
add
(
item
);
this
.
items
.
set
(
item
.
test
.
id
,
item
);
this
.
locations
.
add
(
item
);
}
}
src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName.ts
0 → 100644
浏览文件 @
7a7d11fc
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
Iterable
}
from
'
vs/base/common/iterator
'
;
import
{
IWorkspaceFolder
}
from
'
vs/platform/workspace/common/workspace
'
;
import
{
ITestTreeElement
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections
'
;
import
{
HierarchicalByLocationProjection
as
HierarchicalByLocationProjection
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation
'
;
import
{
HierarchicalElement
,
HierarchicalFolder
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes
'
;
import
{
InternalTestItem
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
/**
* Type of test element in the list.
*/
export
const
enum
ListElementType
{
/** The element is a leaf test that should be shown in the list */
TestLeaf
,
/** The element is not runnable, but doesn't have any nested leaf tests */
BranchWithLeaf
,
/** The element has nested leaf tests */
BranchWithoutLeaf
,
/** State not yet computed */
Unset
,
}
/**
* Version of the HierarchicalElement that is displayed as a list.
*/
export
class
HierarchicalByNameElement
extends
HierarchicalElement
{
public
elementType
:
ListElementType
=
ListElementType
.
Unset
;
public
readonly
isTestRoot
=
!
this
.
actualParent
;
private
readonly
actualChildren
=
new
Set
<
HierarchicalByNameElement
>
();
public
get
description
()
{
let
description
:
string
|
undefined
;
for
(
let
parent
=
this
.
actualParent
;
parent
&&
!
parent
.
isTestRoot
;
parent
=
parent
.
actualParent
)
{
description
=
description
?
`
${
parent
.
label
}
›
${
description
}
`
:
parent
.
label
;
}
return
description
;
}
/**
* @param actualParent Parent of the item in the test heirarchy
*/
constructor
(
internal
:
InternalTestItem
,
parentItem
:
HierarchicalFolder
|
HierarchicalElement
,
private
readonly
addUpdated
:
(
n
:
ITestTreeElement
)
=>
void
,
private
readonly
actualParent
?:
HierarchicalByNameElement
,
)
{
super
(
internal
,
parentItem
);
actualParent
?.
addChild
(
this
);
this
.
updateLeafTestState
();
}
/**
* @override
*/
public
update
(
actual
:
InternalTestItem
,
addUpdated
:
(
n
:
ITestTreeElement
)
=>
void
)
{
const
wasRunnable
=
this
.
test
.
item
.
runnable
;
super
.
update
(
actual
,
addUpdated
);
if
(
this
.
test
.
item
.
runnable
!==
wasRunnable
)
{
this
.
updateLeafTestState
();
}
}
/**
* Should be called when the list element is removed.
*/
public
remove
()
{
this
.
actualParent
?.
removeChild
(
this
);
}
private
removeChild
(
element
:
HierarchicalByNameElement
)
{
this
.
actualChildren
.
delete
(
element
);
this
.
updateLeafTestState
();
}
private
addChild
(
element
:
HierarchicalByNameElement
)
{
this
.
actualChildren
.
add
(
element
);
this
.
updateLeafTestState
();
}
/**
* Updates the test leaf state for this node. Should be called when a child
* or this node is modified. Note that we never need to look at the children
* here, the children will already be leaves, or not.
*/
private
updateLeafTestState
()
{
const
newType
=
Iterable
.
some
(
this
.
actualChildren
,
c
=>
c
.
elementType
!==
ListElementType
.
BranchWithoutLeaf
)
?
ListElementType
.
BranchWithLeaf
:
this
.
test
.
item
.
runnable
?
ListElementType
.
TestLeaf
:
ListElementType
.
BranchWithoutLeaf
;
if
(
newType
!==
this
.
elementType
)
{
this
.
elementType
=
newType
;
this
.
addUpdated
(
this
);
}
this
.
actualParent
?.
updateLeafTestState
();
}
}
/**
* Projection that shows tests in a flat list (grouped by provider). The only
* change is that, while creating the item, the item parent is set to the
* test root rather than the heirarchal parent.
*/
export
class
HierarchicalByNameProjection
extends
HierarchicalByLocationProjection
{
/**
* @override
*/
protected
createItem
(
item
:
InternalTestItem
,
folder
:
IWorkspaceFolder
):
HierarchicalElement
{
const
parent
=
this
.
getOrCreateFolderElement
(
folder
);
const
actualParent
=
item
.
parent
?
this
.
items
.
get
(
item
.
parent
)
as
HierarchicalByNameElement
:
undefined
;
for
(
const
testRoot
of
parent
.
children
)
{
if
(
testRoot
.
test
.
providerId
===
item
.
providerId
)
{
return
new
HierarchicalByNameElement
(
item
,
testRoot
,
this
.
addUpdated
,
actualParent
);
}
}
return
new
HierarchicalByNameElement
(
item
,
parent
,
this
.
addUpdated
);
}
/**
* @override
*/
protected
deleteItem
(
item
:
HierarchicalElement
)
{
(
item
as
HierarchicalByNameElement
).
remove
();
}
}
src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalNodes.ts
0 → 100644
浏览文件 @
7a7d11fc
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
Iterable
}
from
'
vs/base/common/iterator
'
;
import
{
IWorkspaceFolder
}
from
'
vs/platform/workspace/common/workspace
'
;
import
{
TestRunState
}
from
'
vs/workbench/api/common/extHostTypes
'
;
import
{
ITestTreeElement
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections
'
;
import
{
maxPriority
,
statePriority
}
from
'
vs/workbench/contrib/testing/browser/testExplorerTree
'
;
import
{
InternalTestItem
,
TestIdWithProvider
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
/**
* Test tree element element that groups be hierarchy.
*/
export
class
HierarchicalElement
implements
ITestTreeElement
{
public
readonly
children
=
new
Set
<
HierarchicalElement
>
();
public
computedState
:
TestRunState
|
undefined
;
public
readonly
depth
:
number
=
this
.
parentItem
.
depth
+
1
;
public
get
treeId
()
{
return
`test:
${
this
.
test
.
id
}
`
;
}
public
get
label
()
{
return
this
.
test
.
item
.
label
;
}
public
get
state
()
{
return
this
.
test
.
item
.
state
.
runState
;
}
public
get
location
()
{
return
this
.
test
.
item
.
location
;
}
public
get
runnable
():
Iterable
<
TestIdWithProvider
>
{
return
this
.
test
.
item
.
runnable
?
[{
providerId
:
this
.
test
.
providerId
,
testId
:
this
.
test
.
id
}]
:
Iterable
.
empty
();
}
public
get
debuggable
()
{
return
this
.
test
.
item
.
debuggable
?
[{
providerId
:
this
.
test
.
providerId
,
testId
:
this
.
test
.
id
}]
:
Iterable
.
empty
();
}
constructor
(
public
readonly
test
:
InternalTestItem
,
public
readonly
parentItem
:
HierarchicalFolder
|
HierarchicalElement
)
{
this
.
test
=
{
...
test
,
item
:
{
...
test
.
item
}
};
// clone since we Object.assign updatese
}
public
getChildren
()
{
return
this
.
children
;
}
public
update
(
actual
:
InternalTestItem
,
addUpdated
:
(
n
:
ITestTreeElement
)
=>
void
)
{
const
stateChange
=
actual
.
item
.
state
.
runState
!==
this
.
state
;
Object
.
assign
(
this
.
test
,
actual
);
if
(
stateChange
)
{
refreshComputedState
(
this
,
addUpdated
);
}
}
}
/**
* Workspace folder in the hierarcha view.
*/
export
class
HierarchicalFolder
implements
ITestTreeElement
{
public
readonly
children
=
new
Set
<
HierarchicalElement
>
();
public
readonly
parentItem
=
null
;
public
readonly
depth
=
0
;
public
computedState
:
TestRunState
|
undefined
;
public
get
treeId
()
{
return
`folder:
${
this
.
folder
.
index
}
`
;
}
public
get
runnable
()
{
return
Iterable
.
concatNested
(
Iterable
.
map
(
this
.
children
,
c
=>
c
.
runnable
));
}
public
get
debuggable
()
{
return
Iterable
.
concatNested
(
Iterable
.
map
(
this
.
children
,
c
=>
c
.
debuggable
));
}
constructor
(
private
readonly
folder
:
IWorkspaceFolder
)
{
}
public
get
label
()
{
return
this
.
folder
.
name
;
}
public
getChildren
()
{
return
this
.
children
;
}
}
/**
* Gets the computed state for the node.
*/
export
const
getComputedState
=
(
node
:
ITestTreeElement
)
=>
{
if
(
node
.
computedState
===
undefined
)
{
node
.
computedState
=
node
.
state
??
TestRunState
.
Unset
;
for
(
const
child
of
node
.
getChildren
())
{
node
.
computedState
=
maxPriority
(
node
.
computedState
,
getComputedState
(
child
));
}
}
return
node
.
computedState
;
};
/**
* Refreshes the computed state for the node and its parents. Any changes
* elements cause `addUpdated` to be called.
*/
export
const
refreshComputedState
=
(
node
:
ITestTreeElement
,
addUpdated
:
(
n
:
ITestTreeElement
)
=>
void
)
=>
{
if
(
node
.
computedState
===
undefined
)
{
return
;
}
const
oldPriority
=
statePriority
[
node
.
computedState
];
node
.
computedState
=
undefined
;
const
newState
=
getComputedState
(
node
);
const
newPriority
=
statePriority
[
getComputedState
(
node
)];
if
(
newPriority
===
oldPriority
)
{
return
;
}
addUpdated
(
node
);
if
(
newPriority
>
oldPriority
)
{
// Update all parents to ensure they're at least this priority.
for
(
let
parent
=
node
.
parentItem
;
parent
;
parent
=
parent
.
parentItem
)
{
const
prev
=
parent
.
computedState
;
if
(
prev
!==
undefined
&&
statePriority
[
prev
]
>=
newPriority
)
{
break
;
}
parent
.
computedState
=
newState
;
addUpdated
(
parent
);
}
}
else
if
(
newPriority
<
oldPriority
)
{
// Re-render all parents of this node whose computed priority might have come from this node
for
(
let
parent
=
node
.
parentItem
;
parent
;
parent
=
parent
.
parentItem
)
{
const
prev
=
parent
.
computedState
;
if
(
prev
===
undefined
||
statePriority
[
prev
]
>
oldPriority
)
{
break
;
}
parent
.
computedState
=
undefined
;
parent
.
computedState
=
getComputedState
(
parent
);
addUpdated
(
parent
);
}
}
};
src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts
0 → 100644
浏览文件 @
7a7d11fc
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
CompressibleObjectTree
}
from
'
vs/base/browser/ui/tree/objectTree
'
;
import
{
Event
}
from
'
vs/base/common/event
'
;
import
{
FuzzyScore
}
from
'
vs/base/common/filters
'
;
import
{
IDisposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
Position
}
from
'
vs/editor/common/core/position
'
;
import
{
ITextEditorSelection
}
from
'
vs/platform/editor/common/editor
'
;
import
{
TestRunState
}
from
'
vs/workbench/api/common/extHostTypes
'
;
import
{
InternalTestItem
,
TestIdWithProvider
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
/**
* Describes a rendering of tests in the explorer view. Different
* implementations of this are used for trees and lists, and groupings.
* Originally this was implemented as inline logic within the ViewModel and
* using a single IncrementalTestChangeCollector, but this became hairy
* with status projections.
*/
export
interface
ITestTreeProjection
extends
IDisposable
{
/**
* Event that fires when the projection changes.
*/
onUpdate
:
Event
<
void
>
;
/**
* Gets the test at the given position in th editor. Should be fast,
* since it is called on each cursor move.
*/
getTestAtPosition
(
uri
:
URI
,
position
:
Position
):
ITestTreeElement
|
undefined
;
/**
* Applies pending update to the tree.
*/
applyTo
(
tree
:
CompressibleObjectTree
<
ITestTreeElement
,
FuzzyScore
>
):
void
;
}
export
interface
ITestTreeElement
{
/**
* Computed element state. Will be set automatically if not initially provided.
* The projection is responsible for clearing (or updating) this if it
* becomes invalid.
*/
computedState
:
TestRunState
|
undefined
;
/**
* Unique ID of the element in the tree.
*/
readonly
treeId
:
string
;
/**
* Location of the test, if any.
*/
readonly
location
?:
{
uri
:
URI
;
range
:
ITextEditorSelection
};
/**
* Test item, if any.
*/
readonly
test
?:
Readonly
<
InternalTestItem
>
;
/**
* Tree description.
*/
readonly
description
?:
string
;
/**
* Depth of the item in the tree.
*/
readonly
depth
:
number
;
/**
* Tests that can be run using this tree item.
*/
readonly
runnable
:
Iterable
<
TestIdWithProvider
>
;
/**
* Tests that can be run using this tree item.
*/
readonly
debuggable
:
Iterable
<
TestIdWithProvider
>
;
/**
* State of of the tree item. Mostly used for deriving the computed state.
*/
readonly
state
?:
TestRunState
;
readonly
label
:
string
;
readonly
parentItem
:
ITestTreeElement
|
null
;
getChildren
():
Iterable
<
ITestTreeElement
>
;
}
src/vs/workbench/contrib/testing/browser/explorerProjections/locationStore.ts
0 → 100644
浏览文件 @
7a7d11fc
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
findFirstInSorted
}
from
'
vs/base/common/arrays
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
Position
}
from
'
vs/editor/common/core/position
'
;
import
{
Location
as
ModeLocation
}
from
'
vs/editor/common/modes
'
;
export
const
locationsEqual
=
(
a
:
ModeLocation
|
undefined
,
b
:
ModeLocation
|
undefined
)
=>
{
if
(
a
===
undefined
||
b
===
undefined
)
{
return
b
===
a
;
}
return
a
.
uri
.
toString
()
===
b
.
uri
.
toString
()
&&
a
.
range
.
startLineNumber
===
b
.
range
.
startLineNumber
&&
a
.
range
.
startColumn
===
b
.
range
.
startColumn
&&
a
.
range
.
endLineNumber
===
b
.
range
.
endLineNumber
&&
a
.
range
.
endColumn
===
b
.
range
.
endColumn
;
};
/**
* Stores and looks up test-item-like-objects by their uri/range. Used to
* implement the 'reveal' action efficiently.
*/
export
class
TestLocationStore
<
T
extends
{
location
?:
ModeLocation
,
depth
:
number
}
>
{
private
readonly
itemsByUri
=
new
Map
<
string
,
T
[]
>
();
public
getTestAtPosition
(
uri
:
URI
,
position
:
Position
)
{
const
tests
=
this
.
itemsByUri
.
get
(
uri
.
toString
());
if
(
!
tests
)
{
return
;
}
return
tests
.
find
(
test
=>
{
const
range
=
test
.
location
?.
range
;
return
range
&&
new
Position
(
range
.
startLineNumber
,
range
.
startColumn
).
isBeforeOrEqual
(
position
)
&&
position
.
isBefore
(
new
Position
(
range
.
endLineNumber
??
range
.
startLineNumber
,
range
.
endColumn
??
range
.
startColumn
,
));
});
}
public
remove
(
item
:
T
,
fromLocation
=
item
.
location
)
{
if
(
!
fromLocation
)
{
return
;
}
const
key
=
fromLocation
.
uri
.
toString
();
const
arr
=
this
.
itemsByUri
.
get
(
key
);
if
(
!
arr
)
{
return
;
}
for
(
let
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
if
(
arr
[
i
]
===
item
)
{
arr
.
splice
(
i
,
1
);
return
;
}
}
}
public
add
(
item
:
T
)
{
if
(
!
item
.
location
)
{
return
;
}
const
key
=
item
.
location
.
uri
.
toString
();
const
arr
=
this
.
itemsByUri
.
get
(
key
);
if
(
!
arr
)
{
this
.
itemsByUri
.
set
(
key
,
[
item
]);
return
;
}
arr
.
splice
(
findFirstInSorted
(
arr
,
x
=>
x
.
depth
<
item
.
depth
),
0
,
item
);
}
}
src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts
0 → 100644
浏览文件 @
7a7d11fc
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
ICompressedTreeElement
}
from
'
vs/base/browser/ui/tree/compressedObjectTreeModel
'
;
import
{
CompressibleObjectTree
,
ObjectTree
}
from
'
vs/base/browser/ui/tree/objectTree
'
;
import
{
Iterable
}
from
'
vs/base/common/iterator
'
;
import
{
ITestTreeElement
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections
'
;
/**
* Removes nodes from the set whose parents don't exist in the tree. This is
* useful to remove nodes that are queued to be updated or rendered, who will
* be rendered by a call to setChildren.
*/
export
const
pruneNodesWithParentsNotInTree
=
<
T
extends
ITestTreeElement
>
(
nodes
:
Set
<
T
|
null
>
,
tree
:
ObjectTree
<
ITestTreeElement
,
any
>
)
=>
{
for
(
const
node
of
nodes
)
{
if
(
node
&&
node
.
parentItem
&&
!
tree
.
hasElement
(
node
.
parentItem
))
{
nodes
.
delete
(
node
);
}
}
};
/**
* Helper to gather and bulk-apply tree updates.
*/
export
class
NodeChangeList
<
T
extends
ITestTreeElement
&
{
children
:
Iterable
<
T
>
;
parentItem
:
T
|
null
}
>
{
private
changedParents
=
new
Set
<
T
|
null
>
();
private
updatedNodes
=
new
Set
<
T
>
();
public
updated
(
node
:
T
)
{
this
.
updatedNodes
.
add
(
node
);
}
public
removed
(
node
:
T
)
{
this
.
changedParents
.
add
(
node
.
parentItem
);
}
public
added
(
node
:
T
)
{
this
.
changedParents
.
add
(
node
.
parentItem
);
}
public
applyTo
(
tree
:
CompressibleObjectTree
<
ITestTreeElement
,
any
>
,
renderNode
:
(
n
:
T
)
=>
ICompressedTreeElement
<
ITestTreeElement
>
,
roots
:
()
=>
Iterable
<
T
>
,
)
{
pruneNodesWithParentsNotInTree
(
this
.
changedParents
,
tree
);
pruneNodesWithParentsNotInTree
(
this
.
updatedNodes
,
tree
);
for
(
const
parent
of
this
.
changedParents
)
{
if
(
parent
===
null
||
tree
.
hasElement
(
parent
))
{
const
pchildren
:
Iterable
<
T
>
=
parent
?
parent
.
children
:
roots
();
tree
.
setChildren
(
parent
,
Iterable
.
map
(
pchildren
,
renderNode
));
}
}
for
(
const
node
of
this
.
updatedNodes
)
{
if
(
tree
.
hasElement
(
node
))
{
tree
.
rerender
(
node
);
}
}
this
.
changedParents
.
clear
();
this
.
updatedNodes
.
clear
();
}
}
src/vs/workbench/contrib/testing/browser/explorerProjections/stateByLocation.ts
0 → 100644
浏览文件 @
7a7d11fc
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
ICompressedTreeElement
}
from
'
vs/base/browser/ui/tree/compressedObjectTreeModel
'
;
import
{
CompressibleObjectTree
}
from
'
vs/base/browser/ui/tree/objectTree
'
;
import
{
Emitter
}
from
'
vs/base/common/event
'
;
import
{
FuzzyScore
}
from
'
vs/base/common/filters
'
;
import
{
Iterable
}
from
'
vs/base/common/iterator
'
;
import
{
DisposableStore
}
from
'
vs/base/common/lifecycle
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
Position
}
from
'
vs/editor/common/core/position
'
;
import
{
Location
as
ModeLocation
}
from
'
vs/editor/common/modes
'
;
import
{
TestRunState
}
from
'
vs/workbench/api/common/extHostTypes
'
;
import
{
ITestTreeElement
,
ITestTreeProjection
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections
'
;
import
{
locationsEqual
,
TestLocationStore
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections/locationStore
'
;
import
{
NodeChangeList
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper
'
;
import
{
StateElement
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections/stateNodes
'
;
import
{
statesInOrder
}
from
'
vs/workbench/contrib/testing/browser/testExplorerTree
'
;
import
{
TestSubscriptionListener
}
from
'
vs/workbench/contrib/testing/browser/testingCollectionService
'
;
import
{
AbstractIncrementalTestCollection
,
IncrementalChangeCollector
,
IncrementalTestCollectionItem
,
InternalTestItem
,
TestDiffOpType
,
TestIdWithProvider
,
TestsDiff
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
interface
IStatusTestItem
extends
IncrementalTestCollectionItem
{
treeElements
:
Map
<
TestRunState
,
TestStateElement
>
;
previousState
:
TestRunState
;
depth
:
number
;
parentItem
?:
IStatusTestItem
;
location
?:
ModeLocation
;
}
type
TreeElement
=
StateElement
<
TestStateElement
>
|
TestStateElement
;
class
TestStateElement
implements
ITestTreeElement
{
public
computedState
=
this
.
state
;
public
get
treeId
()
{
return
`test:
${
this
.
test
.
id
}
`
;
}
public
get
label
()
{
return
this
.
test
.
item
.
label
;
}
public
get
location
()
{
return
this
.
test
.
item
.
location
;
}
public
get
runnable
():
Iterable
<
TestIdWithProvider
>
{
// if this item is runnable and all its children are in the same state,
// we can run all of them in one go. This will eventually be true
// for leaf nodes, whose treeElements contain only their own state.
if
(
this
.
test
.
item
.
runnable
&&
this
.
test
.
treeElements
.
size
===
1
)
{
return
[{
testId
:
this
.
test
.
id
,
providerId
:
this
.
test
.
providerId
}];
}
return
Iterable
.
concatNested
(
Iterable
.
map
(
this
.
children
,
c
=>
c
.
runnable
));
}
public
get
debuggable
():
Iterable
<
TestIdWithProvider
>
{
// same logic as runnable above
if
(
this
.
test
.
item
.
debuggable
&&
this
.
test
.
treeElements
.
size
===
1
)
{
return
[{
testId
:
this
.
test
.
id
,
providerId
:
this
.
test
.
providerId
}];
}
return
Iterable
.
concatNested
(
Iterable
.
map
(
this
.
children
,
c
=>
c
.
debuggable
));
}
public
readonly
depth
=
this
.
test
.
depth
;
public
readonly
children
=
new
Set
<
TestStateElement
>
();
getChildren
():
Iterable
<
ITestTreeElement
>
{
return
this
.
children
;
}
constructor
(
public
readonly
state
:
TestRunState
,
public
readonly
test
:
IStatusTestItem
,
public
readonly
parentItem
:
TestStateElement
|
StateElement
<
TestStateElement
>
,
)
{
parentItem
.
children
.
add
(
this
);
}
public
remove
()
{
this
.
parentItem
.
children
.
delete
(
this
);
}
}
/**
* Shows tests in a hierarchical way, but grouped by status. This is more
* complex than it may look at first glance, because nodes can appear in
* multiple places if they have children with different statuses.
*/
export
class
StateByLocationProjection
extends
AbstractIncrementalTestCollection
<
IStatusTestItem
>
implements
ITestTreeProjection
{
private
readonly
updateEmitter
=
new
Emitter
<
void
>
();
private
readonly
changes
=
new
NodeChangeList
<
TreeElement
>
();
private
readonly
locations
=
new
TestLocationStore
<
IStatusTestItem
>
();
private
readonly
disposable
=
new
DisposableStore
();
/**
* @inheritdoc
*/
public
readonly
onUpdate
=
this
.
updateEmitter
.
event
;
/**
* Root elements for states in the tree.
*/
protected
readonly
stateRoots
=
new
Map
<
TestRunState
,
StateElement
<
TestStateElement
>>
();
constructor
(
listener
:
TestSubscriptionListener
)
{
super
();
this
.
disposable
.
add
(
listener
.
onDiff
(([,
diff
])
=>
this
.
apply
(
diff
)));
const
firstDiff
:
TestsDiff
=
[];
for
(
const
[,
collection
]
of
listener
.
workspaceFolderCollections
)
{
const
queue
=
[
collection
.
rootNodes
];
while
(
queue
.
length
)
{
for
(
const
id
of
queue
.
pop
()
!
)
{
const
node
=
collection
.
getNodeById
(
id
)
!
;
firstDiff
.
push
([
TestDiffOpType
.
Add
,
node
]);
queue
.
push
(
node
.
children
);
}
}
}
this
.
apply
(
firstDiff
);
}
/**
* Frees listeners associated with the projection.
*/
public
dispose
()
{
this
.
disposable
.
dispose
();
}
/**
* @inheritdoc
*/
public
getTestAtPosition
(
uri
:
URI
,
position
:
Position
)
{
const
item
=
this
.
locations
.
getTestAtPosition
(
uri
,
position
);
if
(
!
item
)
{
return
undefined
;
}
for
(
const
state
of
statesInOrder
)
{
const
element
=
item
.
treeElements
.
get
(
state
);
if
(
element
)
{
return
element
;
}
}
return
undefined
;
}
/**
* @inheritdoc
*/
public
applyTo
(
tree
:
CompressibleObjectTree
<
ITestTreeElement
,
FuzzyScore
>
)
{
this
.
changes
.
applyTo
(
tree
,
this
.
renderNode
,
()
=>
this
.
stateRoots
.
values
());
}
private
readonly
renderNode
=
(
node
:
TreeElement
):
ICompressedTreeElement
<
ITestTreeElement
>
=>
{
return
{
element
:
node
,
incompressible
:
node
.
depth
>
0
,
children
:
Iterable
.
map
(
node
.
children
,
this
.
renderNode
),
};
};
/**
* @override
*/
protected
createChangeCollector
():
IncrementalChangeCollector
<
IStatusTestItem
>
{
return
{
add
:
node
=>
{
this
.
resolveNodesRecursive
(
node
);
this
.
locations
.
add
(
node
);
},
remove
:
(
node
,
isNested
)
=>
{
this
.
locations
.
remove
(
node
);
if
(
!
isNested
)
{
for
(
const
state
of
node
.
treeElements
.
keys
())
{
this
.
pruneStateElements
(
node
,
state
,
true
);
}
}
},
update
:
node
=>
{
if
(
node
.
item
.
state
.
runState
!==
node
.
previousState
)
{
this
.
pruneStateElements
(
node
,
node
.
previousState
);
this
.
resolveNodesRecursive
(
node
);
}
const
locationChanged
=
!
locationsEqual
(
node
.
location
,
node
.
item
.
location
);
if
(
locationChanged
)
{
this
.
locations
.
remove
(
node
);
node
.
location
=
node
.
item
.
location
;
this
.
locations
.
add
(
node
);
}
const
treeNode
=
node
.
treeElements
.
get
(
node
.
item
.
state
.
runState
)
!
;
this
.
changes
.
updated
(
treeNode
);
},
complete
:
()
=>
{
this
.
updateEmitter
.
fire
();
}
};
}
/**
* Ensures tree nodes for the item state are present in the tree.
*/
protected
resolveNodesRecursive
(
item
:
IStatusTestItem
)
{
const
state
=
item
.
item
.
state
.
runState
;
item
.
previousState
=
item
.
item
.
state
.
runState
;
// Create a list of items until the current item who don't have a tree node for the status yet
let
chain
:
IStatusTestItem
[]
=
[];
for
(
let
i
:
IStatusTestItem
|
undefined
=
item
;
i
&&
!
i
.
treeElements
.
has
(
state
);
i
=
i
.
parentItem
)
{
chain
.
push
(
i
);
}
for
(
let
i
=
chain
.
length
-
1
;
i
>=
0
;
i
--
)
{
const
item2
=
chain
[
i
];
// the loop would have stopped pushing parents when either it reaches
// the root, or it reaches a parent who already has a node for this state.
const
parent
=
item2
.
parentItem
?.
treeElements
.
get
(
state
)
??
this
.
getOrCreateStateElement
(
state
);
const
node
=
this
.
createElement
(
state
,
item2
,
parent
);
item2
.
treeElements
.
set
(
state
,
node
);
parent
.
children
.
add
(
node
);
if
(
i
===
chain
.
length
-
1
)
{
this
.
changes
.
added
(
node
);
}
}
}
protected
createElement
(
state
:
TestRunState
,
item
:
IStatusTestItem
,
parent
:
TreeElement
)
{
return
new
TestStateElement
(
state
,
item
,
parent
);
}
/**
* Recursively (from the leaf to the root) removes tree elements if there's
* no children who have the given state left.
*
* Returns true if it resulted in a node being removed.
*/
protected
pruneStateElements
(
item
:
IStatusTestItem
|
undefined
,
state
:
TestRunState
,
force
=
false
)
{
if
(
!
item
)
{
const
stateRoot
=
this
.
stateRoots
.
get
(
state
);
if
(
stateRoot
?.
children
.
size
===
0
)
{
this
.
changes
.
removed
(
stateRoot
);
this
.
stateRoots
.
delete
(
state
);
return
true
;
}
return
false
;
}
const
node
=
item
.
treeElements
.
get
(
state
);
if
(
!
node
)
{
return
false
;
}
// Check to make sure we aren't in the state, and there's no child with the
// state. For the unset state, only show the node if it's a leaf or it
// has children in the unset state.
if
(
!
force
)
{
if
(
item
.
item
.
state
.
runState
===
state
&&
!
(
state
===
TestRunState
.
Unset
&&
item
.
children
.
size
>
0
))
{
return
false
;
}
for
(
const
childId
of
item
.
children
)
{
if
(
this
.
items
.
get
(
childId
)?.
treeElements
.
has
(
state
))
{
return
false
;
}
}
}
// If so, proceed to deletion and recurse upwards.
item
.
treeElements
.
delete
(
state
);
node
.
remove
();
if
(
!
this
.
pruneStateElements
(
item
.
parentItem
,
state
))
{
this
.
changes
.
removed
(
node
);
}
return
true
;
}
protected
getOrCreateStateElement
(
state
:
TestRunState
)
{
let
s
=
this
.
stateRoots
.
get
(
state
);
if
(
!
s
)
{
s
=
new
StateElement
(
state
);
this
.
changes
.
added
(
s
);
this
.
stateRoots
.
set
(
state
,
s
);
}
return
s
;
}
protected
createItem
(
item
:
InternalTestItem
,
parentItem
?:
IStatusTestItem
):
IStatusTestItem
{
return
{
...
item
,
depth
:
parentItem
?
parentItem
.
depth
+
1
:
0
,
parentItem
:
parentItem
,
previousState
:
item
.
item
.
state
.
runState
,
location
:
item
.
item
.
location
,
children
:
new
Set
(),
treeElements
:
new
Map
(),
};
}
}
src/vs/workbench/contrib/testing/browser/explorerProjections/stateByName.ts
0 → 100644
浏览文件 @
7a7d11fc
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
ICompressedTreeElement
}
from
'
vs/base/browser/ui/tree/compressedObjectTreeModel
'
;
import
{
CompressibleObjectTree
}
from
'
vs/base/browser/ui/tree/objectTree
'
;
import
{
Emitter
}
from
'
vs/base/common/event
'
;
import
{
FuzzyScore
}
from
'
vs/base/common/filters
'
;
import
{
Iterable
}
from
'
vs/base/common/iterator
'
;
import
{
DisposableStore
}
from
'
vs/base/common/lifecycle
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
Position
}
from
'
vs/editor/common/core/position
'
;
import
{
Location
as
ModeLocation
}
from
'
vs/editor/common/modes
'
;
import
{
TestRunState
}
from
'
vs/workbench/api/common/extHostTypes
'
;
import
{
ITestTreeElement
,
ITestTreeProjection
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections
'
;
import
{
ListElementType
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName
'
;
import
{
locationsEqual
,
TestLocationStore
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections/locationStore
'
;
import
{
NodeChangeList
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper
'
;
import
{
StateElement
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections/stateNodes
'
;
import
{
TestSubscriptionListener
}
from
'
vs/workbench/contrib/testing/browser/testingCollectionService
'
;
import
{
AbstractIncrementalTestCollection
,
IncrementalChangeCollector
,
IncrementalTestCollectionItem
,
InternalTestItem
,
TestDiffOpType
,
TestIdWithProvider
,
TestsDiff
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
class
ListTestStateElement
implements
ITestTreeElement
{
public
computedState
=
this
.
test
.
item
.
state
.
runState
;
public
get
treeId
()
{
return
`test:
${
this
.
test
.
id
}
`
;
}
public
get
label
()
{
return
this
.
test
.
item
.
label
;
}
public
get
location
()
{
return
this
.
test
.
item
.
location
;
}
public
get
runnable
():
Iterable
<
TestIdWithProvider
>
{
return
this
.
test
.
item
.
runnable
?
[{
testId
:
this
.
test
.
id
,
providerId
:
this
.
test
.
providerId
}]
:
Iterable
.
empty
();
}
public
get
debuggable
():
Iterable
<
TestIdWithProvider
>
{
return
this
.
test
.
item
.
debuggable
?
[{
testId
:
this
.
test
.
id
,
providerId
:
this
.
test
.
providerId
}]
:
Iterable
.
empty
();
}
public
get
description
()
{
let
description
:
string
|
undefined
;
for
(
let
parent
=
this
.
test
.
parentItem
;
parent
&&
parent
.
depth
>
0
;
parent
=
parent
.
parentItem
)
{
description
=
description
?
`
${
parent
.
item
.
label
}
›
${
description
}
`
:
parent
.
item
.
label
;
}
return
description
;
}
public
readonly
depth
=
1
;
public
readonly
children
=
Iterable
.
empty
();
getChildren
():
Iterable
<
never
>
{
return
Iterable
.
empty
();
}
constructor
(
public
readonly
test
:
IStatusListTestItem
,
public
readonly
parentItem
:
StateElement
<
ListTestStateElement
>
,
)
{
parentItem
.
children
.
add
(
this
);
}
public
remove
()
{
this
.
parentItem
.
children
.
delete
(
this
);
}
}
interface
IStatusListTestItem
extends
IncrementalTestCollectionItem
{
node
?:
ListTestStateElement
;
type
:
ListElementType
;
previousState
:
TestRunState
;
depth
:
number
;
parentItem
?:
IStatusListTestItem
;
location
?:
ModeLocation
;
}
type
TreeElement
=
StateElement
<
ListTestStateElement
>
|
ListTestStateElement
;
/**
* Projection that shows tests in a flat list (grouped by status).
*/
export
class
StateByNameProjection
extends
AbstractIncrementalTestCollection
<
IStatusListTestItem
>
implements
ITestTreeProjection
{
private
readonly
updateEmitter
=
new
Emitter
<
void
>
();
private
readonly
changes
=
new
NodeChangeList
<
TreeElement
>
();
private
readonly
locations
=
new
TestLocationStore
<
IStatusListTestItem
>
();
private
readonly
disposable
=
new
DisposableStore
();
/**
* @inheritdoc
*/
public
readonly
onUpdate
=
this
.
updateEmitter
.
event
;
/**
* Root elements for states in the tree.
*/
protected
readonly
stateRoots
=
new
Map
<
TestRunState
,
StateElement
<
ListTestStateElement
>>
();
constructor
(
listener
:
TestSubscriptionListener
)
{
super
();
this
.
disposable
.
add
(
listener
.
onDiff
(([,
diff
])
=>
this
.
apply
(
diff
)));
const
firstDiff
:
TestsDiff
=
[];
for
(
const
[,
collection
]
of
listener
.
workspaceFolderCollections
)
{
const
queue
=
[
collection
.
rootNodes
];
while
(
queue
.
length
)
{
for
(
const
id
of
queue
.
pop
()
!
)
{
const
node
=
collection
.
getNodeById
(
id
)
!
;
firstDiff
.
push
([
TestDiffOpType
.
Add
,
node
]);
queue
.
push
(
node
.
children
);
}
}
}
this
.
apply
(
firstDiff
);
}
/**
* Frees listeners associated with the projection.
*/
public
dispose
()
{
this
.
disposable
.
dispose
();
}
/**
* @inheritdoc
*/
public
getTestAtPosition
(
uri
:
URI
,
position
:
Position
)
{
return
this
.
locations
.
getTestAtPosition
(
uri
,
position
)?.
node
;
}
/**
* @inheritdoc
*/
public
applyTo
(
tree
:
CompressibleObjectTree
<
ITestTreeElement
,
FuzzyScore
>
)
{
this
.
changes
.
applyTo
(
tree
,
this
.
renderNode
,
()
=>
this
.
stateRoots
.
values
());
}
private
readonly
renderNode
=
(
node
:
TreeElement
):
ICompressedTreeElement
<
ITestTreeElement
>
=>
{
return
{
element
:
node
,
incompressible
:
true
,
children
:
node
instanceof
StateElement
?
Iterable
.
map
(
node
.
children
,
this
.
renderNode
)
:
undefined
,
};
};
/**
* @override
*/
protected
createChangeCollector
():
IncrementalChangeCollector
<
IStatusListTestItem
>
{
return
{
add
:
node
=>
{
this
.
resolveNodesRecursive
(
node
);
this
.
locations
.
add
(
node
);
},
remove
:
(
node
,
isRoot
)
=>
{
if
(
node
.
node
)
{
this
.
locations
.
remove
(
node
);
}
// for the top node being deleted, we need to update parents. For
// others we only need to remove them from the tree view.
if
(
isRoot
)
{
this
.
removeNode
(
node
);
}
else
{
this
.
removeNodeSingle
(
node
);
}
},
update
:
node
=>
{
if
(
node
.
item
.
state
.
runState
!==
node
.
previousState
)
{
this
.
removeNode
(
node
);
}
this
.
resolveNodesRecursive
(
node
);
const
locationChanged
=
!
locationsEqual
(
node
.
location
,
node
.
item
.
location
);
if
(
locationChanged
)
{
this
.
locations
.
remove
(
node
);
node
.
location
=
node
.
item
.
location
;
this
.
locations
.
add
(
node
);
}
if
(
node
.
node
)
{
this
.
changes
.
updated
(
node
.
node
);
}
},
complete
:
()
=>
{
this
.
updateEmitter
.
fire
();
}
};
}
/**
* Ensures tree nodes for the item state are present in the tree.
*/
protected
resolveNodesRecursive
(
item
:
IStatusListTestItem
)
{
const
newType
=
Iterable
.
some
(
item
.
children
,
c
=>
this
.
items
.
get
(
c
)
!
.
type
!==
ListElementType
.
BranchWithoutLeaf
)
?
ListElementType
.
BranchWithLeaf
:
item
.
item
.
runnable
?
ListElementType
.
TestLeaf
:
ListElementType
.
BranchWithoutLeaf
;
if
(
newType
===
item
.
type
)
{
return
;
}
const
isVisible
=
newType
===
ListElementType
.
TestLeaf
;
const
wasVisible
=
item
.
type
===
ListElementType
.
TestLeaf
;
item
.
type
=
newType
;
if
(
!
isVisible
&&
wasVisible
&&
item
.
node
)
{
this
.
removeNodeSingle
(
item
);
}
else
if
(
isVisible
&&
!
wasVisible
)
{
const
state
=
item
.
item
.
state
.
runState
;
item
.
node
=
item
.
node
||
new
ListTestStateElement
(
item
,
this
.
getOrCreateStateElement
(
state
));
this
.
changes
.
added
(
item
.
node
);
}
if
(
item
.
parentItem
)
{
this
.
resolveNodesRecursive
(
item
.
parentItem
);
}
}
/**
* Recursively (from the leaf to the root) removes tree elements if there's
* no children who have the given state left.
*
* Returns true if it resulted in a node being removed.
*/
private
removeNode
(
item
:
IStatusListTestItem
)
{
if
(
!
item
.
node
)
{
return
;
}
this
.
removeNodeSingle
(
item
);
if
(
item
.
parentItem
)
{
this
.
resolveNodesRecursive
(
item
.
parentItem
);
}
}
private
removeNodeSingle
(
item
:
IStatusListTestItem
)
{
if
(
!
item
.
node
)
{
return
;
}
item
.
node
.
remove
();
this
.
changes
.
removed
(
item
.
node
);
const
parent
=
item
.
node
.
parentItem
;
item
.
node
=
undefined
;
item
.
type
=
ListElementType
.
Unset
;
if
(
parent
.
children
.
size
===
0
)
{
this
.
changes
.
removed
(
parent
);
this
.
stateRoots
.
delete
(
parent
.
state
);
}
}
private
getOrCreateStateElement
(
state
:
TestRunState
)
{
let
s
=
this
.
stateRoots
.
get
(
state
);
if
(
!
s
)
{
s
=
new
StateElement
(
state
);
this
.
changes
.
added
(
s
);
this
.
stateRoots
.
set
(
state
,
s
);
}
return
s
;
}
/**
* @override
*/
protected
createItem
(
item
:
InternalTestItem
,
parentItem
?:
IStatusListTestItem
):
IStatusListTestItem
{
return
{
...
item
,
type
:
ListElementType
.
Unset
,
depth
:
parentItem
?
parentItem
.
depth
+
1
:
0
,
parentItem
:
parentItem
,
previousState
:
item
.
item
.
state
.
runState
,
location
:
item
.
item
.
location
,
children
:
new
Set
(),
};
}
}
src/vs/workbench/contrib/testing/browser/explorerProjections/stateNodes.ts
0 → 100644
浏览文件 @
7a7d11fc
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
Iterable
}
from
'
vs/base/common/iterator
'
;
import
{
TestRunState
}
from
'
vs/workbench/api/common/extHostTypes
'
;
import
{
ITestTreeElement
}
from
'
vs/workbench/contrib/testing/browser/explorerProjections
'
;
import
{
testStateNames
}
from
'
vs/workbench/contrib/testing/common/constants
'
;
/**
* Base state node element, used in both name and location grouping.
*/
export
class
StateElement
<
T
extends
ITestTreeElement
>
implements
ITestTreeElement
{
public
computedState
=
this
.
state
;
public
get
treeId
()
{
return
`state:
${
this
.
state
}
`
;
}
public
readonly
depth
=
0
;
public
readonly
label
=
testStateNames
[
this
.
state
];
public
readonly
parentItem
=
null
;
public
readonly
children
=
new
Set
<
T
>
();
getChildren
():
Iterable
<
T
>
{
return
this
.
children
;
}
public
get
runnable
()
{
return
Iterable
.
concatNested
(
Iterable
.
map
(
this
.
children
,
c
=>
c
.
runnable
));
}
public
get
debuggable
()
{
return
Iterable
.
concatNested
(
Iterable
.
map
(
this
.
children
,
c
=>
c
.
debuggable
));
}
constructor
(
public
readonly
state
:
TestRunState
)
{
}
}
src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
浏览文件 @
7a7d11fc
...
...
@@ -11,12 +11,11 @@ import { Action2, MenuId } from 'vs/platform/actions/common/actions';
import
{
ContextKeyAndExpr
,
ContextKeyEqualsExpr
}
from
'
vs/platform/contextkey/common/contextkey
'
;
import
{
ServicesAccessor
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
ThemeIcon
}
from
'
vs/platform/theme/common/themeService
'
;
import
{
TestRunState
}
from
'
vs/workbench/api/common/extHostTypes
'
;
import
{
ViewAction
}
from
'
vs/workbench/browser/parts/views/viewPane
'
;
import
*
as
icons
from
'
vs/workbench/contrib/testing/browser/icons
'
;
import
{
ITestingCollectionService
}
from
'
vs/workbench/contrib/testing/browser/testingCollectionService
'
;
import
{
TestingExplorerView
,
TestingExplorerViewModel
}
from
'
vs/workbench/contrib/testing/browser/testingExplorerView
'
;
import
{
TestExplorerViewMode
,
Testing
}
from
'
vs/workbench/contrib/testing/common/constants
'
;
import
{
TestExplorerView
Grouping
,
TestExplorerView
Mode
,
Testing
}
from
'
vs/workbench/contrib/testing/common/constants
'
;
import
{
EMPTY_TEST_RESULT
,
InternalTestItem
,
RunTestsResult
,
TestIdWithProvider
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
import
{
TestingContextKeys
}
from
'
vs/workbench/contrib/testing/common/testingContextKeys
'
;
import
{
ITestService
}
from
'
vs/workbench/contrib/testing/common/testService
'
;
...
...
@@ -40,14 +39,15 @@ export const filterVisibleActions = (actions: ReadonlyArray<Action>) =>
export
class
DebugAction
extends
Action
{
constructor
(
private
readonly
test
:
InternalTestItem
,
private
readonly
tests
:
Iterable
<
TestIdWithProvider
>
,
isRunning
:
boolean
,
@
ITestService
private
readonly
testService
:
ITestService
)
{
super
(
'
action.run
'
,
localize
(
'
debug test
'
,
'
Debug Test
'
),
'
test-action
'
+
ThemeIcon
.
asClassName
(
icons
.
testingDebugIcon
),
/* enabled= */
test
.
item
.
state
.
runState
!==
TestRunState
.
Running
/* enabled= */
!
is
Running
);
}
...
...
@@ -56,7 +56,7 @@ export class DebugAction extends Action {
*/
public
run
():
Promise
<
any
>
{
return
this
.
testService
.
runTests
({
tests
:
[
{
testId
:
this
.
test
.
id
,
providerId
:
this
.
test
.
providerId
}
],
tests
:
[
...
this
.
tests
],
debug
:
true
,
});
}
...
...
@@ -64,14 +64,15 @@ export class DebugAction extends Action {
export
class
RunAction
extends
Action
{
constructor
(
private
readonly
test
:
InternalTestItem
,
private
readonly
tests
:
Iterable
<
TestIdWithProvider
>
,
isRunning
:
boolean
,
@
ITestService
private
readonly
testService
:
ITestService
)
{
super
(
'
action.run
'
,
localize
(
'
run test
'
,
'
Run Test
'
),
'
test-action
'
+
ThemeIcon
.
asClassName
(
icons
.
testingRunIcon
),
/* enabled= */
test
.
item
.
state
.
runState
!==
TestRunState
.
Running
,
/* enabled= */
!
is
Running
,
);
}
...
...
@@ -80,7 +81,7 @@ export class RunAction extends Action {
*/
public
run
():
Promise
<
any
>
{
return
this
.
testService
.
runTests
({
tests
:
[
{
testId
:
this
.
test
.
id
,
providerId
:
this
.
test
.
providerId
}
],
tests
:
[
...
this
.
tests
],
debug
:
false
,
});
}
...
...
@@ -270,3 +271,75 @@ export class TestingViewAsTreeAction extends ViewAction<TestingExplorerView> {
view
.
viewModel
.
viewMode
=
TestExplorerViewMode
.
Tree
;
}
}
export
class
TestingGroupByLocationAction
extends
ViewAction
<
TestingExplorerView
>
{
constructor
()
{
super
({
id
:
'
testing.groupByLocation
'
,
viewId
:
Testing
.
ExplorerViewId
,
title
:
localize
(
'
testing.groupByLocation
'
,
"
Sort by Name
"
),
f1
:
false
,
toggled
:
TestingContextKeys
.
viewGrouping
.
isEqualTo
(
TestExplorerViewGrouping
.
ByLocation
),
menu
:
{
id
:
MenuId
.
ViewTitle
,
order
:
10
,
group
:
'
groupBy
'
,
when
:
ContextKeyEqualsExpr
.
create
(
'
view
'
,
Testing
.
ExplorerViewId
)
}
});
}
/**
* @override
*/
public
runInView
(
_accessor
:
ServicesAccessor
,
view
:
TestingExplorerView
)
{
view
.
viewModel
.
viewGrouping
=
TestExplorerViewGrouping
.
ByLocation
;
}
}
export
class
TestingGroupByStatusAction
extends
ViewAction
<
TestingExplorerView
>
{
constructor
()
{
super
({
id
:
'
testing.groupByStatus
'
,
viewId
:
Testing
.
ExplorerViewId
,
title
:
localize
(
'
testing.groupByStatus
'
,
"
Sort by Status
"
),
f1
:
false
,
toggled
:
TestingContextKeys
.
viewGrouping
.
isEqualTo
(
TestExplorerViewGrouping
.
ByStatus
),
menu
:
{
id
:
MenuId
.
ViewTitle
,
order
:
10
,
group
:
'
groupBy
'
,
when
:
ContextKeyEqualsExpr
.
create
(
'
view
'
,
Testing
.
ExplorerViewId
)
}
});
}
/**
* @override
*/
public
runInView
(
_accessor
:
ServicesAccessor
,
view
:
TestingExplorerView
)
{
view
.
viewModel
.
viewGrouping
=
TestExplorerViewGrouping
.
ByStatus
;
}
}
export
class
RefreshTestsAction
extends
Action2
{
constructor
()
{
super
({
id
:
'
testing.refreshTests
'
,
title
:
localize
(
'
testing.refresh
'
,
"
Refresh Tests
"
),
menu
:
{
id
:
MenuId
.
ViewTitle
,
order
:
0
,
when
:
ContextKeyEqualsExpr
.
create
(
'
view
'
,
Testing
.
ExplorerViewId
)
}
});
}
/**
* @override
*/
public
run
(
accessor
:
ServicesAccessor
)
{
accessor
.
get
(
ITestService
).
resubscribeToAllTests
();
}
}
src/vs/workbench/contrib/testing/browser/testExplorerTree.ts
浏览文件 @
7a7d11fc
...
...
@@ -31,4 +31,8 @@ export const stateNodes = Object.entries(statePriority).reduce(
},
{}
as
{
[
K
in
TestRunState
]:
TreeStateNode
}
);
export
const
cmpPriority
=
(
a
:
TestRunState
,
b
:
TestRunState
)
=>
statePriority
[
b
]
-
statePriority
[
a
];
export
const
maxPriority
=
(
a
:
TestRunState
,
b
:
TestRunState
)
=>
statePriority
[
a
]
>
statePriority
[
b
]
?
a
:
b
;
export
const
statesInOrder
=
Object
.
keys
(
statePriority
).
map
(
s
=>
Number
(
s
)
as
TestRunState
).
sort
(
cmpPriority
);
src/vs/workbench/contrib/testing/browser/testing.contribution.ts
浏览文件 @
7a7d11fc
...
...
@@ -73,6 +73,9 @@ registerAction2(Action.TestingViewAsTreeAction);
registerAction2
(
Action
.
CancelTestRunAction
);
registerAction2
(
Action
.
RunSelectedAction
);
registerAction2
(
Action
.
DebugSelectedAction
);
registerAction2
(
Action
.
TestingGroupByLocationAction
);
registerAction2
(
Action
.
TestingGroupByStatusAction
);
registerAction2
(
Action
.
RefreshTestsAction
);
CommandsRegistry
.
registerCommand
({
id
:
'
vscode.runTests
'
,
...
...
src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
浏览文件 @
7a7d11fc
此差异已折叠。
点击以展开。
src/vs/workbench/contrib/testing/common/constants.ts
浏览文件 @
7a7d11fc
...
...
@@ -3,6 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
localize
}
from
'
vs/nls
'
;
import
{
TestRunState
}
from
'
vs/workbench/api/common/extHostTypes
'
;
export
const
enum
Testing
{
ViewletId
=
'
workbench.view.testing
'
,
ExplorerViewId
=
'
workbench.view.testing
'
,
...
...
@@ -17,3 +20,13 @@ export const enum TestExplorerViewGrouping {
ByLocation
=
'
location
'
,
ByStatus
=
'
status
'
,
}
export
const
testStateNames
:
{
[
K
in
TestRunState
]:
string
}
=
{
[
TestRunState
.
Errored
]:
localize
(
'
testState.errored
'
,
'
Errored
'
),
[
TestRunState
.
Failed
]:
localize
(
'
testState.failed
'
,
'
Failed
'
),
[
TestRunState
.
Passed
]:
localize
(
'
testState.passed
'
,
'
Passed
'
),
[
TestRunState
.
Queued
]:
localize
(
'
testState.queued
'
,
'
Queued
'
),
[
TestRunState
.
Running
]:
localize
(
'
testState.running
'
,
'
Running
'
),
[
TestRunState
.
Skipped
]:
localize
(
'
testState.skipped
'
,
'
Skipped
'
),
[
TestRunState
.
Unset
]:
localize
(
'
testState.unset
'
,
'
Unset
'
),
};
src/vs/workbench/contrib/testing/common/testService.ts
浏览文件 @
7a7d11fc
...
...
@@ -52,4 +52,9 @@ export interface ITestService {
* Updates the number of test providers still discovering tests for the given resource.
*/
updateDiscoveringCount
(
resource
:
ExtHostTestingResource
,
uri
:
URI
,
delta
:
number
):
void
;
/**
* Requests to resubscribe to all active subscriptions, discarding old tests.
*/
resubscribeToAllTests
():
void
;
}
src/vs/workbench/contrib/testing/common/testServiceImpl.ts
浏览文件 @
7a7d11fc
...
...
@@ -156,6 +156,18 @@ export class TestService extends Disposable implements ITestService {
}
}
/**
* @inheritdoc
*/
public
resubscribeToAllTests
()
{
for
(
const
subscription
of
this
.
testSubscriptions
.
values
())
{
this
.
unsubscribeEmitter
.
fire
(
subscription
.
ident
);
const
diff
=
subscription
.
collection
.
clear
();
subscription
.
onDiff
.
fire
(
diff
);
this
.
subscribeEmitter
.
fire
(
subscription
.
ident
);
}
}
/**
* @inheritdoc
*/
...
...
@@ -241,6 +253,22 @@ class MainThreadTestCollection extends AbstractIncrementalTestCollection<Increme
return
ops
;
}
/**
* Clears everything from the collection, and returns a diff that applies
* that action.
*/
public
clear
()
{
const
ops
:
TestsDiff
=
[];
for
(
const
root
of
this
.
roots
)
{
ops
.
push
([
TestDiffOpType
.
Remove
,
root
]);
}
this
.
roots
.
clear
();
this
.
items
.
clear
();
return
ops
;
}
protected
createItem
(
internal
:
InternalTestItem
):
IncrementalTestCollectionItem
{
return
{
...
internal
,
children
:
new
Set
()
};
}
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录