refactor(spec): remove spectron from main app

This commit is contained in:
Evan Morikawa 2015-12-02 13:40:12 -08:00
parent ced66902d5
commit f007962500
7 changed files with 56 additions and 348 deletions

View file

@ -65,14 +65,12 @@
"service-hub": "^0.2.0",
"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",
"webdriverio": "3.2.6"
"underscore.string": "^3.0"
},
"packageDependencies": {},
"private": true,

View file

@ -1,110 +0,0 @@
_ = 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

@ -1,122 +0,0 @@
ContenteditableTestHarness = require './contenteditable-test-harness'
return unless NylasEnv.inIntegrationSpecMode()
xdescribe "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]
it "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]
it "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

@ -43,22 +43,16 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
NylasEnv.initialize()
# 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
require specSuite
jasmineEnv = jasmine.getEnv()
jasmineEnv.addReporter(reporter)
jasmineEnv.addReporter(timeReporter)
jasmineEnv.setIncludedTags([process.platform])
jasmineEnv = jasmine.getEnv()
jasmineEnv.addReporter(reporter)
jasmineEnv.addReporter(timeReporter)
jasmineEnv.setIncludedTags([process.platform])
$('body').append $$ -> @div id: 'jasmine-content'
$('body').append $$ -> @div id: 'jasmine-content'
jasmineEnv.execute()
jasmineEnv.execute()
disableFocusMethods = ->
['fdescribe', 'ffdescribe', 'fffdescribe', 'fit', 'ffit', 'fffit'].forEach (methodName) ->

View file

@ -1,7 +1,7 @@
import {N1Launcher} from './integration-helper'
import ContenteditableTestHarness from './contenteditable-test-harness.es6'
fdescribe('Contenteditable Integration Spec', function() {
describe('Contenteditable Integration Spec', function() {
beforeAll((done)=>{
this.app = new N1Launcher(["--dev"]);
this.app.popoutComposerWindowReady().finally(done);
@ -77,23 +77,53 @@ fdescribe('Contenteditable Integration Spec', function() {
}).then(done).catch(done.fail)
});
// it("create a single item then delete it with backspace", (done) => {
// this.ce.test({
// keys: ['-', 'Space', 'a', 'Left arrow', 'Back space'],
// expectedHTML: "<span style=\"line-height: 1.4;\">a</span><br>",
// expectedSelectionResolver: (dom) => {
// return {node: dom.childNodes[0], offset: 0} }
// }).then(done).catch(done.fail)
// });
//
// it("create a single item then delete it with tab", (done) => {
// this.ce.test({
// keys: ['-', 'Space', 'a', 'Shift', 'Tab'],
// expectedHTML: "<span style=\"line-height: 1.4;\">a</span><br>",
// expectedSelectionResolver: (dom) => {
// return {node: dom.childNodes[0], offset: 1} }
// }).then(done).catch(done.fail)
// });
// 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

@ -1,36 +0,0 @@
import {N1Launcher} from './integration-helper'
// Some unit tests, such as the Contenteditable specs need to be run with
// Spectron availble in the environment.
describe('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

@ -223,52 +223,6 @@ 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.