mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-12 23:54:45 +08:00
refactor(spec): remove spectron from main app
This commit is contained in:
parent
783d835100
commit
f508dd8683
7 changed files with 56 additions and 348 deletions
|
@ -65,14 +65,12 @@
|
||||||
"service-hub": "^0.2.0",
|
"service-hub": "^0.2.0",
|
||||||
"source-map-support": "^0.3.2",
|
"source-map-support": "^0.3.2",
|
||||||
"space-pen": "3.8.2",
|
"space-pen": "3.8.2",
|
||||||
"spectron": "0.34.1",
|
|
||||||
"spellchecker": "^3.1.2",
|
"spellchecker": "^3.1.2",
|
||||||
"sqlite3": "https://github.com/mapbox/node-sqlite3/archive/v3.1.1.tar.gz",
|
"sqlite3": "https://github.com/mapbox/node-sqlite3/archive/v3.1.1.tar.gz",
|
||||||
"temp": "^0.8",
|
"temp": "^0.8",
|
||||||
"theorist": "^1.0",
|
"theorist": "^1.0",
|
||||||
"underscore": "^1.8",
|
"underscore": "^1.8",
|
||||||
"underscore.string": "^3.0",
|
"underscore.string": "^3.0"
|
||||||
"webdriverio": "3.2.6"
|
|
||||||
},
|
},
|
||||||
"packageDependencies": {},
|
"packageDependencies": {},
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|
|
@ -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
|
|
|
@ -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. "
|
|
||||||
@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 "* "
|
|
||||||
@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 "- "
|
|
||||||
@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']
|
|
|
@ -43,12 +43,6 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
|
||||||
|
|
||||||
NylasEnv.initialize()
|
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 = jasmine.getEnv()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {N1Launcher} from './integration-helper'
|
import {N1Launcher} from './integration-helper'
|
||||||
import ContenteditableTestHarness from './contenteditable-test-harness.es6'
|
import ContenteditableTestHarness from './contenteditable-test-harness.es6'
|
||||||
|
|
||||||
fdescribe('Contenteditable Integration Spec', function() {
|
describe('Contenteditable Integration Spec', function() {
|
||||||
beforeAll((done)=>{
|
beforeAll((done)=>{
|
||||||
this.app = new N1Launcher(["--dev"]);
|
this.app = new N1Launcher(["--dev"]);
|
||||||
this.app.popoutComposerWindowReady().finally(done);
|
this.app.popoutComposerWindowReady().finally(done);
|
||||||
|
@ -77,23 +77,53 @@ fdescribe('Contenteditable Integration Spec', function() {
|
||||||
}).then(done).catch(done.fail)
|
}).then(done).catch(done.fail)
|
||||||
});
|
});
|
||||||
|
|
||||||
// it("create a single item then delete it with backspace", (done) => {
|
// describe "when creating two items in a list", ->
|
||||||
// this.ce.test({
|
// beforeEach ->
|
||||||
// keys: ['-', 'Space', 'a', 'Left arrow', 'Back space'],
|
// @twoItemKeys = ['-', 'Space', 'a', 'Return', 'b']
|
||||||
// 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) => {
|
// it "creates two items with enter at end", -> waitsForPromise =>
|
||||||
// this.ce.test({
|
// @ce.keys(@twoItemKeys).then =>
|
||||||
// keys: ['-', 'Space', 'a', 'Shift', 'Tab'],
|
// @ce.expectHTML "<ul><li>a</li><li>b</li></ul>"
|
||||||
// expectedHTML: "<span style=\"line-height: 1.4;\">a</span><br>",
|
// @ce.expectSelection (dom) ->
|
||||||
// expectedSelectionResolver: (dom) => {
|
// node: dom.querySelectorAll('li')[1].childNodes[0]
|
||||||
// return {node: dom.childNodes[0], offset: 1} }
|
// offset: 1
|
||||||
// }).then(done).catch(done.fail)
|
//
|
||||||
// });
|
// 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']
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -223,52 +223,6 @@ class NylasEnvConstructor extends Model
|
||||||
window.onbeforeunload = => @_unloading()
|
window.onbeforeunload = => @_unloading()
|
||||||
@_unloadCallbacks = []
|
@_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
|
# Start our error reporting to the backend and attach error handlers
|
||||||
# to the window and the Bluebird Promise library, converting things
|
# to the window and the Bluebird Promise library, converting things
|
||||||
# back through the sourcemap as necessary.
|
# back through the sourcemap as necessary.
|
||||||
|
|
Loading…
Add table
Reference in a new issue