UNPKG

testem

Version:

Test'em 'scripts! Javascript Unit testing made easy.

393 lines (339 loc) 9.64 kB
'use strict'; /* appview.js ========== The actual AppView. This encapsulates the entire UI. */ const StyledString = require('styled_string'); const log = require('npmlog'); const Backbone = require('backbone'); const View = require('./view'); const tabs = require('./runner_tabs'); const constants = require('./constants'); const RunnerTab = tabs.RunnerTab; const RunnerTabs = tabs.RunnerTabs; const Screen = require('./screen'); const pad = require('../../utils/strutils').pad; const ErrorMessagesPanel = require('./error_messages_panel'); const Runner = require('./runner'); module.exports = View.extend({ defaults: { currentTab: 0, atLeastOneRunner: false }, initialize(silent, out, config, app, screen) { this.name = 'Testem'; this.config = config; this.app = app; this.viewRunners = new Backbone.Collection(); this.x = {}; screen = screen || new Screen(); this.set('screen', screen); this.on('ctrl-c', () => app.exit()); this.initCharm(); this.on('inputChar', this.onInputChar.bind(this)); let runnerTabs = this.runnerTabs = new RunnerTabs([], { appview: this, screen: screen }); this.set({ runnerTabs: runnerTabs }); runnerTabs.on('add', () => runnerTabs.render()); this.app.on('runnerAdded', runner => this.runnerAdded(runner)); this.app.on('runnerRemoved', runner => this.runnerRemoved(runner)); runnerTabs.on('add', () => runnerTabs.render()); this.on('change:atLeastOneRunner', (atLeastOne) => { if (atLeastOne && this.get('currentTab') < 0) { this.set('currentTab', 0); } this.renderMiddle(); this.renderBottom(); }); this.on('change:lines change:cols', () => { this.render(); }); this.errorMessagesPanel = new ErrorMessagesPanel({ appview: this, text: '', screen: screen }); this.errorMessagesPanel.on('change:text', (m, text) => { this.set('isPopupVisible', !!text); }); this.startMonitorTermSize(); }, runnerAdded(runner) { let viewRunner = new Runner(runner); this.viewRunners.push(viewRunner); let idx = this.viewRunners.length - 1; this.x[runner.launcherId] = viewRunner; log.info('runnerAdded', runner.name(), runner.launcherId); let tab = new RunnerTab({ runner: viewRunner, index: idx, appview: this, screen: this.get('screen') }); this.runnerTabs.push(tab); this.set('atLeastOneRunner', this.viewRunners.length > 0); }, runnerRemoved(runner) { log.info('runnerRemoved'); this.viewRunners.remove(this.x[runner.launcherId]); this.set('atLeastOneRunner', this.viewRunners.length > 0); }, _showError(titleText, err) { let title = new StyledString(titleText + '\n ').foreground('red'); if (err) { let errMsgs = new StyledString('\n' + err.name) .foreground('white') .concat(new StyledString('\n' + err.message).foreground('red')); title += errMsgs; } this.setErrorPopupMessage(title); if (err) { log.log('warn', titleText); } else { log.log('warn', titleText, { name: err.name, message: err.message }); } }, onInputChar(chr, i) { if (chr === 'q') { log.info('Got keyboard Quit command'); this.app.exit(); } else if (i === 13) { // ENTER log.info('Got keyboard Start Tests command'); this.app.triggerRun('Triggered manually by pressing enter'); } else if (chr === 'p') { this.app.paused = !this.app.paused; this.renderBottom(); } }, initCharm() { let screen = this.get('screen'); screen.reset(); screen.erase('screen'); screen.cursor(false); screen.on('data', this.onScreenData.bind(this)); screen.removeAllListeners('^C'); screen.on('^C', () => this.trigger('ctrl-c')); }, startMonitorTermSize() { this.updateScreenDimensions(); process.stdout.on('resize', () => { let cols = process.stdout.columns; let lines = process.stdout.rows; if (cols !== this.get('cols') || lines !== this.get('lines')) { this.updateScreenDimensions(); } }); }, updateScreenDimensions() { let screen = this.get('screen'); let cols = process.stdout.columns; let lines = process.stdout.rows; screen.enableScroll(constants.LogPanelUnusedLines, lines - 1); this.set({ cols: cols, lines: lines }); this.updateErrorMessagesPanelSize(); }, updateErrorMessagesPanelSize() { this.errorMessagesPanel.set({ line: 2, col: 4, width: this.get('cols') - 8, height: this.get('lines') - 4 }); }, render() { this.renderTop(); if (!this.get('atLeastOneRunner')) { this.renderMiddle(); } this.renderBottom(); }, renderTop() { if (this.isPopupVisible()) { return; } let screen = this.get('screen'); let url = this.config.get('url'); let cols = this.get('cols'); screen .position(0, 1) .write(pad('TEST\u0027EM \u0027SCRIPTS!', cols, ' ', 1)) .position(0, 2) .write(pad('Open the URL below in a browser to connect.', cols, ' ', 1)) .position(0, 3) .display('underscore') .write(url, cols, ' ', 1) .display('reset') .position(url.length + 1, 3) .write(pad('', cols - url.length, ' ', 1)); }, renderMiddle() { if (this.isPopupVisible()) { return; } if (this.viewRunners.length > 0) { return; } let screen = this.get('screen'); let lines = this.get('lines'); let cols = this.get('cols'); let textLineIdx = Math.floor(lines / 2 + 2); for (let i = constants.LogPanelUnusedLines; i < lines; i++) { let text = (i === textLineIdx ? 'Waiting for runners...' : ''); screen .position(0, i) .write(pad(text, cols, ' ', 2)); } }, renderBottom() { if (this.isPopupVisible()) { return; } let screen = this.get('screen'); let cols = this.get('cols'); let pauseStatus = this.app.paused ? '; p to unpause (PAUSED)' : '; p to pause'; let msg = ( !this.get('atLeastOneRunner') ? 'q to quit' : 'Press ENTER to run tests; q to quit' ); msg = '[' + msg + pauseStatus + ']'; screen .position(0, this.get('lines')) .write(pad(msg, cols - 1, ' ', 1)); }, runners() { return this.viewRunners; }, currentRunnerTab() { let idx = this.get('currentTab'); return this.runnerTabs.at(idx); }, onScreenData(buf) { try { let chr = String(buf).charAt(0); let i = chr.charCodeAt(0); let key = (buf[0] === 27 && buf[1] === 91) ? buf[2] : null; let currentRunnerTab = this.currentRunnerTab(); let splitPanel = currentRunnerTab && currentRunnerTab.splitPanel; //log.info([buf[0], buf[1], buf[2]].join(',')) if (key === 67) { // right arrow this.nextTab(); } else if (key === 68) { // left arrow this.prevTab(); } else if (key === 66) { // down arrow splitPanel.scrollDown(); } else if (key === 65) { // up arrow splitPanel.scrollUp(); } else if (chr === '\t') { splitPanel.toggleFocus(); } else if (chr === ' ' && splitPanel) { splitPanel.pageDown(); } else if (chr === 'b') { splitPanel.pageUp(); } else if (chr === 'u') { splitPanel.halfPageUp(); } else if (chr === 'd') { splitPanel.halfPageDown(); } this.trigger('inputChar', chr, i); } catch (e) { log.error('In onInputChar: ' + e + '\n' + e.stack); } }, nextTab() { let currentTab = this.get('currentTab'); currentTab++; if (currentTab >= this.runners().length) { currentTab = 0; } this.set('currentTab', currentTab); }, prevTab() { let currentTab = this.get('currentTab'); currentTab--; if (currentTab < 0) { currentTab = this.runners().length - 1; } this.set('currentTab', currentTab); }, setErrorPopupMessage(msg) { this.errorMessagesPanel.set('text', msg); }, clearErrorPopupMessage() { this.errorMessagesPanel.set('text', ''); this.render(); }, isPopupVisible() { return !!this.get('isPopupVisible'); }, setRawMode() { if (process.stdin.isTTY) { process.stdin.setRawMode(false); } }, report(browserName, result) { if (isTestemItself(result)) { return this._showError('Testem error', result.error); } let runner = this.x[result.launcherId]; if (!runner) { return; } runner.report(result); if (result.logs) { result.logs.forEach(log => runner.get('messages').push(log)); } }, onStart(browserName, opts) { if (isTestemItself(opts)) { return this.clearErrorPopupMessage(); } let runner = this.x[opts.launcherId]; if (!runner) { return; } log.info(browserName, 'onStart'); runner.onStart(); }, onEnd(browserName, opts) { if (isTestemItself(opts)) { return; } let runner = this.x[opts.launcherId]; if (!runner) { return; } log.info(browserName, 'onEnd'); runner.onEnd(); }, finish() { this.cleanup(); }, cleanup(cb) { let screen = this.get('screen'); screen.display('reset'); screen.erase('screen'); screen.position(0, 0); screen.enableScroll(); screen.cursor(true); this.setRawMode(false); screen.destroy(); if (cb) { cb(); } } }); function isTestemItself(opts) { return opts.launcherId === 0; }