-
Notifications
You must be signed in to change notification settings - Fork 20
Run and show test results in the editor #8
Changes from 18 commits
eb14239
4d02424
ca43e68
f58fde7
d1ac430
7986bbd
7d8bec9
b872b85
77e54ee
596f5e5
4fabeec
ddc1f8e
af98d9a
469e2bf
054827a
c9ea899
3955950
2f5319d
86b5ed2
f71d3ab
f35825a
f8675ed
33d8d6e
90b97ad
ec2f444
b4868b2
e6a3385
b9a7069
e7fe057
bc8a08b
f8135c1
dbe0a32
83c0d2c
267805e
27304ac
4059434
87ce941
786465a
a35c73d
dc5cc76
4458a8a
b2d0b8b
6d6449a
7d3aa5e
099a5ff
2bd121b
71b5372
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 |
---|---|---|
@@ -0,0 +1,2 @@ | ||
'atom-workspace': | ||
'ctrl-alt-a': 'ava:toggle' | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
'use babel'; | ||
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. Use |
||
|
||
const Panel = require('./panel'); | ||
const {CompositeDisposable} = require('atom'); | ||
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. Since it goes through Babel, you can use ES2015 |
||
|
||
module.exports = { | ||
activate(state) { | ||
this.panel = new Panel(state.testingForAvaViewState); | ||
this.atomPanel = atom.workspace.addRightPanel({item: this.panel, visible: false}); | ||
this.subscriptions = new CompositeDisposable(); | ||
|
||
this.subscriptions.add( | ||
atom.commands.add('atom-workspace', 'ava:toggle', () => this.toggle())); | ||
|
||
this.subscriptions.add( | ||
atom.commands.add('atom-workspace', 'ava:run', () => this.panel.run())); | ||
}, | ||
deactivate() { | ||
this.subscriptions.dispose(); | ||
this.panel.destroy(); | ||
}, | ||
serialize() { | ||
this.atomAva = this.panel.serialize(); | ||
}, | ||
toggle() { | ||
if (this.atomPanel.isVisible()) { | ||
this.panel.cancelExecution(); | ||
this.atomPanel.hide(); | ||
} else { | ||
this.atomPanel.show(); | ||
this.panel.run(); | ||
} | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
'use babel'; | ||
|
||
const TestRunnerProcess = require('./test-runner-process'); | ||
|
||
class Panel { | ||
constructor(serializedState, testRunnerProcess = new TestRunnerProcess()) { | ||
this.testRunnerProcess = testRunnerProcess; | ||
|
||
this.testRunnerProcess.on('assert', result => this.renderAssert(result)); | ||
this.testRunnerProcess.on('complete', results => this.renderFinalReport(results)); | ||
|
||
this.renderBase(); | ||
} | ||
|
||
renderBase() { | ||
this.element = this.createElement('div', 'ava'); | ||
const message = this.createElement('div', 'message'); | ||
message.textContent = 'AVA test runner'; | ||
this.element.appendChild(message); | ||
|
||
this.executing = this.createElement('div', 'executing'); | ||
this.executing.textContent = 'Loading'; | ||
this.executing.style.display = 'none'; | ||
this.element.appendChild(this.executing); | ||
|
||
this.testsContainer = this.createElement('div', 'test-container'); | ||
this.element.appendChild(this.testsContainer); | ||
} | ||
|
||
run() { | ||
this.displayExecutingIndicator(); | ||
this.testsContainer.innerHTML = ''; | ||
const editor = atom.workspace.getActiveTextEditor(); | ||
const currentFileName = editor.buffer.file.path; | ||
this.testRunnerProcess.run(currentFileName); | ||
} | ||
|
||
cancelExecution() { | ||
this.hideExecutingIndicator(); | ||
this.testRunnerProcess.cancelExecution(); | ||
} | ||
|
||
renderAssert(assert) { | ||
const newTest = this.createElement('div', 'test'); | ||
const status = (assert.ok) ? 'OK' : 'NO'; | ||
newTest.textContent = `${status} - ${assert.name}`; | ||
this.testsContainer.appendChild(newTest); | ||
} | ||
|
||
renderFinalReport(results) { | ||
this.hideExecutingIndicator(); | ||
|
||
const summary = this.createElement('div', 'summary'); | ||
const percentage = Math.round((results.pass / results.count) * 100); | ||
summary.textContent = `${results.count} total - ${percentage}% passed`; | ||
|
||
this.testsContainer.appendChild(summary); | ||
} | ||
|
||
createElement(elementType, cssClass = null) { | ||
const element = document.createElement(elementType); | ||
if (cssClass) { | ||
element.classList.add(cssClass); | ||
} | ||
return element; | ||
} | ||
|
||
displayExecutingIndicator() { | ||
this.executing.style.display = 'block'; | ||
} | ||
|
||
hideExecutingIndicator() { | ||
this.executing.style.display = 'none'; | ||
} | ||
|
||
serialize() { } | ||
|
||
destroy() { | ||
this.element.remove(); | ||
} | ||
} | ||
|
||
module.exports = Panel; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
'use babel'; | ||
|
||
const EventEmitter = require('events'); | ||
const ChildProcess = require('child_process'); | ||
|
||
class TerminalCommandExecutor extends EventEmitter { | ||
constructor() { | ||
super(); | ||
EventEmitter.call(this); | ||
|
||
this.dataReceivedEventName = 'dataReceived'; | ||
this.dataFinishedEventName = 'dataFinished'; | ||
} | ||
|
||
run(command, destinyFolder = null) { | ||
this.cancelExecution(); | ||
|
||
this.command = command; | ||
this.destinyFolder = destinyFolder; | ||
|
||
const spawn = ChildProcess.spawn; | ||
|
||
this.terminal = spawn('bash', ['-l']); | ||
this.terminal.on('close', statusCode => this._streamClosed(statusCode)); | ||
this.terminal.stdout.on('data', data => this._stdOutDataReceived(data)); | ||
this.terminal.stderr.on('data', data => this._stdErrDataReceived(data)); | ||
|
||
const terminalCommand = (this.destinyFolder) ? | ||
`cd \"${this.destinyFolder}\" && ${this.command}\n` : | ||
`${this.command}\n`; | ||
|
||
this.terminal.stdin.write(terminalCommand); | ||
this.terminal.stdin.write('exit\n'); | ||
} | ||
|
||
cancelExecution() { | ||
if (this.terminal) { | ||
this.terminal.kill('SIGKILL'); | ||
this.terminal = null; | ||
} | ||
} | ||
|
||
_stdOutDataReceived(newData) { | ||
this.emit(this.dataReceivedEventName, newData.toString()); | ||
} | ||
|
||
_stdErrDataReceived(newData) { | ||
this.emit(this.dataReceivedEventName, newData.toString()); | ||
} | ||
|
||
_streamClosed(code) { | ||
if (code === 1) { | ||
this.emit(this.dataFinishedEventName, code); | ||
} | ||
} | ||
} | ||
|
||
module.exports = TerminalCommandExecutor; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
'use babel'; | ||
|
||
const parser = require('tap-parser'); | ||
const EventEmitter = require('events'); | ||
const TerminalCommandExecutor = require('./terminal-command-executor'); | ||
|
||
class FilePathParser { | ||
parse(filePath) { | ||
// TODO: Fix parsing of folders and files | ||
const folder = filePath.substring(0, filePath.lastIndexOf('/') + 1); | ||
const file = filePath.split('/').pop(); | ||
return {folder, file}; | ||
} | ||
} | ||
|
||
class TestRunnerProcess extends EventEmitter { | ||
constructor( | ||
executor = new TerminalCommandExecutor(), | ||
filePathParser = new FilePathParser()) { | ||
super(); | ||
|
||
this.eventHandlers = {}; | ||
this.filePathParser = filePathParser; | ||
this.terminalCommandExecutor = executor; | ||
this.terminalCommandExecutor.on('dataReceived', data => this._addAvaOutput(data)); | ||
this.terminalCommandExecutor.on('dataFinished', () => this._endAvaOutput()); | ||
|
||
EventEmitter.call(this); | ||
} | ||
|
||
run(filePath) { | ||
this.parser = this._getParser(); | ||
this._setHandlersOnParser(this.parser); | ||
|
||
const filePathParseResult = this.filePathParser.parse(filePath); | ||
const command = `ava ${filePathParseResult.file} --tap`; | ||
|
||
this.terminalCommandExecutor.run(command, filePathParseResult.folder); | ||
} | ||
|
||
cancelExecution() { | ||
this.terminalCommandExecutor.cancelExecution(); | ||
} | ||
|
||
_setHandlersOnParser(parser) { | ||
parser.on('assert', assert => this.emit('assert', assert)); | ||
parser.on('complete', results => this.emit('complete', results)); | ||
} | ||
|
||
_addAvaOutput(data) { | ||
this.parser.write(data); | ||
} | ||
|
||
_endAvaOutput() { | ||
this.parser.end(); | ||
} | ||
|
||
_getParser() { | ||
return parser(); | ||
} | ||
|
||
destroy() { | ||
this.terminalCommandExecutor.destroy(); | ||
} | ||
} | ||
|
||
module.exports = TestRunnerProcess; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
"name": "ava", | ||
"version": "0.2.0", | ||
"description": "Snippets for AVA", | ||
"main": "./lib/main", | ||
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. Just rename 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 working for me... not sure if I'm missing something. |
||
"license": "MIT", | ||
"repository": "sindresorhus/atom-ava", | ||
"private": true, | ||
|
@@ -28,17 +29,25 @@ | |
"scripts": { | ||
"test": "xo" | ||
}, | ||
"activationCommands": { | ||
"atom-workspace": "ava:toggle" | ||
}, | ||
"keywords": [ | ||
"snippets", | ||
"test", | ||
"runner", | ||
"ava", | ||
"mocha" | ||
], | ||
"dependencies": { | ||
"tap-parser": "^1.2.2", | ||
"ava": "^0.13.0" | ||
}, | ||
"devDependencies": { | ||
"xo": "*" | ||
}, | ||
"xo": { | ||
"envs": ["browser", "node", "jasmine", "atomtest"], | ||
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. One item on a separate line |
||
"esnext": true, | ||
"globals": [ | ||
"atom" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
'use babel'; | ||
|
||
class FakeSpawn { | ||
constructor() { | ||
this.commandsReceived = []; | ||
const self = this; | ||
|
||
this.stdout = { | ||
write: data => self.stdOutCallBack(data), | ||
on: (event, callback) => { | ||
self.stdOutCallBack = callback; | ||
} | ||
}; | ||
this.stderr = { | ||
write: data => self.stdErrCallBack(data), | ||
on: (event, callback) => { | ||
self.stdErrCallBack = callback; | ||
} | ||
}; | ||
this.stdin = { | ||
write: command => self.commandsReceived.push(command) | ||
}; | ||
} | ||
|
||
kill() { } | ||
|
||
on(event, callback) { | ||
this.mainCallBack = callback; | ||
} | ||
|
||
emulateClose() { | ||
this.mainCallBack(1); | ||
} | ||
} | ||
|
||
module.exports = FakeSpawn; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
'use babel'; | ||
|
||
describe('TestingForAva', () => { | ||
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. Do we have to use Jasmine for testing Atom packages or could we use AVA? 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. Yeah, is more consistent and makes a lot of sense use AVA. I have been thinking about that since the beginning. My only doubt is if it can lead to any problems, just because Jasmine in the de facto option in Atom. Probably is time to make some tests. |
||
const packageName = 'ava'; | ||
const mainSelector = '.ava'; | ||
const toggleCommand = 'ava:toggle'; | ||
let workspaceElement = []; | ||
let activationPromise = []; | ||
|
||
beforeEach(() => { | ||
workspaceElement = atom.views.getView(atom.workspace); | ||
activationPromise = atom.packages.activatePackage(packageName); | ||
|
||
const editor = {buffer: {file: {path: '/this/is/a/path/file.js'}}}; | ||
|
||
spyOn(atom.workspace, 'getActiveTextEditor').andReturn(editor); | ||
}); | ||
|
||
describe('when the ava:toggle event is triggered', () => { | ||
it('hides and shows the view', () => { | ||
jasmine.attachToDOM(workspaceElement); | ||
|
||
expect(workspaceElement.querySelector(mainSelector)).not.toExist(); | ||
|
||
atom.commands.dispatch(workspaceElement, toggleCommand); | ||
|
||
waitsForPromise(() => activationPromise); | ||
|
||
runs(() => { | ||
const mainElement = workspaceElement.querySelector(mainSelector); | ||
expect(mainElement).toBeVisible(); | ||
atom.commands.dispatch(workspaceElement, toggleCommand); | ||
expect(mainElement).not.toBeVisible(); | ||
}); | ||
}); | ||
}); | ||
}); |
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.
Can you use a JSON file here instead?