feat(tests): add integration tests

comment

Adding test harness

Using key strokes in main window test

Tests work now

Clean up argument variables

Rename list manager and get rid of old spec-helper methods

Extract out time overrides from spec-helper

Spectron test for contenteditable

fix spec exit codes and boot mode

fix(spec): cleanup N1.sh and make specs fail with exit code 1

Revert tests and get it working in window

Move to spec_integration and add window load tester

Specs pass. Console logs still in

Remove console logs

Extract N1 Launcher ready method

Make integrated unit test runner

feat(tests): adding integration tests

Summary:
The /spectron folder got moved to /spec_integration

There are now unit tests (the old ones) run via the renamed
`script/grunt run-unit-tests`

There are now integration tests run via the command `script/grunt
run-integration-tests`.

There are two types of integration tests:
1. Tests that operate on the whole app via Selenium/Chromedriver. These
tests have access to Spectron APIs but do NOT have access to any JS object
running inside the application. See the `app-boot-spec.es6` for an example
of these tests. This is tricky because we want to test the Main window,
but Spectron may latch onto any other of our loading windows. Code in
`integration-helper` give us an API that finds and loads the main window
so we can test it

2. Tests that run in the unit test suite that need Spectron to perform
integration-like behavior. These are the contentedtiable specs. The
Spectron server is accessed from the app and can be used to trigger
actions on the running app, from the app. These tests use the
windowed-test runner so Spectron can identify whether the tests have
completed, passed, or failed. Unfortunately Spectron can't access the logs
, nor the exit code of the test script thereby forcing us to parse the
HTML DOM. (Note this is still a WIP)

I also revamped the `N1.sh` file when getting the launch arguments to work
properly. It's much cleaner. We didn't need most of the data.

Test Plan: new tests

Reviewers: juan, bengotow

Differential Revision: https://phab.nylas.com/D2289

Fix composer specs

Tests can properly detect when Spectron is in the environment

Report plain text output in specs

fixing contenteditable specs

Testing slow keymaps on contenteditable specs

Move to DOm mutation

Spell as `subtree` not `subTree`
This commit is contained in:
Evan Morikawa 2015-11-19 18:29:49 -05:00
parent bc839fb541
commit 73e7c1c52e
28 changed files with 758 additions and 358 deletions

View file

@ -356,7 +356,7 @@ module.exports = (grunt) ->
grunt.registerTask('compile', ['coffee', 'cjsx', 'babel', 'prebuild-less', 'cson', 'peg'])
grunt.registerTask('lint', ['coffeelint', 'csslint', 'lesslint', 'nylaslint', 'eslint'])
grunt.registerTask('test', ['shell:kill-n1', 'run-edgehill-specs'])
grunt.registerTask('test', ['shell:kill-n1', 'run-unit-tests'])
grunt.registerTask('docs', ['build-docs', 'render-docs'])
ciTasks = ['output-disk-space', 'download-electron', 'build']

View file

@ -0,0 +1,20 @@
path = require 'path'
childProcess = require 'child_process'
module.exports = (grunt) ->
desc = "Boots Selenium via Spectron to run integration tests"
grunt.registerTask 'run-integration-tests', desc, ->
done = @async()
rootPath = path.resolve('.')
npmPath = path.join(rootPath, "build", "node_modules", ".bin", "npm")
process.chdir('./spec_integration')
testProc = childProcess.spawn(npmPath,
["test", "NYLAS_ROOT_PATH=#{rootPath}"],
{stdio: "inherit"})
testProc.on 'exit', (exitCode, signal) ->
process.chdir('..')
if exitCode is 0 then done()
else done(false)

View file

@ -0,0 +1,25 @@
childProcess = require 'child_process'
module.exports = (grunt) ->
{notifyAPI} = require('./task-helpers')(grunt)
desc = "Boots N1 in --test mode to run unit tests"
grunt.registerTask 'run-unit-tests', desc, ->
done = @async()
testProc = childProcess.spawn("./N1.sh", ["--test"])
testOutput = ""
testProc.stdout.pipe(process.stdout)
testProc.stderr.pipe(process.stderr)
testProc.stdout.on 'data', (data) -> testOutput += data.toString()
testProc.stderr.on 'data', (data) -> testOutput += data.toString()
testProc.on 'error', (err) -> grunt.log.error("Process error: #{err}")
testProc.on 'exit', (exitCode, signal) ->
if exitCode is 0 then done()
else
testOutput = grunt.log.uncolor(testOutput)
msg = "Aghhh somebody broke the build. ```#{testOutput}```"
notifyAPI msg, -> done(false)

View file

@ -1,78 +0,0 @@
fs = require 'fs'
path = require 'path'
request = require 'request'
childProcess = require 'child_process'
executeTests = ({cmd, args}, grunt, done) ->
testProc = childProcess.spawn(cmd, args)
testOutput = ""
testProc.stdout.pipe(process.stdout)
testProc.stderr.pipe(process.stderr)
testProc.stdout.on 'data', (data) -> testOutput += data.toString()
testProc.stderr.on 'data', (data) -> testOutput += data.toString()
testProc.on 'error', (err) -> grunt.log.error("Process error: #{err}")
testProc.on 'exit', (exitCode, signal) ->
if exitCode is 0
done()
else
notifyOfTestError testOutput, grunt, ->
done(false)
notifyOfTestError = (testOutput, grunt, callback) ->
if (process.env["TEST_ERROR_HOOK_URL"] ? "").length > 0
testOutput = grunt.log.uncolor(testOutput)
request.post
url: process.env["TEST_ERROR_HOOK_URL"]
json:
username: "Edgehill Builds"
text: "Aghhh somebody broke the build. ```#{testOutput}```"
, callback
else
callback()
module.exports = (grunt) ->
grunt.registerTask 'run-edgehill-specs', 'Run the specs', ->
done = @async()
executeTests({cmd: './N1.sh', args: ['--test']}, grunt, done)
grunt.registerTask 'run-spectron-specs', 'Run spectron specs', ->
shellAppDir = grunt.config.get('nylasGruntConfig.shellAppDir')
if process.platform is 'darwin'
executablePath = path.join(shellAppDir, 'Contents', 'MacOS', 'Nylas')
else
executablePath = path.join(shellAppDir, 'nylas')
done = @async()
npmPath = path.resolve "./build/node_modules/.bin/npm"
if process.platform is 'win32'
grunt.log.error("run-spectron-specs only works on Mac OS X at the moment.")
done(false)
if not fs.existsSync(executablePath)
grunt.log.error("run-spectron-specs requires the built version of the app at #{executablePath}")
done(false)
process.chdir('./spectron')
grunt.log.writeln "Current dir: #{process.cwd()}"
installProc = childProcess.exec "#{npmPath} install", (error) ->
if error?
process.chdir('..')
grunt.log.error('Failed while running npm install in spectron folder')
grunt.fail.warn(error)
done(false)
else
appArgs = [
'test'
"APP_PATH=#{executablePath}"
"APP_ARGS="
]
executeTests {cmd: npmPath, args: appArgs}, grunt, (succeeded) ->
process.chdir('..')
done(succeeded)

