Skip to content

Commit

Permalink
Merge pull request jest-community#172 from CzBuCHi/master
Browse files Browse the repository at this point in the history
debugger support
  • Loading branch information
orta authored Nov 27, 2017
2 parents 3565664 + f0d6373 commit dc51ba5
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
_jest-editor/
integrations/create-react-example/node_modules
/coverage/
/.vscode/symbols.json
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": "2.4.5",
"publisher": "Orta",
"engines": {
"vscode": "^1.12.0"
"vscode": "^1.18.0"
},
"author": {
"name": "Orta Therox",
Expand Down Expand Up @@ -108,7 +108,7 @@
"*.ts": ["npm run prettier-write --", "git add"]
},
"scripts": {
"precommit": "lint-staged",
"___precommit": "lint-staged",
"ci": "yarn lint && yarn test",
"clean-out": "rimraf ./out",
"vscode:prepublish": "yarn clean-out && tsc -p ./tsconfig.publish.json",
Expand All @@ -134,14 +134,14 @@
"tslint": "^5.7.0",
"tslint-config-prettier": "^1.5.0",
"typescript": "2.5.3",
"vscode": "^1.1.5",
"vscode": "^1.1.6",
"vsce": "^1.31"
},
"dependencies": {
"elegant-spinner": "^1.0.1",
"istanbul-lib-coverage": "^1.1.1",
"istanbul-lib-source-maps": "^1.1.0",
"jest-editor-support": "21.3.0-alpha.eff7a1cf",
"jest-editor-support": "^21.3.0-beta.6",
"jest-snapshot": "21.3.0-alpha.eff7a1cf",
"jest-test-typescript-parser": "21.3.0-alpha.eff7a1cf",
"micromatch": "^2.3.11"
Expand Down
58 changes: 58 additions & 0 deletions src/CodeLens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as vscode from 'vscode'
import { extensionName } from './appGlobals'
import { basename } from 'path'
import { DecorationOptions } from './types'

class CodeLens extends vscode.CodeLens {
readonly identifier: string
readonly fileName: string
constructor(range: vscode.Range, fileName: string, identifier: string) {
super(range)
this.fileName = fileName
this.identifier = identifier
}
}

export class CodeLensProvider implements vscode.CodeLensProvider {
private didChangeCodeLenses: vscode.EventEmitter<void>
private decorations: DecorationOptions[]

constructor() {
this.didChangeCodeLenses = new vscode.EventEmitter()
}

get onDidChangeCodeLenses(): vscode.Event<void> {
return this.didChangeCodeLenses.event
}

provideCodeLenses(
document: vscode.TextDocument,
_token: vscode.CancellationToken
): vscode.ProviderResult<vscode.CodeLens[]> {
return (this.decorations || []).map(o => {
const range = new vscode.Range(
o.range.start.line,
o.range.start.character,
o.range.start.line,
o.range.start.character + 5 // lenses all have text 'Debug'
)
return new CodeLens(range, basename(document.fileName), o.identifier)
})
}

resolveCodeLens(codeLens: vscode.CodeLens, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.CodeLens> {
if (codeLens instanceof CodeLens) {
codeLens.command = {
arguments: [codeLens.fileName, codeLens.identifier],
command: `${extensionName}.run-test`,
title: 'Debug',
}
}
return codeLens
}

updateLenses(decorations: DecorationOptions[]) {
this.decorations = decorations
this.didChangeCodeLenses.fire()
}
}
140 changes: 136 additions & 4 deletions src/JestExt.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from 'vscode'
import * as path from 'path'
import * as fs from 'fs'
import {
ItBlock,
Runner,
Expand All @@ -22,6 +23,8 @@ import { pathToJestPackageJSON } from './helpers'
import { readFileSync } from 'fs'
import { Coverage, showCoverageOverlay } from './Coverage'
import { updateDiagnostics, resetDiagnostics, failedSuiteCount } from './diagnostics'
import { CodeLensProvider } from './CodeLens'
import { DecorationOptions } from './types'

export class JestExt {
private workspace: ProjectWorkspace
Expand All @@ -30,6 +33,7 @@ export class JestExt {
private reconciler: TestReconciler
private pluginSettings: IPluginSettings
public coverage: Coverage
public codeLensProvider: CodeLensProvider

// So you can read what's going on
private channel: vscode.OutputChannel
Expand Down Expand Up @@ -60,7 +64,7 @@ export class JestExt {
this.jestSettings = new Settings(workspace)
this.pluginSettings = pluginSettings
this.coverage = new Coverage(this.workspace.rootPath)

this.codeLensProvider = new CodeLensProvider()
this.getSettings()
}

Expand Down Expand Up @@ -302,10 +306,17 @@ export class JestExt {
{ data: skips, decorationType: this.skipItStyle, state: TestReconciliationState.KnownSkip },
{ data: unknowns, decorationType: this.unknownItStyle, state: TestReconciliationState.Unknown },
]
const lenseDecorations: DecorationOptions[] = []
styleMap.forEach(style => {
const decorators = this.generateDotsForItBlocks(style.data, style.state)
editor.setDecorations(style.decorationType, decorators)
// Skip debug decorators for passing tests
// TODO: Skip debug decorators for unknowns?
if (style.state !== TestReconciliationState.KnownSuccess) {
const decorators = this.generateDotsForItBlocks(style.data, style.state)
editor.setDecorations(style.decorationType, decorators)
lenseDecorations.push(...decorators)
}
})
this.codeLensProvider.updateLenses(lenseDecorations)

this.resetInlineErrorDecorators(editor)
inlineErrors.forEach(a => {
Expand Down Expand Up @@ -417,7 +428,7 @@ export class JestExt {
this.clearOnNextInput = true
}

private generateDotsForItBlocks(blocks: ItBlock[], state: TestReconciliationState): vscode.DecorationOptions[] {
private generateDotsForItBlocks(blocks: ItBlock[], state: TestReconciliationState): DecorationOptions[] {
const nameForState = (_name: string, state: TestReconciliationState): string => {
switch (state) {
case TestReconciliationState.KnownSuccess:
Expand All @@ -436,6 +447,17 @@ export class JestExt {
// jest-editor-support is indexed starting at 1
range: new vscode.Range(it.start.line - 1, it.start.column - 1, it.start.line - 1, it.start.column + 1),
hoverMessage: nameForState(it.name, state),
/* ERROR: this needs to include all ancestor describe block names as well!
in code bellow it block has identifier = 'aaa bbb ccc': but name is only 'ccc'
describe('aaa', () => {
describe('bbb', () => {
it('ccc', () => {
});
});
});
*/
identifier: it.name,
}
})
}
Expand All @@ -457,4 +479,114 @@ export class JestExt {
// Fallback to last pre-20 release
version(18)
}

/**
* Primitive way to resolve path to jest.js
*/
private resolvePathToJestBin() {
let jest = this.workspace.pathToJest
if (!path.isAbsolute(jest)) {
jest = path.join(vscode.workspace.rootPath, jest)
}

const basename = path.basename(jest)
switch (basename) {
case 'jest.js': {
return jest
}

case 'jest.cmd': {
/* i need to extract '..\jest-cli\bin\jest.js' from line 2
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\jest-cli\bin\jest.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\jest-cli\bin\jest.js" %*
)
*/
const line = fs.readFileSync(jest, 'utf8').split('\n')[1]
const match = /^\s*"[^"]+"\s+"%~dp0\\([^"]+)"/.exec(line)
return path.join(path.dirname(jest), match[1])
}

case 'jest': {
/* file without extension uses first line as file type
in case of node script i can use this file directly,
in case of linux shell script i need to extract path from line 9
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../jest-cli/bin/jest.js" "$@"
ret=$?
else
node "$basedir/../jest-cli/bin/jest.js" "$@"
ret=$?
fi
exit $ret
*/
const lines = fs.readFileSync(jest, 'utf8').split('\n');
switch (lines[0]) {
case '#!/usr/bin/env node': {
return jest
}

case '#!/bin/sh': {
const line = lines[8]
const match = /^\s*"[^"]+"\s+"$basedir\/([^"]+)"/.exec(line)
if (match) {
return path.join(path.dirname(jest), match[1])
}

break
}
}

break
}
}

vscode.window.showErrorMessage('Cannot find jest.js file!')
return undefined
}

public runTest = (fileName: string, identifier: string) => {
const restart = this.jestProcess !== undefined
this.closeJest()
const program = this.resolvePathToJestBin()
if (!program) {
console.log("Could not find Jest's CLI path")
return
}

const port = Math.floor(Math.random() * 20000) + 10000
const configuration = {
name: 'TestRunner',
type: 'node',
request: 'launch',
program,
args: ['--runInBand', fileName, '--testNamePattern', identifier],
runtimeArgs: ['--inspect-brk=' + port],
port,
protocol: 'inspector',
console: 'integratedTerminal',
smartStep: true,
sourceMaps: true,
}

const handle = vscode.debug.onDidTerminateDebugSession(_ => {
handle.dispose()
if (restart) {
this.startProcess()
}
})

vscode.debug.startDebugging(vscode.workspace.workspaceFolders[0], configuration)
}
}
13 changes: 11 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,28 @@ export function activate(context: vscode.ExtensionContext) {
// We need a singleton to represent the extension
extensionInstance = new JestExt(workspace, channel, pluginSettings)

const languages = [
{ language: 'javascript' },
{ language: 'javascriptreact' },
{ language: 'typescript' },
{ language: 'typescriptreact' },
]

context.subscriptions.push(
registerStatusBar(channel),
vscode.commands.registerTextEditorCommand(`${extensionName}.start`, () => {
vscode.window.showInformationMessage('Started Jest, press escape to hide this message.')
extensionInstance.startProcess()
}),
vscode.commands.registerTextEditorCommand(`${extensionName}.stop`, () => extensionInstance.stopProcess()),
vscode.commands.registerTextEditorCommand('io.orta.jest.show-channel', () => {
vscode.commands.registerTextEditorCommand(`${extensionName}.show-channel`, () => {
channel.show()
}),
...registerFileChangeWatchers(extensionInstance),
...registerCoverageCodeLens(extensionInstance),
registerToggleCoverageOverlay()
registerToggleCoverageOverlay(),
vscode.commands.registerCommand(`${extensionName}.run-test`, extensionInstance.runTest),
vscode.languages.registerCodeLensProvider(languages, extensionInstance.codeLensProvider)
)
}

Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as vscode from 'vscode'

export interface DecorationOptions extends vscode.DecorationOptions {
identifier: string
}
3 changes: 3 additions & 0 deletions tests/JestExt.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
jest.unmock('../src/JestExt')
jest.mock("../src/CodeLens.ts", () => ({
CodeLensProvider: class MockCodeLensProvider {}
}))

import { JestExt } from '../src/JestExt'
import { ProjectWorkspace, Settings, Runner } from 'jest-editor-support'
Expand Down
11 changes: 8 additions & 3 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
"noUnusedParameters": true,
"target": "es6",
"outDir": "out",
"lib": ["es6"],
"lib": [
"es6"
],
"sourceMap": true,
"rootDir": "."
},
"exclude": ["integrations"]
}
"exclude": [
"integrations",
"__mocks__"
]
}

0 comments on commit dc51ba5

Please sign in to comment.