diff --git a/.gitignore b/.gitignore index 996d275..40a53f4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ \.project bower_components node_modules -npm-debug\.log +npm-debug\.log* phantomjsdriver\.log # Build diff --git a/.npmignore b/.npmignore index 7875f18..580bb8f 100644 --- a/.npmignore +++ b/.npmignore @@ -5,7 +5,7 @@ \.project bower_components node_modules -npm-debug\.log +npm-debug\.log* phantomjsdriver\.log # Build diff --git a/.travis.yml b/.travis.yml index 196e306..07c7080 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,14 @@ branches: only: - master +env: + global: + - TEST_FUNC_PORT=3030 + - TEST_FUNC_HOST=127.0.0.1 + - SAUCE_CONNECT_TUNNEL_ID=$TRAVIS_JOB_NUMBER + - SAUCE_USERNAME=requirepack + - secure: "Gg2no6CkOreDPINT4xoJQPV/93Wq7MxZ7fwgATucuze9KYdidJaKFY8nEx3oJshFtPZ6t46JXxlVrR6n9De720R5RefHUBpuSVVzDedXsdYHXyjh6eNj1z88zteUcLp62hGipvAOrXUeC/l8OuCMCjeqIJtICYF4VJF7RlOyc5RnONOID0u62bogaGnwnHqTH/NlBVLjYDvPGrHhX2FB0TNQaRA89AaE69GyaCSFunEigpA48b/ogfHTF6OfHIvAvtLZf1hrQxGrf854snsqi/yi7+3Kz0m6EABINVGNmD29FqPteR5o7ZPquXt8A8IicL8omFM0jxgU4ma0hRFPvzZLaApFpsCtKfkN9h75LvFkTHkBNXXlaS/ZGMgxWzng6x7jOHxnoczoC2GrqPB48H0kZggHUa7O10gFhmmkbJOywAWv+X2cUaINL/Zn/mxons3JKa1ojb4DDT05TlpUDKv/zPhCAg0fB35GR2/XaZXs1wA8/2fA11VHHfB8Wea91sV+V+3L4H3PD5UueHEV4pE40t54GkjrLVr1OSFIqkDjgVHEkmHJQrx9uVoON6uWT7TwNNK9v2Nt9pRyIYFlHdm1S7pb6h279meN7NC9DueMXp3CYzTwWPgzHGg032tBZ8ga4Nr7wLYZu40Ql+h7D3aw4TWKyPNRo5YM3HkxqRc=" + before_install: # GUI for real browsers. - export DISPLAY=:99.0 @@ -19,11 +27,26 @@ before_install: before_script: # Install dev. stuff (e.g., selenium drivers). - npm run install-dev + - nohup bash -c "node_modules/.bin/selenium-standalone start 2>&1 &" + - nohup bash -c "node_modules/.bin/http-server -a $TEST_FUNC_HOST -p $TEST_FUNC_PORT --silent 2>&1 &" + +addons: + sauce_connect: true script: # Run all base checks (with FF browser for functional tests). - npm run check-ci + # Sauce Labs + - >- + node test/util/run-concurrent.js "npm run test-func" + '[ { "ROWDY_SETTINGS":"sauceLabs.IE_8_Windows_2008_Desktop" }, + { "ROWDY_SETTINGS":"sauceLabs.IE_9_Windows_2008_Desktop" }, + { "ROWDY_SETTINGS":"sauceLabs.IE_10_Windows_2012_Desktop" }, + { "ROWDY_SETTINGS":"sauceLabs.safari_8_OS_X_10_10_Desktop" }, + { "ROWDY_SETTINGS":"sauceLabs.chrome_43_OS_X_10_10_Desktop" }, + { "ROWDY_SETTINGS":"local.firefox" } ]' + # Manually send coverage reports to coveralls. - ls coverage/*/lcov.info | cat - cat coverage/*/lcov.info | ./node_modules/.bin/coveralls || echo "Coveralls upload failed" diff --git a/README.md b/README.md index d35f6ad..d3508ce 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ -[![Travis Status][trav_img]][trav_site] - RequirePack =========== [Webpack][webpack] + [RequireJS][requirejs] shared library interoperability! +[![Travis Status][trav_img]][trav_site] +[![Coverage Status][cov_img]][cov_site] + +[![Sauce Test Status][sauce_img]][sauce_site] + Webpack and RequireJS both can consume AMD code straight up. Which means if you are in a transitional / complex scenario where you want _some_ code to load with Webpack and _other_ code to load with RequireJS from the same code base @@ -53,3 +56,9 @@ Ports various servers run on: [rjs-exclude]: https://github.com/jrburke/r.js/blob/master/build/example.build.js#L388-L398 [trav_img]: https://api.travis-ci.org/FormidableLabs/requirepack.svg [trav_site]: https://travis-ci.org/FormidableLabs/requirepack +[sauce]: https://saucelabs.com +[sauce_img]: https://saucelabs.com/browser-matrix/requirepack.svg +[sauce_site]: https://saucelabs.com/u/requirepack +[cov]: https://coveralls.io +[cov_img]: https://img.shields.io/coveralls/FormidableLabs/requirepack.svg +[cov_site]: https://coveralls.io/r/FormidableLabs/requirepack diff --git a/package.json b/package.json index f8f4bc7..6f88c52 100644 --- a/package.json +++ b/package.json @@ -54,13 +54,16 @@ "jquery": "^1.11.3", "mocha": "^2.3.3", "phantomjs": "^1.9.18", - "require-handlebars-plugin": "^1.0.0", + "require-handlebars-plugin": "FormidableLabs/require-handlebars-plugin#bug-ie8-forEach", "requirejs": "^2.1.20", - "rowdy": "^0.3.2", + "rowdy": "^0.3.4", + "sauce-connect-launcher": "^0.13.0", "saucelabs": "^1.0.1", "selenium-standalone": "^4.6.3", + "server-destroy": "^1.0.1", "sinon": "^1.17.1", "sinon-chai": "^2.8.0", + "tree-kill": "^1.0.0", "webdriverio": "^3.2.4", "webpack": "^1.12.2" } diff --git a/test/func/complex-amd/spec/application.spec.js b/test/func/complex-amd/spec/application.spec.js index fe59640..35e2bc3 100644 --- a/test/func/complex-amd/spec/application.spec.js +++ b/test/func/complex-amd/spec/application.spec.js @@ -5,13 +5,23 @@ var promiseDone = require("../../../util/promise-done"); var build = require("../build"); describe(build.scenario, function () { - Object.keys(build.PAGES).forEach(function (page) { + // Extra build to force coverage. + before(function (done) { + build.buildRequirePack(done); + }); + + build.getTestPages().forEach(function (page) { it(page, function (done) { var url = global.TEST_FUNC_BASE_URL + path.join(build.scenario, "dist", page); global.adapter.client .url(url) + // Check errors + .getText("#error").then(function (text) { + expect(text).to.not.be.ok; + }) + // Check headings .getText("#app1").then(function (text) { expect(text).to.equal("App 1"); diff --git a/test/func/handlebars-amd/spec/application.spec.js b/test/func/handlebars-amd/spec/application.spec.js index 1eec113..e54222f 100644 --- a/test/func/handlebars-amd/spec/application.spec.js +++ b/test/func/handlebars-amd/spec/application.spec.js @@ -5,13 +5,23 @@ var promiseDone = require("../../../util/promise-done"); var build = require("../build"); describe(build.scenario, function () { - Object.keys(build.PAGES).forEach(function (page) { + // Extra build to force coverage. + before(function (done) { + build.buildRequirePack(done); + }); + + build.getTestPages().forEach(function (page) { it(page, function (done) { var url = global.TEST_FUNC_BASE_URL + path.join(build.scenario, "dist", page); global.adapter.client .url(url) + // Check errors + .getText("#error").then(function (text) { + expect(text).to.not.be.ok; + }) + // Check headings .getText("#app1").then(function (text) { expect(text).to.equal("App 1"); diff --git a/test/func/mocha.opts b/test/func/mocha.opts index 633620d..019bc6f 100644 --- a/test/func/mocha.opts +++ b/test/func/mocha.opts @@ -1,3 +1,3 @@ --require test/func/setup.js --recursive ---timeout 10000 +--timeout 25000 diff --git a/test/func/setup.js b/test/func/setup.js index 741e518..dfb95d9 100644 --- a/test/func/setup.js +++ b/test/func/setup.js @@ -4,16 +4,27 @@ * Test setup for functional tests. */ var chai = require("chai"); +var startSelenium = process.env.TRAVIS !== "true"; // Enable Rowdy with webdriverio. var _ = require("lodash"); var rowdy = require("rowdy")(_.merge({}, require("rowdy/config"), { options: { - driverLib: "webdriverio" + driverLib: "webdriverio", + server: { + start: startSelenium + } } })); var Adapter = rowdy.adapters.mocha; +// Patch rowdy to force not started. +// TODO: FIX IN ROWDY +// https://github.com/FormidableLabs/rowdy/issues/40 +if ((rowdy.setting.server || {}).start) { + rowdy.setting.server.start = startSelenium; +} + // Add test lib globals. global.expect = chai.expect; global.adapter = new Adapter(); diff --git a/test/func/simple-amd/spec/application.spec.js b/test/func/simple-amd/spec/application.spec.js index 1eec113..e54222f 100644 --- a/test/func/simple-amd/spec/application.spec.js +++ b/test/func/simple-amd/spec/application.spec.js @@ -5,13 +5,23 @@ var promiseDone = require("../../../util/promise-done"); var build = require("../build"); describe(build.scenario, function () { - Object.keys(build.PAGES).forEach(function (page) { + // Extra build to force coverage. + before(function (done) { + build.buildRequirePack(done); + }); + + build.getTestPages().forEach(function (page) { it(page, function (done) { var url = global.TEST_FUNC_BASE_URL + path.join(build.scenario, "dist", page); global.adapter.client .url(url) + // Check errors + .getText("#error").then(function (text) { + expect(text).to.not.be.ok; + }) + // Check headings .getText("#app1").then(function (text) { expect(text).to.equal("App 1"); diff --git a/test/func/spec/base.spec.js b/test/func/spec/base.spec.js index accf51e..da4ce34 100644 --- a/test/func/spec/base.spec.js +++ b/test/func/spec/base.spec.js @@ -1,5 +1,5 @@ "use strict"; -/*eslint-disable max-statements*/ +/*eslint-disable max-statements, no-invalid-this */ /** * Base server unit test initialization / global before/after's. @@ -9,16 +9,57 @@ // Set test environment process.env.NODE_ENV = process.env.NODE_ENV || "test-func"; +// ---------------------------------------------------------------------------- +// Sauce Connect Tunnel +// ---------------------------------------------------------------------------- +var rowdy = require("rowdy"); +var isSauceLabs = rowdy.config.setting.isSauceLabs; + +if (isSauceLabs && process.env.LAUNCH_SAUCE_CONNECT === "true") { + var connect = require("sauce-connect-launcher"); + var connectPs; + + before(function (done) { + // SC takes a **long** time. + this.timeout(60000); + + connect({ + username: rowdy.config.setting.host, + accessKey: rowdy.config.setting.key, + verbose: true + }, function (err, ps) { + if (err) { return done(err); } + // Stash process. + connectPs = ps; + + // Patch settings + //obj.desiredCapabilities.tunnelIdentifier = + + done(); + }); + }); + + after(function (done) { + if (connectPs) { + this.timeout(30000); + return connectPs.close(done); + } + + done(); + }); +} + // ---------------------------------------------------------------------------- // Selenium (Webdriverio/Rowdy) initialization // ---------------------------------------------------------------------------- // **Note** Can stash adapter, but not `adapter.client` because it is a lazy // getter that relies on the global `before|beforeEach` setup. var adapter = global.adapter; -var ELEM_WAIT = 500; // Global wait. +var ELEM_WAIT = isSauceLabs ? 5000 : 500; // Global wait. adapter.before(); before(function (done) { + if (isSauceLabs) { this.timeout(20000); } adapter.client // Set timeout for waiting on elements. .timeoutsImplicitWait(ELEM_WAIT) @@ -35,7 +76,9 @@ adapter.after(); var APP_PORT = process.env.TEST_FUNC_PORT || 3030; var APP_HOST = process.env.TEST_FUNC_HOST || "127.0.0.1"; var httpServer = require("http-server"); +var enableDestroy = require("server-destroy"); var server; +var realServer; // ---------------------------------------------------------------------------- // Globals @@ -50,13 +93,21 @@ before(function () { // App server // ---------------------------------------------------------------------------- before(function (done) { + if (process.env.TRAVIS === "true") { return done(); } + server = httpServer.createServer(); server.listen(APP_PORT, APP_HOST, done); + + // `http-server` doesn't pass enough of the underlying server, so we capture it. + realServer = server.server; + + // Wrap the server with a "REALLY REALLY KILL IT!" `destroy` method. + enableDestroy(realServer); }); after(function (done) { - if (!(server && server.server)) { return done(); } - // `http-server` doesn't pass the close callback, so we hack into the - // underlying implementation. Sigh. - server.server.close(done); + if (!realServer) { return done(); } + + // Take that server! + realServer.destroy(done); }); diff --git a/test/util/build.js b/test/util/build.js index 38bc81c..2346ccf 100644 --- a/test/util/build.js +++ b/test/util/build.js @@ -54,7 +54,8 @@ Build.prototype.PAGES = { { src: "../requirejs.config.js" }, function () { require.config({ - baseUrl: "../src" + baseUrl: "../src", + waitSeconds: 0 }); require(["lib"], function () { require(["app1", "app2"]); @@ -115,6 +116,11 @@ Build.prototype.PAGES = { ] }; +// Provide pages to test. +Build.prototype.getTestPages = function () { + return _.keys(this.PAGES); +}; + // Helpers Build.prototype._writeHtml = function (destPath, scripts, callback) { var dest = path.join(this.destDir, destPath); diff --git a/test/util/run-concurrent.js b/test/util/run-concurrent.js new file mode 100644 index 0000000..8d57d7c --- /dev/null +++ b/test/util/run-concurrent.js @@ -0,0 +1,63 @@ +"use strict"; + +/** + * Run multiple commands concurrently. + * + * Usage: + * + * node test/util/run-concurrent.js "npm run test-func" "[ {ENV_VARS1}, {ENV_VARS2} ]" + */ +var _ = require("lodash"); +var async = require("async"); +var kill = require("tree-kill"); +var spawn = require("child_process").spawn; + +// Parse argv. +var args = process.argv; +if (args.length !== 4) { + throw new Error("requires two arguments: [command] [ENV_VARS_ARRAY]"); +} + +// Get the arguments. +// Command: Assume splitable on space. (VERY NAIVE). +var cmdParts = args[2].split(" "); +var cmd = cmdParts[0]; +var cmdFlags = _.rest(cmdParts); +var envObjs = JSON.parse(args[3]); + +// Track the processes. +var procs = []; + +// Let's go parallel! +async.map(envObjs, function (env, cb) { + var err; + + var proc = spawn(cmd, cmdFlags, { + stdio: "inherit", + env: _.merge({}, process.env, env) + }); + + proc.on("close", function (code) { + if (!err && code !== 0) { + err = new Error( + "non-zero exit of " + args[2] + + " w/ env: " + JSON.stringify(env) + + " w/ code: " + code); + + err.code = code; + } + + cb(err); + }); + + procs.push(proc); +}, function (err) { + if (err) { + // Kill all existing procs. + async.map(procs, function (proc, cb) { + kill(proc.pid, "SIGKILL", cb); + }, function (killErr) { + throw killErr || err; + }); + } +}); diff --git a/test/util/templates.js b/test/util/templates.js index ba339a5..b43bd3e 100644 --- a/test/util/templates.js +++ b/test/util/templates.js @@ -21,7 +21,20 @@ module.exports = { " Demo", " ", " ", - "
" + "
", + "
", + " " ], (scripts || []).map(function (script) { // Strings are treated as inline functions to wrap in a closure.