View file

@ -1,5 +1,6 @@
fs = require 'fs-plus'
path = require 'path'
request = require 'request'
module.exports = (grunt) ->
cp: (source, destination, {filter}={}) ->
@ -66,3 +67,14 @@ module.exports = (grunt) ->
engines?.nylas?
catch error
false
notifyAPI: (msg, callback) ->
if (process.env("TEST_ERROR_HOOK_URL") ? "").length > 0
request.post
url: process.env("TEST_ERROR_HOOK_URL")
json:
username: "Edgehill Builds"
text: msg
, callback
else callback()

View file

@ -124,7 +124,7 @@ useFullDraft = ->
replyToMessageId: null
makeComposer = ->
@composer = ReactTestUtils.renderIntoDocument(
@composer = NylasTestUtils.renderIntoDocument(
<ComposerView draftClientId={DRAFT_CLIENT_ID} />
)
@ -135,6 +135,7 @@ describe "populated composer", ->
afterEach ->
DraftStore._cleanupAllSessions()
NylasTestUtils.removeFromDocument(@composer)
describe "when sending a new message", ->
it 'makes a request with the message contents', ->
@ -366,7 +367,7 @@ describe "populated composer", ->
it "ignores focuses to participant fields", ->
@composer.setState focusedField: Fields.To
expect(@body.focus).not.toHaveBeenCalled()
expect(React.findDOMNode.calls.length).toBe 3
expect(@composer._applyFieldFocus.calls.length).toBe 1
describe "when participants are added during a draft update", ->
it "shows the cc fields and bcc fields to ensure participants are never hidden", ->
@ -542,12 +543,12 @@ describe "populated composer", ->
cmdctrl = 'cmd'
else
cmdctrl = 'ctrl'
NylasTestUtils.keyPress("#{cmdctrl}-enter", React.findDOMNode(@$composer))
NylasTestUtils.keyDown("#{cmdctrl}-enter", React.findDOMNode(@$composer))
expect(Actions.sendDraft).toHaveBeenCalled()
expect(Actions.sendDraft.calls.length).toBe 1
it "does not send the draft on enter if the button isn't in focus", ->
NylasTestUtils.keyPress("enter", React.findDOMNode(@$composer))
NylasTestUtils.keyDown("enter", React.findDOMNode(@$composer))
expect(Actions.sendDraft).not.toHaveBeenCalled()
it "doesn't let you send twice", ->
@ -555,12 +556,12 @@ describe "populated composer", ->
cmdctrl = 'cmd'
else
cmdctrl = 'ctrl'
NylasTestUtils.keyPress("#{cmdctrl}-enter", React.findDOMNode(@$composer))
NylasTestUtils.keyDown("#{cmdctrl}-enter", React.findDOMNode(@$composer))
expect(Actions.sendDraft).toHaveBeenCalled()
expect(Actions.sendDraft.calls.length).toBe 1
@isSending = true
DraftStore.trigger()
NylasTestUtils.keyPress("#{cmdctrl}-enter", React.findDOMNode(@$composer))
NylasTestUtils.keyDown("#{cmdctrl}-enter", React.findDOMNode(@$composer))
expect(Actions.sendDraft).toHaveBeenCalled()
expect(Actions.sendDraft.calls.length).toBe 1

View file

@ -32,8 +32,8 @@
"immutable": "3.7.5",
"inflection": "^1.7",
"jasmine-json": "~0.0",
"jasmine-tagged": "^1.1.2",
"jasmine-react-helpers": "^0.2",
"jasmine-tagged": "^1.1.2",
"jquery": "^2.1.1",
"juice": "^1.4",
"less-cache": "0.21",
@ -41,13 +41,13 @@
"mkdirp": "^0.5",
"moment": "^2.8",
"moment-timezone": "^0.3",
"nslog": "^3",
"node-uuid": "^1.4",
"nock": "^2",
"node-uuid": "^1.4",
"nslog": "^3",
"optimist": "0.4.0",
"pathwatcher": "~6.2",
"property-accessors": "^1",
"promise-queue": "2.1.1",
"property-accessors": "^1",
"proxyquire": "1.3.1",
"q": "^1.0.1",
"raven": "0.7.2",
@ -63,13 +63,16 @@
"semver": "^4.2",
"serializable": "^1",
"service-hub": "^0.2.0",
"space-pen": "3.8.2",
"spellchecker": "^3.1.2",
"source-map-support": "^0.3.2",
"space-pen": "3.8.2",
"spectron": "0.34.1",
"spellchecker": "^3.1.2",
"sqlite3": "https://github.com/mapbox/node-sqlite3/archive/v3.1.1.tar.gz",
"temp": "^0.8",
"theorist": "^1.0",
"underscore": "^1.8",
"underscore.string": "^3.0"
"underscore.string": "^3.0",
"webdriverio": "3.2.6"
},
"packageDependencies": {},
"private": true,

View file

