diff --git a/script/utils/child-process-wrapper.js b/script/utils/child-process-wrapper.js
index 3a05973d5..095d431d0 100644
--- a/script/utils/child-process-wrapper.js
+++ b/script/utils/child-process-wrapper.js
@@ -14,6 +14,7 @@ exports.safeExec = function(command, options, callback) {
// The default is 200KB.
options.maxBuffer = 1024 * 1024;
+ options.stdio = "inherit"
var child = childProcess.exec(command, options, function(error, stdout, stderr) {
if (error && !options.ignoreStderr)
process.exit(error.code || 1);
@@ -31,9 +32,8 @@ exports.safeSpawn = function(command, args, options, callback) {
callback = options;
options = {};
}
+ options.stdio = "inherit"
var child = childProcess.spawn(command, args, options);
- child.stderr.pipe(process.stderr);
- child.stdout.pipe(process.stdout);
child.on('error', function(error) {
console.error('Command \'' + command + '\' failed: ' + error.message);
});
diff --git a/spec_integration/bootstrap.js b/spec_integration/bootstrap.js
index 73bffb797..df6fe59e7 100644
--- a/spec_integration/bootstrap.js
+++ b/spec_integration/bootstrap.js
@@ -2,11 +2,38 @@
// 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);
+var chalk = require('chalk')
+var util = require('util')
+
+console.errorColor = function(err){
+ if (typeof err === "string") {
+ console.error(chalk.red(err));
+ } else {
+ console.error(chalk.red(util.inspect(err)));
+ }
+}
+
+console.inspect = function(val) {
+ console.log(util.inspect(val, true, depth=7, colorize=true));
+}
+
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
+
+Promise = require('bluebird')
+Promise.config({
+ warnings: true,
+ longStackTraces: true,
+ cancellation: true
+})
+
+process.on("unhandledRejection", function(reason, promise) {
+ if (reason.stack) { console.errorColor(reason.stack); }
+ console.errorColor(promise);
+});
+
diff --git a/spec_integration/contenteditable-integration-spec.es6 b/spec_integration/contenteditable-integration-spec.es6
index 47c7e4157..d4db223d8 100644
--- a/spec_integration/contenteditable-integration-spec.es6
+++ b/spec_integration/contenteditable-integration-spec.es6
@@ -1,18 +1,14 @@
-import Promise from 'bluebird'
import {N1Launcher} from './integration-helper'
import ContenteditableTestHarness from './contenteditable-test-harness.es6'
fdescribe('Contenteditable Integration Spec', function() {
beforeAll((done)=>{
- console.log("----------- BEFORE ALL");
- // Boot in dev mode with no arguments
this.app = new N1Launcher(["--dev"]);
this.app.popoutComposerWindowReady().finally(done);
});
beforeEach((done) => {
- console.log("----------- BEFORE EACH");
- this.ce = new ContenteditableTestHarness(this.app.client, expect)
+ this.ce = new ContenteditableTestHarness(this.app.client)
this.ce.init().finally(done);
});
@@ -24,51 +20,120 @@ fdescribe('Contenteditable Integration Spec', function() {
}
});
- fit("Creates ordered lists", (done)=> {
- console.log("RUNNING KEYS");
- this.app.client.keys(["1", ".", "Space"]).then(()=>{
- console.log("DONE FIRING KEYS");
- e1 = this.ce.expectHTML("
- WOOO
")
- e2 = this.ce.expectSelection((dom) => {
- return {node: dom.querySelectorAll("li")[0]}
- })
- return Promise.all(e1,e2)
- }).catch((err)=>{ console.log("XXXX ERROR"); console.log(err); }).finally(done)
+
+
+ describe('Manipulating Lists', () => {
+ it("Creates ordered lists", (done)=> {
+ this.ce.test({
+ keys: ["1", ".", "Space"],
+ expectedHTML: "
",
+ expectedSelectionResolver: (dom) => {
+ return {node: dom.querySelectorAll('li')[0]} }
+ }).then(done).catch(done.fail)
+ });
+
+ it('Undoes ordered list creation with backspace', (done) => {
+ this.ce.test({
+ keys: ["1", ".", "Space", "Back space"],
+ expectedHTML: "1.
",
+ expectedSelectionResolver: (dom) => {
+ return {node: dom.childNodes[0], offset: 3} }
+ }).then(done).catch(done.fail)
+ });
+
+ it("Creates unordered lists with star", (done) => {
+ this.ce.test({
+ keys: ['*', 'Space'],
+ expectedHTML: "",
+ expectedSelectionResolver: (dom) => {
+ return {node: dom.querySelectorAll("li")[0] } }
+ }).then(done).catch(done.fail)
+ });
+
+ it("Undoes unordered list creation with backspace", (done) => {
+ this.ce.test({
+ keys: ['*', 'Space', 'Back space'],
+ expectedHTML: "*
",
+ expectedSelectionResolver: (dom) => {
+ return {node: dom.childNodes[0], offset: 2} }
+ }).then(done).catch(done.fail)
+ });
+
+ it("Creates unordered lists with dash", (done) => {
+ this.ce.test({
+ keys: ['-', 'Space'],
+ expectedHTML: "",
+ expectedSelectionResolver: (dom) => {
+ return {node: dom.querySelectorAll("li")[0] } }
+ }).then(done).catch(done.fail)
+ });
+
+ it("Undoes unordered list creation with backspace", (done) => {
+ this.ce.test({
+ keys: ['-', 'Space', 'Back space'],
+ expectedHTML: "-
",
+ expectedSelectionResolver: (dom) => {
+ return {node: dom.childNodes[0], offset: 2} }
+ }).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: "a
",
+ // 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: "a
",
+ // expectedSelectionResolver: (dom) => {
+ // return {node: dom.childNodes[0], offset: 1} }
+ // }).then(done).catch(done.fail)
+ // });
+
});
- 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)
- });
+ describe('Ensuring popout composer window works', () => {
+ it("has main window visible", (done)=> {
+ this.app.client.isWindowVisible()
+ .then((result)=>{ expect(result).toBe(true) })
+ .then(done).catch(done.fail)
+ });
- it("doesn't have the dev tools open", (done)=> {
- this.app.client.isWindowDevToolsOpened()
- .then((result)=>{ expect(result).toBe(false) })
- .finally(done)
- });
+ it("has main window focused", (done)=> {
+ this.app.client.isWindowFocused()
+ .then((result)=>{ expect(result).toBe(true) })
+ .then(done).catch(done.fail)
+ });
- it("has width", (done)=> {
- this.app.client.getWindowWidth()
- .then((result)=>{ expect(result).toBeGreaterThan(0) })
- .finally(done)
- });
+ it("isn't minimized", (done)=> {
+ this.app.client.isWindowMinimized()
+ .then((result)=>{ expect(result).toBe(false) })
+ .then(done).catch(done.fail)
+ });
- it("has height", (done)=> {
- this.app.client.getWindowHeight()
- .then((result)=>{ expect(result).toBeGreaterThan(0) })
- .finally(done)
+ it("doesn't have the dev tools open", (done)=> {
+ this.app.client.isWindowDevToolsOpened()
+ .then((result)=>{ expect(result).toBe(false) })
+ .then(done).catch(done.fail)
+ });
+
+ it("has width", (done)=> {
+ this.app.client.getWindowWidth()
+ .then((result)=>{ expect(result).toBeGreaterThan(0) })
+ .then(done).catch(done.fail)
+ });
+
+ it("has height", (done)=> {
+ this.app.client.getWindowHeight()
+ .then((result)=>{ expect(result).toBeGreaterThan(0) })
+ .then(done).catch(done.fail)
+ });
});
});
diff --git a/spec_integration/contenteditable-test-harness.es6 b/spec_integration/contenteditable-test-harness.es6
index 3766b1203..1283b22b5 100644
--- a/spec_integration/contenteditable-test-harness.es6
+++ b/spec_integration/contenteditable-test-harness.es6
@@ -1,43 +1,40 @@
-import Promise from 'bluebird'
-
class ContenteditableTestHarness {
- constructor(client, expect) {
- this.expect = expect
+ constructor(client) {
this.client = client;
}
init() {
- console.log("INIT TEST HARNESS");
return this.client.execute(() => {
ce = document.querySelector(".contenteditable")
ce.innerHTML = ""
ce.focus()
- }).then(({value})=>{
- console.log(value);
+ })
+ }
+
+ test({keys, expectedHTML, expectedSelectionResolver}) {
+ return this.client.keys(keys).then(()=>{
+ return this.expectHTML(expectedHTML)
+ }).then(()=>{
+ return this.expectSelection(expectedSelectionResolver)
})
}
expectHTML(expectedHTML) {
- console.log("EXPECTING HTML");
- console.log(expectedHTML);
- return this.client.execute((expect, arg2) => {
- console.log(expect);
- console.log(arg2);
- ce = document.querySelector(".contenteditable")
- expect(ce.innerHTML).toBe(expectedHTML)
- return ce.innerHTML
- }, this.expect, "FOOO").then(({value})=>{
- console.log("GOT HTML VALUE");
- console.log(value);
- }).catch((err)=>{
- console.log("XXXXXXXXXX GOT ERROR")
- console.log(err);
+ return this.client.execute(() => {
+ return document.querySelector(".contenteditable").innerHTML
+ }).then(({value})=>{
+ expect(value).toBe(expectedHTML)
})
}
expectSelection(callback) {
- return this.client.execute(() => {
+ // Since `execute` fires parameters to Selenium via REST API, we can
+ // only pass strings. We serialize the callback so we can run it in
+ // the correct execution environment of the window instead of the
+ // Selenium wrapper.
+ return this.client.execute((callbackStr) => {
+ eval(`callback=${callbackStr}`);
ce = document.querySelector(".contenteditable")
expectSel = callback(ce)
@@ -48,10 +45,51 @@ class ContenteditableTestHarness {
selection = document.getSelection()
- this.expect(selection.anchorNode).toBe(anchorNode)
- this.expect(selection.focusNode).toBe(focusNode)
- this.expect(selection.anchorOffset).toBe(anchorOffset)
- this.expect(selection.focusOffset).toBe(focusOffset)
+ return {
+ anchorNodeMatch: selection.anchorNode === anchorNode,
+ focusNodeMatch: selection.focusNode === focusNode,
+ anchorOffsetMatch: selection.anchorOffset === anchorOffset,
+ focusOffsetMatch: selection.focusOffset === focusOffset,
+ expectedAnchorNode: anchorNode.outerHTML,
+ expectedFocusNode: focusNode.outerHTML,
+ expectedAnchorOffset: anchorOffset,
+ expectedFocusOffset: focusOffset,
+ actualAnchorNode: selection.anchorNode.outerHTML,
+ actualFocusNode: selection.focusNode.outerHTML,
+ actualAnchorOffset: selection.anchorOffset,
+ actualFocusOffset: selection.focusOffset,
+ }
+
+ }, callback.toString()).then(({value}) => {
+ matchInfo = value
+
+ allMatched = true;
+ if (!matchInfo.anchorNodeMatch) {
+ console.errorColor("\nAnchor nodes don't match")
+ console.errorColor(`Expected: "${matchInfo.actualAnchorNode}" to be "${matchInfo.expectedAnchorNode}"`);
+ allMatched = false;
+ }
+ if (!matchInfo.focusNodeMatch) {
+ console.errorColor("\nFocus nodes don't match")
+ console.errorColor(`Expected: "${matchInfo.actualFocusNode}" to be "${matchInfo.expectedFocusNode}"`);
+ allMatched = false;
+ }
+ if (!matchInfo.anchorOffsetMatch) {
+ console.errorColor("\nAnchor offsets don't match")
+ console.errorColor(`Expected: ${matchInfo.actualAnchorOffset} to be ${matchInfo.expectedAnchorOffset}`);
+ allMatched = false;
+ }
+ if (!matchInfo.focusOffsetMatch) {
+ console.errorColor("\nFocus offsets don't match")
+ console.errorColor(`Expected: ${matchInfo.actualFocusOffset} to be ${matchInfo.expectedFocusOffset}`);
+ allMatched = false;
+ }
+
+ outMsgDescription = "matched. See discrepancies above"
+ if (allMatched) { outMsg = outMsgDescription
+ } else { outMsg = "Selection" }
+ // "Expected Selection to be matched. See discrepancies above"
+ expect(outMsg).toBe(outMsgDescription);
})
}
}
diff --git a/spec_integration/integration-helper.es6 b/spec_integration/integration-helper.es6
index cc7f945a6..61ec67b80 100644
--- a/spec_integration/integration-helper.es6
+++ b/spec_integration/integration-helper.es6
@@ -1,5 +1,4 @@
import path from 'path'
-import Promise from 'bluebird'
import {Application} from 'spectron';
class N1Launcher extends Application {
@@ -46,7 +45,7 @@ class N1Launcher extends Application {
return NylasEnv.getLoadSettings().windowType;
}).then(({value})=>{
if(value === "composer") {
- return client.isWindowVisible()
+ return client.isExisting(".contenteditable")
} else {
return false
}
diff --git a/spec_integration/package.json b/spec_integration/package.json
index 03e630d5b..ecde74ed7 100644
--- a/spec_integration/package.json
+++ b/spec_integration/package.json
@@ -12,6 +12,7 @@
"bluebird": "^3.0.5",
"babel-core": "^5.8.21",
"jasmine": "^2.3.2",
- "spectron": "^0.34.1"
+ "spectron": "^0.34.1",
+ "chalk": "^1.1"
}
}