-
Notifications
You must be signed in to change notification settings - Fork 293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sidebar support #357
Sidebar support #357
Changes from 20 commits
c3276aa
4e40c11
50e0088
039dae9
15d9fd2
2732054
b0df658
d86f461
67d3e2b
34ecbef
40eec87
2d19ca3
084c4e1
cce9684
bb78173
4f9ad71
7ccdd30
f576f45
9ea3637
832c1f6
02ec9ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ import { hasDocument, isOpenInMultipleEditors } from './editor' | |
import { CoverageOverlay } from './Coverage/CoverageOverlay' | ||
import { JestProcess, JestProcessManager } from './JestProcessManagement' | ||
import { isWatchNotSupported, WatchMode } from './Jest' | ||
import { JestTreeProvider } from './SideBar/JestTreeProvider' | ||
import * as messaging from './messaging' | ||
|
||
export class JestExt { | ||
|
@@ -34,6 +35,8 @@ export class JestExt { | |
public debugCodeLensProvider: DebugCodeLensProvider | ||
debugConfigurationProvider: DebugConfigurationProvider | ||
|
||
sidebarProvider: JestTreeProvider | ||
|
||
// So you can read what's going on | ||
private channel: vscode.OutputChannel | ||
|
||
|
@@ -83,6 +86,8 @@ export class JestExt { | |
) | ||
this.debugConfigurationProvider = new DebugConfigurationProvider() | ||
|
||
this.sidebarProvider = new JestTreeProvider(this.testResultProvider, context, pluginSettings.sidebar) | ||
|
||
this.jestProcessManager = new JestProcessManager({ | ||
projectWorkspace: workspace, | ||
runAllTestsFirstInWatchMode: this.pluginSettings.runAllTestsFirst, | ||
|
@@ -255,6 +260,8 @@ export class JestExt { | |
? updatedSettings.debugCodeLens.showWhenTestStateIn | ||
: [] | ||
|
||
this.sidebarProvider.updateSettings(updatedSettings.sidebar) | ||
|
||
this.stopProcess() | ||
|
||
setTimeout(() => { | ||
|
@@ -354,6 +361,7 @@ export class JestExt { | |
|
||
private testsHaveStartedRunning() { | ||
this.channel.clear() | ||
this.sidebarProvider.clear() | ||
status.running('initial full test run') | ||
} | ||
|
||
|
@@ -364,6 +372,8 @@ export class JestExt { | |
const statusList = this.testResultProvider.updateTestResults(normalizedData) | ||
updateDiagnostics(statusList, this.failDiagnostics) | ||
|
||
this.sidebarProvider.refresh(data) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure if we should process all the tests up front like this, giving we will only need to display the tree when the file is actually displayed in the editor... please see if you can move the logic to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The tree is displayed every time the tests are run not only when the file is selected. You probably mean the outline tree but what we are showing here is the test sidebar tree. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. indeed, I was thinking about the outline view, my bad. trying it again on vscode-jest itself, added a few fake tests and encountered the following issues:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added support for root tests, removed the folder name from filename and changed the default setting to show files by default. My practice has always been to have all tests inside describe and to have only one describe per file. That's why I chose the implementation I did. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Please take a look at triggerUpdateSettings |
||
|
||
const failedFileCount = failedSuiteCount(this.failDiagnostics) | ||
if (failedFileCount <= 0 && normalizedData.success) { | ||
status.success() | ||
|
@@ -423,6 +433,12 @@ export class JestExt { | |
} | ||
} | ||
|
||
public showTest = async (fileName: string, line: number) => { | ||
const textRange = new vscode.Range(line, 0, line, 0) | ||
const doc = await vscode.workspace.openTextDocument(fileName) | ||
await vscode.window.showTextDocument(doc, { selection: textRange }) | ||
} | ||
|
||
onDidCloseTextDocument(document: vscode.TextDocument) { | ||
this.removeCachedTestResults(document) | ||
this.removeCachedDecorationTypes(document) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
import * as vscode from 'vscode' | ||
import { TestResultFile, TestResultSuite, TestResultTest } from './TestResultTree' | ||
import { extensionName } from '../appGlobals' | ||
|
||
export type NodeStatus = 'unknown' | 'passed' | 'failed' | 'skipped' | ||
|
||
export class JestTreeNode extends vscode.TreeItem { | ||
constructor( | ||
label: string, | ||
public readonly children: JestTreeNode[], | ||
context: SidebarContext, | ||
public contextValue: string = '', | ||
public readonly status: NodeStatus = 'unknown' | ||
) { | ||
super(label, children.length > 0 ? context.getTreeItemCollapsibleState() : vscode.TreeItemCollapsibleState.None) | ||
|
||
if (this.status === 'unknown') { | ||
this.status = this.calculateStatus() | ||
} | ||
|
||
this.iconPath = context.getIconPath(this.status) | ||
} | ||
|
||
get tooltip(): string { | ||
return this.terseTooltip | ||
} | ||
|
||
get terseTooltip(): string { | ||
if (this.children.length > 0) { | ||
return this.children.map(c => `${this.label} > ` + c.terseTooltip.replace(/\n/g, `\n${this.label} > `)).join('\n') | ||
} | ||
const prettyStatus = this.status.charAt(0).toUpperCase() + this.status.toLowerCase().slice(1) | ||
return `${this.label} ● ${prettyStatus}` | ||
} | ||
|
||
calculateStatus(): NodeStatus { | ||
if (this.children.length > 0) { | ||
if (this.children.find(c => c.status === 'failed')) { | ||
return 'failed' | ||
} | ||
if (this.children.find(c => c.status === 'skipped')) { | ||
return 'skipped' | ||
} | ||
if (!this.children.find(c => c.status !== 'passed')) { | ||
return 'passed' | ||
} | ||
} | ||
|
||
return 'unknown' | ||
} | ||
} | ||
|
||
export class JestTreeNodeForTest extends JestTreeNode { | ||
constructor(private test: TestResultTest, context: SidebarContext) { | ||
super(test.name, [], context, 'test', convertTestStatus(test.status)) | ||
} | ||
|
||
get tooltip(): string { | ||
if (this.test.failureMessages.length > 0) { | ||
return `${this.terseTooltip}\n\n${this.test.failureMessages.join('\n')}` | ||
} | ||
return this.terseTooltip | ||
} | ||
|
||
get command(): vscode.Command { | ||
return { | ||
title: 'Show test', | ||
command: `${extensionName}.show-test`, | ||
arguments: [this.test.filename, this.test.line], | ||
} | ||
// codeLens.command = { | ||
// arguments: [codeLens.fileName, escapeRegExp(codeLens.testName)], | ||
// command: `${extensionName}.run-test`, | ||
// title: 'Debug', | ||
// } | ||
} | ||
} | ||
|
||
export interface ISidebarSettings { | ||
showFiles: boolean | ||
autoExpand: boolean | ||
} | ||
|
||
export class SidebarContext { | ||
showFiles: boolean | ||
autoExpand: boolean | ||
|
||
constructor(private extensionContext: vscode.ExtensionContext, settings: ISidebarSettings) { | ||
this.updateSettings(settings) | ||
} | ||
|
||
updateSettings(settings: ISidebarSettings): void { | ||
this.autoExpand = settings.autoExpand | ||
this.showFiles = settings.showFiles | ||
} | ||
|
||
getTreeItemCollapsibleState() { | ||
return this.autoExpand ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed | ||
} | ||
|
||
getIconPath(iconColor: string) { | ||
return { | ||
light: this.extensionContext.asAbsolutePath('./src/SideBar/light-' + iconColor + '.svg'), | ||
dark: this.extensionContext.asAbsolutePath('./src/SideBar/dark-' + iconColor + '.svg'), | ||
} | ||
} | ||
} | ||
|
||
function convertTestStatus(testStatus: 'failed' | 'passed' | 'pending'): NodeStatus { | ||
return testStatus === 'pending' ? 'skipped' : testStatus | ||
} | ||
|
||
export function generateTree(files: undefined | TestResultFile[], context: SidebarContext): JestTreeNode { | ||
const rootNode = new JestTreeNode( | ||
'Tests', | ||
files === undefined ? [] : getNodesFromFiles(files, context), | ||
context, | ||
'root' | ||
) | ||
rootNode.collapsibleState = vscode.TreeItemCollapsibleState.Expanded | ||
return rootNode | ||
} | ||
|
||
function getNodesFromFiles(files: TestResultFile[], context: SidebarContext): JestTreeNode[] { | ||
return [].concat(...files.map(f => getNodesFromFile(f, context))) | ||
} | ||
|
||
function getNodesFromFile(file: TestResultFile, context: SidebarContext): JestTreeNode[] { | ||
if (context.showFiles) { | ||
return [new JestTreeNode(cleanFilename(file.name), getNodesFromSuite(file.suite, context), context, 'file')] | ||
} else { | ||
if (file.suite.tests.length === 0) { | ||
return getNodesFromSuite(file.suite, context) | ||
} else { | ||
return file.suite.suites | ||
.map(s => new JestTreeNode(s.name, getNodesFromSuite(s, context), context, 'suite')) | ||
.concat([ | ||
new JestTreeNode( | ||
cleanFilename(file.name), | ||
file.suite.tests.map(t => new JestTreeNodeForTest(t, context)), | ||
context, | ||
'file' | ||
), | ||
]) | ||
} | ||
} | ||
} | ||
|
||
function cleanFilename(filename: string): string { | ||
const file = vscode.Uri.file(filename) | ||
const folder = vscode.workspace.getWorkspaceFolder(file) | ||
if (folder && folder.uri && file.path.toLowerCase().startsWith(folder.uri.path.toLowerCase())) { | ||
return file.path.substring(folder.uri.path.length + 1) | ||
} | ||
return filename | ||
} | ||
|
||
function getNodesFromSuite(suite: TestResultSuite, context: SidebarContext): JestTreeNode[] { | ||
return suite.suites | ||
.map(s => new JestTreeNode(s.name, getNodesFromSuite(s, context), context, 'suite')) | ||
.concat(suite.tests.map(t => new JestTreeNodeForTest(t, context))) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import * as vscode from 'vscode' | ||
import { JestTotalResults, JestFileResults } from 'jest-editor-support' | ||
import { JestTreeNode, SidebarContext, ISidebarSettings, generateTree } from './JestTreeNode' | ||
import { TestResultFile } from './TestResultTree' | ||
import { TestResultProvider } from '../TestResults' | ||
|
||
export class JestTreeProvider implements vscode.TreeDataProvider<JestTreeNode> { | ||
private _onDidChangeTreeData: vscode.EventEmitter<JestTreeNode | undefined> = new vscode.EventEmitter< | ||
JestTreeNode | undefined | ||
>() | ||
readonly onDidChangeTreeData: vscode.Event<JestTreeNode | undefined> = this._onDidChangeTreeData.event | ||
|
||
private context: SidebarContext | ||
private rootNode: JestTreeNode | ||
private allResults: JestFileResults[] | ||
|
||
constructor( | ||
private testResultProvider: TestResultProvider, | ||
extensionContext: vscode.ExtensionContext, | ||
settings: ISidebarSettings | ||
) { | ||
this.context = new SidebarContext(extensionContext, settings) | ||
this.clear() | ||
} | ||
|
||
updateSettings(settings: ISidebarSettings): void { | ||
this.context.updateSettings(settings) | ||
} | ||
|
||
clear(): void { | ||
this.allResults = [] | ||
this.rootNode = generateTree(undefined, this.context) | ||
this._onDidChangeTreeData.fire() | ||
} | ||
|
||
refresh(data: JestTotalResults): void { | ||
this.loadTestResults(data) | ||
this._onDidChangeTreeData.fire() | ||
} | ||
|
||
getTreeItem(element: JestTreeNode): vscode.TreeItem { | ||
return element | ||
} | ||
|
||
getChildren(element?: JestTreeNode): JestTreeNode[] { | ||
if (!element) { | ||
return this.getRootElements() | ||
} else { | ||
return this.getElementChildren(element) | ||
} | ||
} | ||
|
||
private loadTestResults(data: JestTotalResults) { | ||
this.allResults = this.allResults | ||
.filter(r => !data.testResults.find(r1 => r1.name === r.name)) | ||
.concat(data.testResults) | ||
.sort((a, b) => a.name.localeCompare(b.name)) | ||
const testFiles = this.allResults.map(r => this.loadTestResultsForFile(r)) | ||
this.rootNode = generateTree(testFiles, this.context) | ||
} | ||
|
||
private loadTestResultsForFile(data: JestFileResults): TestResultFile { | ||
const parsedResults = this.testResultProvider.getResults(data.name) | ||
return new TestResultFile(data, parsedResults) | ||
} | ||
|
||
private getRootElements(): JestTreeNode[] { | ||
return [this.rootNode] | ||
} | ||
|
||
private getElementChildren(node: JestTreeNode): JestTreeNode[] { | ||
return node.children | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vscode displays settings in alphabetic order, thus the 2 sidebar variables don't appear together in the setting editor. Maybe you can rename these 2 identifiers (put
sidebar
up front?).