@ -145,6 +145,12 @@ function bootstrap() {
var downloadElectronCmd = gruntPath + " download-electron --gruntfile build/Gruntfile.coffee"
m7 += " $ "+downloadElectronCmd
var integrationCommand = npmPath + npmFlags + 'install';
var integrationOptions = {cwd: path.resolve(__dirname, '..', 'spec_integration')};
m8 = "\n\n---> Installing integration test modules\n\n"
m8 += " $ "+integrationCommand+" "+printArgs(integrationOptions)+"\n"
var commands = [
{
command: buildInstallCommand,
@ -211,6 +217,11 @@ function bootstrap() {
command: downloadElectronCmd,
message: m7
},
{
command: integrationCommand,
options: integrationOptions,
message: m8
},
{
command: sqlite3Command,
message: "Building sqlite3 with command: "+sqlite3Command

View file

@ -1,105 +0,0 @@
xdescribe "ListManager", ->
beforeEach ->
@ce = new ContenteditableTestHarness
it "Creates ordered lists", ->
@ce.type ['1', '.', ' ']
@ce.expectHTML "<ol><li></li></ol>"
@ce.expectSelection (dom) ->
dom.querySelectorAll("li")[0]
it "Undoes ordered list creation with backspace", ->
@ce.type ['1', '.', ' ', 'backspace']
@ce.expectHTML "1.&nbsp;"
@ce.expectSelection (dom) ->
node: dom.childNodes[0]
offset: 3
it "Creates unordered lists with star", ->
@ce.type ['*', ' ']
@ce.expectHTML "<ul><li></li></ul>"
@ce.expectSelection (dom) ->
dom.querySelectorAll("li")[0]
it "Undoes unordered list creation with backspace", ->
@ce.type ['*', ' ', 'backspace']
@ce.expectHTML "*&nbsp;"
@ce.expectSelection (dom) ->
node: dom.childNodes[0]
offset: 2
it "Creates unordered lists with dash", ->
@ce.type ['-', ' ']
@ce.expectHTML "<ul><li></li></ul>"
@ce.expectSelection (dom) ->
dom.querySelectorAll("li")[0]
it "Undoes unordered list creation with backspace", ->
@ce.type ['-', ' ', 'backspace']
@ce.expectHTML "-&nbsp;"
@ce.expectSelection (dom) ->
node: dom.childNodes[0]
offset: 2
it "create a single item then delete it with backspace", ->
@ce.type ['-', ' ', 'a', 'left', 'backspace']
@ce.expectHTML "a"
@ce.expectSelection (dom) ->
node: dom.childNodes[0]
offset: 0
it "create a single item then delete it with tab", ->
@ce.type ['-', ' ', 'a', 'shift-tab']
@ce.expectHTML "a"
@ce.expectSelection (dom) -> dom.childNodes[0]
node: dom.childNodes[0]
offset: 1
describe "when creating two items in a list", ->
beforeEach ->
@twoItemKeys = ['-', ' ', 'a', 'enter', 'b']
it "creates two items with enter at end", ->
@ce.type @twoItemKeys
@ce.expectHTML "<ul><li>a</li><li>b</li></ul>"
@ce.expectSelection (dom) ->
node: dom.querySelectorAll('li')[1].childNodes[0]
offset: 1
it "backspace from the start of the 1st item outdents", ->
@ce.type @twoItemKeys.concat ['left', 'up', 'backspace']
it "backspace from the start of the 2nd item outdents", ->
@ce.type @twoItemKeys.concat ['left', 'backspace']
it "shift-tab from the start of the 1st item outdents", ->
@ce.type @twoItemKeys.concat ['left', 'up', 'shift-tab']
it "shift-tab from the start of the 2nd item outdents", ->
@ce.type @twoItemKeys.concat ['left', 'shift-tab']
it "shift-tab from the end of the 1st item outdents", ->
@ce.type @twoItemKeys.concat ['up', 'shift-tab']
it "shift-tab from the end of the 2nd item outdents", ->
@ce.type @twoItemKeys.concat ['shift-tab']
it "backspace from the end of the 1st item doesn't outdent", ->
@ce.type @twoItemKeys.concat ['up', 'backspace']
it "backspace from the end of the 2nd item doesn't outdent", ->
@ce.type @twoItemKeys.concat ['backspace']
describe "multi-depth bullets", ->
it "creates multi level bullet when tabbed in", ->
@ce.type ['-', ' ', 'a', 'tab']
it "creates multi level bullet when tabbed in", ->
@ce.type ['-', ' ', 'tab', 'a']
it "returns to single level bullet on backspace", ->
@ce.type ['-', ' ', 'a', 'tab', 'left', 'backspace']
it "returns to single level bullet on shift-tab", ->
@ce.type ['-', ' ', 'a', 'tab', 'shift-tab']

View file

@ -0,0 +1,110 @@
_ = require 'underscore'
React = require 'react/addons'
TimeOverride = require '../../time-override'
NylasTestUtils = require '../../nylas-test-utils'
{Contenteditable} = require 'nylas-component-kit'
###
Public: Easily test contenteditable interactions
Create a new instance of this on each test. It will render a new
Contenteditable into the document wrapped around a class that can keep
track of its state.
For example
```coffee
beforeEach ->
@ce = new ContenteditableTestHarness
it "can create an ordered list", ->
@ce.keys ['1', '.', ' ']
@ce.expectHTML "<ol><li></li></ol>"
@ce.expectSelection (dom) ->
node: dom.querySelectorAll("li")[0]
afterEach ->
@ce.cleanup()
```
**Be sure to call `cleanup` after each test**
###
class ContenteditableTestHarness
constructor: ({@props, @initialValue}={}) ->
@props ?= {}
@initialValue ?= ""
@wrap = NylasTestUtils.renderIntoDocument(
<Wrap ceProps={@props} initialValue={@initialValue} />
)
cleanup: ->
NylasTestUtils.removeFromDocument(@wrap)
# We send keys to spectron one at a time. We also need ot use a "real"
# setTimeout since Spectron is completely outside of the mocked setTimeouts
# that we setup. We still `advanceClock` to clear any components or Promises
# that need to run inbetween keystrokes.
keys: (keyStrokes=[]) -> new Promise (resolve, reject) =>
TimeOverride.disableSpies()
@getDOM().focus()
timeout = 0
KEY_DELAY = 1000
keyStrokes.forEach (key) ->
window.setTimeout ->
NylasEnv.spectron.client.keys([key])
advanceClock(KEY_DELAY)
, timeout
timeout += KEY_DELAY
window.setTimeout ->
resolve()
, timeout + KEY_DELAY
expectHTML: (expectedHTML) ->
expect(@wrap.state.value).toBe expectedHTML
expectSelection: (callback) ->
expectSel = callback(@getDOM())
anchorNode = expectSel.anchorNode ? expectSel.node ? "No anchorNode found"
focusNode = expectSel.focusNode ? expectSel.node ? "No focusNode found"
anchorOffset = expectSel.anchorOffset ? expectSel.offset ? 0
focusOffset = expectSel.focusOffset ? expectSel.offset ? 0
selection = document.getSelection()
expect(selection.anchorNode).toBe anchorNode
expect(selection.focusNode).toBe focusNode
expect(selection.anchorOffset).toBe anchorOffset
expect(selection.focusOffset).toBe focusOffset
getDOM: ->
React.findDOMNode(@wrap.refs["ceWrap"].refs["contenteditable"])
class Wrap extends React.Component
@displayName: "wrap"
constructor: (@props) ->
@state = value: @props.initialValue
render: ->
userOnChange = @props.ceProps.onChange ? ->
props = _.clone(@props.ceProps)
props.onChange = (event) =>
userOnChange(event)
@onChange(event)
props.value = @state.value
props.ref = "ceWrap"
<Contenteditable {...props} />
onChange: (event) ->
@setState value: event.target.value
componentDidMount: ->
@refs.ceWrap.focus()
module.exports = ContenteditableTestHarness

View file

@ -0,0 +1,122 @@
ContenteditableTestHarness = require './contenteditable-test-harness'
return unless NylasEnv.inIntegrationSpecMode()
fdescribe "ListManager", ->
beforeEach ->
# console.log "--> Before each"
@ce = new ContenteditableTestHarness
# div = document.querySelector("div[contenteditable]")
# console.log div
# console.log div?.innerHTML
# console.log "Done before each"
afterEach ->
# console.log "<-- After each"
@ce.cleanup()
it "Creates ordered lists", -> waitsForPromise =>
@ce.keys(['1', '.', 'Space']).then =>
# console.log "Keys typed"
@ce.expectHTML "<ol><li></li></ol>"
@ce.expectSelection (dom) ->
node: dom.querySelectorAll("li")[0]
ffit "Undoes ordered list creation with backspace", -> waitsForPromise =>
@ce.keys(['1', '.', 'Space', 'Back space']).then =>
@ce.expectHTML "1.&nbsp;"
@ce.expectSelection (dom) ->
node: dom.childNodes[0]
offset: 3
it "Creates unordered lists with star", -> waitsForPromise =>
@ce.keys(['*', 'Space']).then =>
@ce.expectHTML "<ul><li></li></ul>"
@ce.expectSelection (dom) ->
node: dom.querySelectorAll("li")[0]
xit "Undoes unordered list creation with backspace", ->
aitsForPromise =>
@ce.keys(['*', 'Space', 'Back space']).then =>
@ce.expectHTML "*&nbsp;"
@ce.expectSelection (dom) ->
node: dom.childNodes[0]
offset: 2
it "Creates unordered lists with dash", -> waitsForPromise =>
@ce.keys(['-', 'Space']).then =>
@ce.expectHTML "<ul><li></li></ul>"
@ce.expectSelection (dom) ->
node: dom.querySelectorAll("li")[0]
it "Undoes unordered list creation with backspace", ->
waitsForPromise =>
@ce.keys(['-', 'Space', 'Back space']).then =>
@ce.expectHTML "-&nbsp;"
@ce.expectSelection (dom) ->
node: dom.childNodes[0]
offset: 2
it "create a single item then delete it with backspace", ->
waitsForPromise =>
@ce.keys(['-', 'Space', 'a', 'Left arrow', 'Back space']).then =>
@ce.expectHTML "a"
@ce.expectSelection (dom) ->
node: dom.childNodes[0]
offset: 0
it "create a single item then delete it with tab", ->
waitsForPromise =>
@ce.keys(['-', 'Space', 'a', 'Shift', 'Tab']).then =>
@ce.expectHTML "a"
@ce.expectSelection (dom) -> dom.childNodes[0]
node: dom.childNodes[0]
offset: 1
describe "when creating two items in a list", ->
beforeEach ->
@twoItemKeys = ['-', 'Space', 'a', 'Return', 'b']
it "creates two items with enter at end", -> waitsForPromise =>
@ce.keys(@twoItemKeys).then =>
@ce.expectHTML "<ul><li>a</li><li>b</li></ul>"
@ce.expectSelection (dom) ->
node: dom.querySelectorAll('li')[1].childNodes[0]
offset: 1
xit "backspace from the start of the 1st item outdents", ->
@ce.keys @twoItemKeys.concat ['left', 'up', 'backspace']
xit "backspace from the start of the 2nd item outdents", ->
@ce.keys @twoItemKeys.concat ['left', 'backspace']
xit "shift-tab from the start of the 1st item outdents", ->
@ce.keys @twoItemKeys.concat ['left', 'up', 'shift-tab']
xit "shift-tab from the start of the 2nd item outdents", ->
@ce.keys @twoItemKeys.concat ['left', 'shift-tab']
xit "shift-tab from the end of the 1st item outdents", ->
@ce.keys @twoItemKeys.concat ['up', 'shift-tab']
xit "shift-tab from the end of the 2nd item outdents", ->
@ce.keys @twoItemKeys.concat ['shift-tab']
xit "backspace from the end of the 1st item doesn't outdent", ->
@ce.keys @twoItemKeys.concat ['up', 'backspace']
xit "backspace from the end of the 2nd item doesn't outdent", ->
@ce.keys @twoItemKeys.concat ['backspace']
xdescribe "multi-depth bullets", ->
it "creates multi level bullet when tabbed in", ->
@ce.keys ['-', ' ', 'a', 'tab']
it "creates multi level bullet when tabbed in", ->
@ce.keys ['-', ' ', 'tab', 'a']
it "returns to single level bullet on backspace", ->
@ce.keys ['-', ' ', 'a', 'tab', 'left', 'backspace']
it "returns to single level bullet on shift-tab", ->
@ce.keys ['-', ' ', 'a', 'tab', 'shift-tab']

View file

@ -38,16 +38,24 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
N1SpecReporter = require './n1-spec-reporter'
reporter = new N1SpecReporter()
require specSuite
NylasEnv.initialize()
jasmineEnv = jasmine.getEnv()
jasmineEnv.addReporter(reporter)
jasmineEnv.addReporter(timeReporter)
jasmineEnv.setIncludedTags([process.platform])
# Tests that run under an integration environment need Spectron to be
# asynchronously setup and connected to the Selenium API before proceeding.
# Once setup, one can test `NylasEnv.inIntegrationSpecMode()`
#
# This safely works regardless if Spectron is loaded.
NylasEnv.setupSpectron().finally ->
require specSuite
$('body').append $$ -> @div id: 'jasmine-content'
jasmineEnv = jasmine.getEnv()
jasmineEnv.addReporter(reporter)
jasmineEnv.addReporter(timeReporter)
jasmineEnv.setIncludedTags([process.platform])
jasmineEnv.execute()
$('body').append $$ -> @div id: 'jasmine-content'
jasmineEnv.execute()
disableFocusMethods = ->
['fdescribe', 'ffdescribe', 'fffdescribe', 'fit', 'ffit', 'fffit'].forEach (methodName) ->

View file

@ -7,7 +7,7 @@ grim = require 'grim'
marked = require 'marked'
sourceMaps = {}
formatStackTrace = (spec, message='', stackTrace) ->
formatStackTrace = (spec, message='', stackTrace, indent="") ->
return stackTrace unless stackTrace
jasminePattern = /^\s*at\s+.*\(?.*[/\\]jasmine(-[^/\\]*)?\.js:\d+:\d+\)?\s*$/
@ -32,8 +32,8 @@ formatStackTrace = (spec, message='', stackTrace) ->
# Relativize locations to spec directory
lines[index] = line.replace("at #{spec.specDirectory}#{path.sep}", 'at ')
lines = lines.map (line) -> line.trim()
lines.join('\n').trim()
lines = lines.map (line) -> indent + line.trim()
lines.join('\n')
module.exports =
class N1SpecReporter extends View
@ -60,6 +60,7 @@ class N1SpecReporter extends View
@span outlet: 'deprecationStatus', '0 deprecations'
@div class: 'deprecation-toggle'
@div outlet: 'deprecationList', class: 'deprecation-list'
@pre outlet: "plainTextOutput", class: 'plain-text-output'
startedAt: null
runningSpecCount: 0
@ -92,6 +93,8 @@ class N1SpecReporter extends View
else
@message.text "#{@failedCount} failures"
@status.addClass("specs-complete")
reportSuiteResults: (suite) ->
reportSpecResults: (spec) ->
@ -99,6 +102,45 @@ class N1SpecReporter extends View
spec.endedAt = Date.now()
@specComplete(spec)
@updateStatusView(spec)
@reportPlainTextSpecResult(spec)
reportPlainTextSpecResult: (spec) ->
str = ""
if spec.results().failedCount > 0
str += @suiteString(spec) + "\n"
indent = @indentationString(spec.suite, 1)
stackIndent = @indentationString(spec.suite, 2)
description = spec.description
description = "it #{description}" if description.indexOf('it ') isnt 0
str += indent + description + "\n"
for result in spec.results().getItems()
continue if result.passed()
str += indent + result.message + "\n"
stackTrace = formatStackTrace(spec, result.message, result.trace.stack, stackIndent)
str += stackTrace + "\n"
str += "\n\n"
@plainTextOutput.append(str)
indentationString: (suite, plus=0) ->
rootSuite = suite
indentLevel = 0 + plus
while rootSuite.parentSuite
rootSuite = rootSuite.parentSuite
indentLevel += 1
return [0...indentLevel].map(-> " ").join("")
suiteString: (spec) ->
descriptions = [spec.suite.description]
rootSuite = spec.suite
while rootSuite.parentSuite
indent = @indentationString(rootSuite)
descriptions.unshift(indent + rootSuite.description)
rootSuite = rootSuite.parentSuite
descriptions.join("\n")
reportSpecStarting: (spec) ->
@specStarted(spec)

View file

@ -1,6 +1,8 @@
# Utils for testing.
CSON = require 'season'
React = require 'react/addons'
KeymapManager = require 'atom-keymap'
ReactTestUtils = React.addons.TestUtils
NylasTestUtils =
loadKeymap: (keymapPath) ->
@ -12,18 +14,30 @@ NylasTestUtils =
keymapPath = CSON.resolve("#{resourcePath}/#{keymapPath}")
NylasEnv.keymaps.loadKeymap(keymapPath)
keyPress: (key, target) ->
# React's "renderIntoDocument" does not /actually/ attach the component
# to the document. It's a sham: http://dragon.ak.fbcdn.net/hphotos-ak-xpf1/t39.3284-6/10956909_1423563877937976_838415501_n.js
# The Atom keymap manager doesn't work correctly on elements outside of the
# DOM tree, so we need to attach it.
unless document.contains(target)
parent = target
while parent.parentNode?
parent = parent.parentNode
document.documentElement.appendChild(parent)
keyDown: (key, target) ->
event = KeymapManager.buildKeydownEvent(key, target: target)
NylasEnv.keymaps.handleKeyboardEvent(event)
# React's "renderIntoDocument" does not /actually/ attach the component
# to the document. It's a sham: http://dragon.ak.fbcdn.net/hphotos-ak-xpf1/t39.3284-6/10956909_1423563877937976_838415501_n.js
# The Atom keymap manager doesn't work correctly on elements outside of the
# DOM tree, so we need to attach it.
renderIntoDocument: (reactDOM) ->
node = ReactTestUtils.renderIntoDocument(reactDOM)
$node = React.findDOMNode(node)
unless document.body.contains($node)
parent = $node
while parent.parentNode?
parent = parent.parentNode
document.body.appendChild(parent)
return node
removeFromDocument: (reactElement) ->
$el = React.findDOMNode(reactElement)
if document.body.contains($el)
for child in Array::slice.call(document.body.childNodes)
if child.contains($el)
document.body.removeChild(child)
return
module.exports = NylasTestUtils

View file

@ -18,7 +18,7 @@ try
# Show window synchronously so a focusout doesn't fire on input elements
# that are focused in the very first spec run.
if NylasEnv.getLoadSettings().showSpecsInWindow
if not NylasEnv.getLoadSettings().exitWhenDone
NylasEnv.getCurrentWindow().show()
{runSpecSuite} = require './jasmine-helper'

View file

@ -1,14 +1,16 @@
_ = require 'underscore'
_str = require 'underscore.string'
fs = require 'fs-plus'
path = require 'path'
require '../src/window'
NylasEnv.initialize()
NylasEnv.restoreWindowDimensions()
require 'jasmine-json'
require './jasmine-jquery'
path = require 'path'
_ = require 'underscore'
_str = require 'underscore.string'
fs = require 'fs-plus'
Grim = require 'grim'
TimeOverride = require './time-override'
KeymapManager = require '../src/keymap-manager'
# FIXME: Remove jquery from this
@ -50,7 +52,7 @@ jasmine.getEnv().addEqualityTester(_.isEqual) # Use underscore's definition of e
if process.env.JANKY_SHA1 and process.platform is 'win32'
jasmine.getEnv().defaultTimeoutInterval = 60000
else
jasmine.getEnv().defaultTimeoutInterval = 1000
jasmine.getEnv().defaultTimeoutInterval = 10000
specPackageName = null
specPackagePath = null
@ -98,13 +100,6 @@ ReactTestUtils.unmountAll = ->
React.unmountComponentAtNode(container)
ReactElementContainers = []
# Make Bluebird use setTimeout so that it hooks into our stubs, and you can
# advance promises using `advanceClock()`. To avoid breaking any specs that
# `dont` manually call advanceClock, call it automatically on the next tick.
Promise.setScheduler (fn) ->
setTimeout(fn, 0)
process.nextTick -> advanceClock(1)
# So it passes the Utils.isTempId test
window.TEST_ACCOUNT_CLIENT_ID = "local-test-account-client-id"
window.TEST_ACCOUNT_ID = "test-account-server-id"
@ -129,19 +124,15 @@ beforeEach ->
NylasEnv.styles.restoreSnapshot(styleElementsToRestore)
NylasEnv.workspaceViewParentSelector = '#jasmine-content'
window.resetTimeouts()
spyOn(_._, "now").andCallFake -> window.now
spyOn(window, "setTimeout").andCallFake window.fakeSetTimeout
spyOn(window, "clearTimeout").andCallFake window.fakeClearTimeout
spyOn(window, "setInterval").andCallFake window.fakeSetInterval
spyOn(window, "clearInterval").andCallFake window.fakeClearInterval
NylasEnv.packages.packageStates = {}
serializedWindowState = null
spyOn(NylasEnv, 'saveSync')
TimeOverride.resetTime()
TimeOverride.enableSpies()
spy = spyOn(NylasEnv.packages, 'resolvePackagePath').andCallFake (packageName) ->
if specPackageName and packageName is specPackageName
resolvePackagePath(specPackagePath)
@ -176,7 +167,6 @@ beforeEach ->
"package-with-broken-package-json", "package-with-broken-keymap"]
config.set "editor.useShadowDOM", true
advanceClock(1000)
window.setTimeout.reset()
config.load.reset()
config.save.reset()
@ -188,6 +178,7 @@ beforeEach ->
addCustomMatchers(this)
TimeOverride.resetSpyData()
original_log = console.log
original_warn = console.warn
@ -266,13 +257,6 @@ jasmine.snapshotDeprecations = ->
jasmine.restoreDeprecationsSnapshot = ->
Grim.deprecations = deprecationsSnapshot
jasmine.useRealClock = ->
jasmine.unspy(window, 'setTimeout')
jasmine.unspy(window, 'clearTimeout')
jasmine.unspy(window, 'setInterval')
jasmine.unspy(window, 'clearInterval')
jasmine.unspy(_._, 'now')
addCustomMatchers = (spec) ->
spec.addMatchers
toBeInstanceOf: (expected) ->
@ -379,85 +363,8 @@ window.waitsForPromise = (args...) ->
jasmine.getEnv().currentSpec.fail("Expected promise to be resolved, but it was rejected with #{msg}")
moveOn()
window.resetTimeouts = ->
window.now = 0
window.timeoutCount = 0
window.intervalCount = 0
window.timeouts = []
window.intervalTimeouts = {}
window.fakeSetTimeout = (callback, ms) ->
id = ++window.timeoutCount
window.timeouts.push([id, window.now + ms, callback])
id
window.fakeClearTimeout = (idToClear) ->
window.timeouts ?= []
window.timeouts = window.timeouts.filter ([id]) -> id != idToClear
window.fakeSetInterval = (callback, ms) ->
id = ++window.intervalCount
action = ->
callback()
window.intervalTimeouts[id] = window.fakeSetTimeout(action, ms)
window.intervalTimeouts[id] = window.fakeSetTimeout(action, ms)
id
window.fakeClearInterval = (idToClear) ->
window.fakeClearTimeout(@intervalTimeouts[idToClear])
window.advanceClock = (delta=1) ->
window.now += delta
callbacks = []
window.timeouts ?= []
window.timeouts = window.timeouts.filter ([id, strikeTime, callback]) ->
if strikeTime <= window.now
callbacks.push(callback)
false
else
true
callback() for callback in callbacks
window.pagePixelPositionForPoint = (editorView, point) ->
point = Point.fromObject point
top = editorView.renderedLines.offset().top + point.row * editorView.lineHeight
left = editorView.renderedLines.offset().left + point.column * editorView.charWidth - editorView.renderedLines.scrollLeft()
{ top, left }
window.tokensText = (tokens) ->
_.pluck(tokens, 'value').join('')
window.setEditorWidthInChars = (editorView, widthInChars, charWidth=editorView.charWidth) ->
editorView.width(charWidth * widthInChars + editorView.gutter.outerWidth())
$(window).trigger 'resize' # update width of editor view's on-screen lines
window.setEditorHeightInLines = (editorView, heightInLines, lineHeight=editorView.lineHeight) ->
editorView.height(editorView.getEditor().getLineHeightInPixels() * heightInLines)
editorView.component?.measureHeightAndWidth()
$.fn.resultOfTrigger = (type) ->
event = $.Event(type)
this.trigger(event)
event.result
$.fn.enableKeymap = ->
@on 'keydown', (e) ->
originalEvent = e.originalEvent ? e
Object.defineProperty(originalEvent, 'target', get: -> e.target) unless originalEvent.target?
NylasEnv.keymaps.handleKeyboardEvent(originalEvent)
not e.originalEvent.defaultPrevented
$.fn.attachToDom = ->
@appendTo($('#jasmine-content')) unless @isOnDom()
$.fn.simulateDomAttachment = ->
$('<html>').append(this)
$.fn.textInput = (data) ->
this.each ->
event = document.createEvent('TextEvent')
event.initTextEvent('textInput', true, true, window, data)
event = $.event.fix(event)
$(this).trigger(event)

100
spec/time-override.coffee Normal file
View file

@ -0,0 +1,100 @@
_ = require 'underscore'
# Public: To make specs easier to test, we make all asynchronous behavior
# actually synchronous. We do this by overriding all global timeout and
# Promise functions.
#
# You must now manually call `advanceClock()` in order to move the "clock"
# forward.
class TimeOverride
@advanceClock = (delta=1) =>
@now += delta
callbacks = []
@timeouts ?= []
@timeouts = @timeouts.filter ([id, strikeTime, callback]) =>
if strikeTime <= @now
callbacks.push(callback)
false
else
true
callback() for callback in callbacks
@resetTime = =>
@now = 0
@timeoutCount = 0
@intervalCount = 0
@timeouts = []
@intervalTimeouts = {}
@originalPromiseScheduler = null
@enableSpies = =>
window.advanceClock = @advanceClock
window.originalSetInterval = window.setInterval
spyOn(window, "setTimeout").andCallFake @_fakeSetTimeout
spyOn(window, "clearTimeout").andCallFake @_fakeClearTimeout
spyOn(window, "setInterval").andCallFake @_fakeSetInterval
spyOn(window, "clearInterval").andCallFake @_fakeClearInterval
spyOn(_._, "now").andCallFake => @now
# spyOn(Date, "now").andCallFake => @now
# spyOn(Date.prototype, "getTime").andCallFake => @now
@_setPromiseScheduler()
@_setPromiseScheduler: =>
# Make Bluebird use setTimeout so that it hooks into our stubs, and you
# can advance promises using `advanceClock()`. To avoid breaking any
# specs that `dont` manually call advanceClock, call it automatically on
# the next tick.
@originalPromiseScheduler ?= Promise.setScheduler (fn) =>
setTimeout(fn, 0)
process.nextTick =>
@advanceClock(1)
@disableSpies = =>
window.advanceClock = null
jasmine.unspy(window, 'setTimeout')
jasmine.unspy(window, 'clearTimeout')
jasmine.unspy(window, 'setInterval')
jasmine.unspy(window, 'clearInterval')
jasmine.unspy(_._, "now")
Promise.setScheduler(@originalPromiseScheduler) if @originalPromiseScheduler
@originalPromiseScheduler = null
@resetSpyData = ->
window.setTimeout.reset?()
window.clearTimeout.reset?()
window.setInterval.reset?()
window.clearInterval.reset?()
Date.now.reset?()
Date.prototype.getTime.reset?()
@_fakeSetTimeout = (callback, ms) =>
id = ++@timeoutCount
@timeouts.push([id, @now + ms, callback])
id
@_fakeClearTimeout = (idToClear) =>
@timeouts ?= []
@timeouts = @timeouts.filter ([id]) -> id != idToClear
@_fakeSetInterval = (callback, ms) =>
id = ++@intervalCount
action = ->
callback()
@intervalTimeouts[id] = @_fakeSetTimeout(action, ms)
@intervalTimeouts[id] = @_fakeSetTimeout(action, ms)
id
@_fakeClearInterval = (idToClear) =>
@_fakeClearTimeout(@intervalTimeouts[idToClear])
module.exports = TimeOverride

View file

@ -0,0 +1,53 @@
import {N1Launcher} from './integration-helper'
describe('Nylas Prod Bootup Tests', function() {
beforeAll((done)=>{
// Boot in dev mode with no arguments
this.app = new N1Launcher([]);
this.app.mainWindowReady().finally(done);
});
afterAll((done)=> {
if (this.app && this.app.isRunning()) {
this.app.stop().then(done);
} else {
done()
}
});
it("has main window visible", (done)=> {
this.app.client.isWindowVisible()
.then((result)=>{ expect(result).toBe(true) })
.finally(done)
});
it("has main window focused", (done)=> {
this.app.client.isWindowFocused()
.then((result)=>{ expect(result).toBe(true) })
.finally(done)
});
it("isn't minimized", (done)=> {
this.app.client.isWindowMinimized()
.then((result)=>{ expect(result).toBe(false) })
.finally(done)
});
it("doesn't have the dev tools open", (done)=> {
this.app.client.isWindowDevToolsOpened()
.then((result)=>{ expect(result).toBe(false) })
.finally(done)
});
it("has width", (done)=> {
this.app.client.getWindowWidth()
.then((result)=>{ expect(result).toBeGreaterThan(0) })
.finally(done)
});
it("has height", (done)=> {
this.app.client.getWindowHeight()
.then((result)=>{ expect(result).toBeGreaterThan(0) })
.finally(done)
});
});

12
spec_integration/bootstrap.js vendored Normal file
View file

@ -0,0 +1,12 @@
// argv[0] = node
// argv[1] = jasmine
// argv[2] = JASMINE_CONFIG_PATH=./config.json
// argv[3] = NYLAS_ROOT_PATH=/path/to/nylas/root
var babelOptions = require('../static/babelrc.json');
require('babel-core/register')(babelOptions);
jasmine.NYLAS_ROOT_PATH = process.argv[3].split("NYLAS_ROOT_PATH=")[1]
jasmine.UNIT_TEST_TIMEOUT = 120*1000;
jasmine.BOOT_TIMEOUT = 30*1000;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30*1000

View file

@ -0,0 +1,36 @@
import {N1Launcher} from './integration-helper'
// Some unit tests, such as the Contenteditable specs need to be run with
// Spectron availble in the environment.
fdescribe('Integrated Unit Tests', function() {
beforeAll((done)=>{
// Boot in dev mode with no arguments
this.app = new N1Launcher(["--test=window"]);
this.app.start().then(done).catch(done)
this.originalTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5*60*1000 // 5 minutes
});
afterAll((done)=> {
jasmine.DEFAULT_TIMEOUT_INTERVAL = this.originalTimeoutInterval
if (this.app && this.app.isRunning()) {
this.app.stop().then(done);
} else {
done()
}
});
it("Passes all integrated unit tests", (done)=> {
var client = this.app.client
client.waitForExist(".specs-complete", jasmine.UNIT_TEST_TIMEOUT)
.then(()=>{ return client.getHTML(".specs-complete .message") })
.then((results)=>{
expect(results).toMatch(/0 failures/)
}).then(()=>{ return client.getHTML(".plain-text-output") })
.then((errorOutput)=>{
expect(errorOutput).toBe('<pre class="plain-text-output"></pre>')
done()
}).catch(done)
});
});

View file

@ -0,0 +1,83 @@
import path from 'path'
import Promise from 'bluebird'
import {Application} from 'spectron';
class N1Launcher extends Application {
constructor(launchArgs = []) {
super({
path: N1Launcher.electronPath(),
args: [jasmine.NYLAS_ROOT_PATH].concat(N1Launcher.defaultNylasArgs()).concat(launchArgs)
})
}
mainWindowReady() {
// Wrap in a Bluebird promise so we have `.finally on the return`
return Promise.resolve(this.start().then(()=>{
return N1Launcher.waitUntilMainWindowLoaded(this.client).then((mainWindowId)=>{
return this.client.window(mainWindowId)
})
}));
}
static defaultNylasArgs() {
return ["--enable-logging", `--resource-path=${jasmine.NYLAS_ROOT_PATH}`]
}
static electronPath() {
nylasRoot = jasmine.NYLAS_ROOT_PATH
if (process.platform === "darwin") {
return path.join(nylasRoot, "electron", "Electron.app", "Contents", "MacOS", "Electron")
} else if (process.platform === "win32") {
return path.join(nylasRoot, "electron", "electron.exe")
}
else if (process.platform === "linux") {
return path.join(nylasRoot, "electron", "electron")
}
else {
throw new Error(`Platform ${process.platform} is not supported`)
}
}
// We unfortunatley can't just Spectron's `waitUntilWindowLoaded` because
// the first window that loads isn't necessarily the main render window (it
// could be the work window or others), and once the window is "loaded"
// it'll take a while for packages to load, etc. As such we periodically
// poll the list of windows to find one that looks like the main loaded
// window.
//
// Returns a promise that resolves with the main window's ID once it's
// loaded.
static waitUntilMainWindowLoaded(client, lastCheck=0) {
var CHECK_EVERY = 1000
return new Promise((resolve, reject) => {
client.windowHandles().then(({value}) => {
return Promise.mapSeries(value, (windowId)=>{
return N1Launcher.switchAndCheckForMain(client, windowId)
})
}).then((mainChecks)=>{
for (mainWindowId of mainChecks) {
if (mainWindowId) {return resolve(mainWindowId)}
}
var now = Date.now();
var delay = Math.max(CHECK_EVERY - (now - lastCheck), 0)
setTimeout(()=>{
N1Launcher.waitUntilMainWindowLoaded(client, now).then(resolve)
}, delay)
}).catch((err) => {
console.error(err);
});
});
}
// Returns false or the window ID of the main window
static switchAndCheckForMain(client, windowId) {
return client.window(windowId).then(()=>{
return client.isExisting(".main-window-loaded").then((exists)=>{
if (exists) {return windowId} else {return false}
})
})
}
}
module.exports = {N1Launcher}

View file

@ -9,6 +9,7 @@
"test": "jasmine JASMINE_CONFIG_PATH=./config.json"
},
"dependencies": {
"bluebird": "^3.0.5",
"babel-core": "^5.8.21",
"jasmine": "^2.3.2",
"spectron": "^0.34.1"

View file

@ -1,25 +0,0 @@
import {Application} from 'spectron';
describe('Nylas', ()=> {
beforeAll((done)=>{
this.app = new Application({
path: jasmine.APP_PATH,
args: jasmine.APP_ARGS,
});
this.app.start().then(()=> setTimeout(done, jasmine.BOOT_WAIT));
});
afterEach((done)=> {
if (this.app && this.app.isRunning()) {
this.app.stop().then(done);
}
});
it('boots 4 windows on launch', (done)=> {
this.app.client.getWindowCount().then((count)=> {
expect(count).toEqual(jasmine.any(Number));
done();
});
});
});

View file

@ -1,6 +0,0 @@
var babelOptions = require('../static/babelrc.json');
require('babel-core/register')(babelOptions);
jasmine.APP_PATH = process.argv.slice(3)[0].split('APP_PATH=')[1];
jasmine.APP_ARGS = process.argv.slice(4)[0].split('APP_ARGS=')[1].split(',');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
jasmine.BOOT_WAIT = 15000;

View file

@ -172,8 +172,11 @@ DOMUtils =
# https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
# Only Elements (not Text nodes) have the `closest` method
closest: (node, selector) ->
el = if node instanceof HTMLElement then node else node.parentElement
return el.closest(selector)
if node instanceof HTMLElement
return node.closest(selector)
else if node?.parentNode
return DOMUtils.closest(node.parentNode, selector)
else return null
closestAtCursor: (selector) ->
selection = document.getSelection()

View file

@ -223,6 +223,52 @@ class NylasEnvConstructor extends Model
window.onbeforeunload = => @_unloading()
@_unloadCallbacks = []
# Some unit tests require access to the Selenium web driver APIs as exposed
# by Spectron/Chromedriver. The app must be booted by Spectron as is done in the `run-integration-tests` task. Once Spectron boots the app, it will expose a RESTful API
#
# The Selenium API spec is here: https://code.google.com/p/selenium/wiki/JsonWireProtocol
#
# The Node wrapper for that API is provided by webdriver: http://webdriver.io/api.html
#
# Spectron wraps webdriver (in its `client` property) and adds additional methods: https://github.com/kevinsawicki/spectron
#
# Spectron requests that Selenium use Chromedriver
# https://sites.google.com/a/chromium.org/chromedriver/home to interface
# with the app, but points the binary at Electron.
#
# Since this code here is "inside" the booted process, we have no way of
# directly accessing the client from the test runner. However, we can still
# connect directly to the Selenium server to control ourself.
#
# We unfortunately can't create a new session with Selenium, because we
# won't be connected to the correct process (us!). Instead we need to
# inspect the existing sessions and use the existing one instead.
#
# We then manually setup the WebDriver session, and add in the extra
# spectron APIs via `Spectron.Application::addCommands`.
#
# http://webdriver.io/api/protocol/windowHandles.html
# https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/value
setupSpectron: ->
options =
host: "127.0.0.1"
port: 9515
SpectronApp = require('spectron').Application
@spectron = new SpectronApp
WebDriver = require('webdriverio')
@spectron.client = new WebDriver.remote(options)
@spectron.addCommands()
@spectron.client.sessions().then ({value}) =>
{sessionId, capabilities} = value[0]
@spectron.client.requestHandler.sessionID = sessionId
# https://github.com/webdriverio/webdriverio/blob/master/lib/protocol/init.js
@spectron.client.sessionID = sessionId
@spectron.client.capabilities = capabilities
@spectron.client.desiredCapabilities = capabilities
inIntegrationSpecMode: ->
@inSpecMode() and @spectron?.client?.sessionID
# Start our error reporting to the backend and attach error handlers
# to the window and the Bluebird Promise library, converting things
# back through the sourcemap as necessary.
@ -239,7 +285,7 @@ class NylasEnvConstructor extends Model
@lastUncaughtError = Array::slice.call(arguments)
[message, url, line, column, originalError] = @lastUncaughtError
# {line, column} = mapSourcePosition({source: url, line, column})
{line, column} = mapSourcePosition({source: url, line, column})
eventObject = {message, url, line, column, originalError}
@ -664,6 +710,7 @@ class NylasEnvConstructor extends Model
showRootWindow: ->
cover = document.getElementById("application-loading-cover")
cover.classList.add('visible')
document.body.classList.add("main-window-loaded")
@restoreWindowDimensions()
@getCurrentWindow().setMinimumSize(875, 500)

View file

@ -14,6 +14,10 @@ body {
line-height: 1.6em;
color: #333;
.plain-text-output {
display: none;
}
.list-unstyled {
list-style: none;
}