diff --git a/Gruntfile.js b/Gruntfile.js index e3b4940e12153..dd04aa2d139cc 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -53,7 +53,9 @@ module.exports = function(grunt) { grunt.registerTask('build:min', ['jsx:release', 'browserify:min']); grunt.registerTask('build:test', [ 'jsx:debug', + 'jsx:jasmine', 'jsx:test', + 'browserify:jasmine', 'browserify:test' ]); diff --git a/grunt/config/browserify.js b/grunt/config/browserify.js index 35ffef9d807c2..7594e95544289 100644 --- a/grunt/config/browserify.js +++ b/grunt/config/browserify.js @@ -76,10 +76,23 @@ var transformer = { after: [simpleBannerify] }; +var jasmine = { + entries: [ + "./build/jasmine/all.js" + ], + requires: { + "jasmine": "./build/jasmine/all.js" + }, + outfile: "./build/jasmine.js", + debug: false +}; + var test = { entries: [ "./build/modules/test/all.js", - "./build/modules/**/__tests__/*-test.js" + ], + requires: [ + "**/__tests__/*-test.js" ], outfile: './build/react-test.js', debug: false, @@ -88,6 +101,7 @@ var test = { module.exports = { basic: basic, + jasmine: jasmine, test: test, min: min, transformer: transformer diff --git a/grunt/config/jsx/jsx.js b/grunt/config/jsx/jsx.js index c75c318aca845..34fe98c16630e 100644 --- a/grunt/config/jsx/jsx.js +++ b/grunt/config/jsx/jsx.js @@ -6,7 +6,18 @@ var rootIDs = [ var debug = { rootIDs: rootIDs, - configFile: "grunt/config/jsx/debug.json" + configFile: "grunt/config/jsx/debug.json", + sourceDir: "src", + outputDir: "build/modules" +}; + +var jasmine = { + rootIDs: [ + "all" + ], + configFile: debug.configFile, + sourceDir: "vendor/jasmine", + outputDir: "build/jasmine" }; var test = { @@ -14,16 +25,21 @@ var test = { "test/all.js", "**/__tests__/*.js" ]), - configFile: debug.configFile + configFile: debug.configFile, + sourceDir: "src", + outputDir: "build/modules" }; var release = { rootIDs: rootIDs, - configFile: "grunt/config/jsx/release.json" + configFile: "grunt/config/jsx/release.json", + sourceDir: "src", + outputDir: "build/modules" }; module.exports = { debug: debug, + jasmine: jasmine, test: test, release: release }; diff --git a/grunt/config/phantom.js b/grunt/config/phantom.js index c5bfc7717af1d..36cbc0f3ded11 100644 --- a/grunt/config/phantom.js +++ b/grunt/config/phantom.js @@ -4,5 +4,8 @@ exports.run = { port: 8080, harness: "test/phantom-harness.js", // Run `grunt test --debug` to enable in-browser testing. - debug: !!grunt.option("debug") + debug: !!grunt.option("debug"), + tests: [ + "**/__tests__/*-test.js" + ] }; diff --git a/grunt/tasks/browserify.js b/grunt/tasks/browserify.js index b73b67bd19f77..d43c0841f8619 100644 --- a/grunt/tasks/browserify.js +++ b/grunt/tasks/browserify.js @@ -12,7 +12,6 @@ module.exports = function() { // More/better assertions // grunt.config.requires('outfile'); // grunt.config.requires('entries'); - config.requires = config.requires || {}; config.transforms = config.transforms || []; config.after = config.after || []; if (typeof config.after === 'function') { @@ -24,9 +23,20 @@ module.exports = function() { var bundle = browserify(entries); // Make sure the things that need to be exposed are. - // TODO: support a blob pattern maybe? - for (var name in config.requires) { - bundle.require(config.requires[name], { expose: name }); + var requires = config.requires || {}; + if (requires instanceof Array) { + grunt.file.expand({ + nonull: true, // Keep IDs that don't expand to anything. + cwd: "src" + }, requires).forEach(function(name) { + bundle.require("./build/modules/" + name, { + expose: name.replace(/\.js$/i, "") + }); + }); + } else if (typeof requires === "object") { + Object.keys(requires).forEach(function(name) { + bundle.require(requires[name], { expose: name }); + }); } // Extract other options diff --git a/grunt/tasks/jsx.js b/grunt/tasks/jsx.js index 1ecee11fd7c98..6b2f4123d74d1 100644 --- a/grunt/tasks/jsx.js +++ b/grunt/tasks/jsx.js @@ -9,8 +9,8 @@ module.exports = function() { var args = [ "bin/jsx", - "src", - "build/modules" + config.sourceDir, + config.outputDir ]; var rootIDs = expand({ diff --git a/grunt/tasks/phantom.js b/grunt/tasks/phantom.js index 2134dcde63a7d..ff71f683c69a2 100644 --- a/grunt/tasks/phantom.js +++ b/grunt/tasks/phantom.js @@ -37,6 +37,14 @@ function run(config, done) { args.push("--debug"); } + args.push("--tests"); + var tests = grunt.file.expand({ + nonull: true, + cwd: "src" + }, config.tests || []).forEach(function(file) { + args.push(file.replace(/\.js$/i, "")); + }); + var child = spawn({ cmd: phantomjs, args: args diff --git a/src/test/all.js b/src/test/all.js index c6511b9f0a4ca..b370c31b629f8 100644 --- a/src/test/all.js +++ b/src/test/all.js @@ -2,17 +2,56 @@ // modules in src/test and to specify an ordering on those modules, since // some still have implicit dependencies on others. -require("./phantom"); -require("./console"); +var Ap = Array.prototype; +var slice = Ap.slice; +var Fp = Function.prototype; + +if (!Fp.bind) { + // PhantomJS doesn't support Function.prototype.bind natively, so + // polyfill it whenever this module is required. + Fp.bind = function(context) { + var func = this; + var args = slice.call(arguments, 1); + var bound; + + if (func.prototype) { + if (args.length > 0) { + bound = function() { + return func.apply( + !(this instanceof func) && context || this, + args.concat(slice.call(arguments)) + ); + }; + } else { + bound = function() { + return func.apply( + !(this instanceof func) && context || this, + arguments + ); + }; + } + + bound.prototype = Object.create(func.prototype); + + } else if (args.length > 0) { + bound = function() { + return func.apply( + context || this, + args.concat(slice.call(arguments)) + ); + }; + } else { + bound = function() { + return func.apply(context || this, arguments); + }; + } + + return bound; + }; +} + require("ReactTestUtils"); require("reactComponentExpect"); -require("./diff"); -require("./PrintReporter"); -require("./HtmlReporter"); -require("./ReporterView"); -require("./SpecView"); -require("./SuiteView"); -require("./jasmine-support"); require("mocks"); require("mock-modules"); require("./mock-timers"); diff --git a/src/utils/__tests__/ImmutableObject-test.js b/src/utils/__tests__/ImmutableObject-test.js index 6075f576619c3..fbcda924aee6d 100644 --- a/src/utils/__tests__/ImmutableObject-test.js +++ b/src/utils/__tests__/ImmutableObject-test.js @@ -100,6 +100,7 @@ describe('ImmutableObject', function() { }); testDev('should prevent shallow field addition when strict', function() { + if (window.callPhantom) return; expect(function() { var io = new ImmutableObject({oldField: 'asdf'}); io.newField = 'this will not work'; @@ -107,6 +108,7 @@ describe('ImmutableObject', function() { }); testDev('should prevent shallow field mutation when strict', function() { + if (window.callPhantom) return; expect(function() { var io = new ImmutableObject({oldField: 'asdf'}); io.oldField = 'this will not work!'; @@ -114,6 +116,7 @@ describe('ImmutableObject', function() { }); testDev('should prevent deep field addition when strict', function() { + if (window.callPhantom) return; expect(function() { var io = new ImmutableObject({shallowField: {deepField: {oldField: null}}}); @@ -122,6 +125,7 @@ describe('ImmutableObject', function() { }); testDev('should prevent deep field mutation when strict', function() { + if (window.callPhantom) return; expect(function() { var io = new ImmutableObject({shallowField: {deepField: {oldField: null}}}); diff --git a/test/frame.html b/test/frame.html new file mode 100644 index 0000000000000..34061d7ce1a5f --- /dev/null +++ b/test/frame.html @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/test/index.html b/test/index.html index a609479571207..ee98b71067175 100644 --- a/test/index.html +++ b/test/index.html @@ -2,12 +2,18 @@ + - - diff --git a/test/phantom-harness.js b/test/phantom-harness.js index 0350e5c9cd80e..6dcfced557a91 100644 --- a/test/phantom-harness.js +++ b/test/phantom-harness.js @@ -19,36 +19,58 @@ fs.changeWorkingDirectory(cwd); // Hard to believe PhantomJS has no option parsing module. var port = 8080; var debug = false; -var lastArg; +var tests = []; +var rest = []; while (argv.length > 0) { var arg = argv.pop(); if (arg === "--port") { - port = +lastArg; + port = +rest.pop(); } else if (arg === "--debug") { debug = true; + } else if (arg === "--tests") { + while (rest.length > 0) + tests.push(rest.pop()); } - lastArg = arg; + rest.push(arg); } +// Dynamically interpolate the individual test '; + }).join("") + inner + ""; + } +); + var server = require("webserver").create(); server.listen(port, function(req, res) { var file = req.url.replace(/^\/+/, ""); + var content; switch (file) { - case "": - default: - file = "index.html"; - break; - case "react-test.js": file = "../build/" + file; break; case "jasmine.css": - case "jasmine.js": - case "jasmine-html.js": file = "../vendor/jasmine/" + file; break; + + case "jasmine.js": + file = "../build/" + file; + break; + + case "frame.html": + break; + + case "": + default: + file = "index.html"; + content = indexHtml; // Prevents calling fs.read again. + break; } if (/\.css$/i.test(file)) { @@ -60,7 +82,7 @@ server.listen(port, function(req, res) { } res.statusCode = 200; - res.write(fs.read(file)); + res.write(content || fs.read(file)); res.close(); }); diff --git a/src/test/HtmlReporter.js b/vendor/jasmine/HtmlReporter.js similarity index 99% rename from src/test/HtmlReporter.js rename to vendor/jasmine/HtmlReporter.js index fd2a5e4f3b6fe..8ed3d3742d78d 100644 --- a/src/test/HtmlReporter.js +++ b/vendor/jasmine/HtmlReporter.js @@ -1,3 +1,5 @@ +var jasmine = require("./jasmine"); + exports.HtmlReporter = jasmine.HtmlReporter = function(_doc) { var self = this; diff --git a/src/test/PrintReporter.js b/vendor/jasmine/PrintReporter.js similarity index 97% rename from src/test/PrintReporter.js rename to vendor/jasmine/PrintReporter.js index 4a76c0e932df9..9ffd94a1dc54f 100644 --- a/src/test/PrintReporter.js +++ b/vendor/jasmine/PrintReporter.js @@ -1,3 +1,4 @@ +var jasmine = require("./jasmine"); var diff = require('./diff'); var red = '\u001b[1;41m'; @@ -54,7 +55,7 @@ PrintReporter.prototype.reportRunnerResults = function(runner) { this.failCount + " fail" ].join(" ")); - require("test/phantom").exit(this.failCount); + require("./phantom").exit(this.failCount); }; diff --git a/src/test/ReporterView.js b/vendor/jasmine/ReporterView.js similarity index 99% rename from src/test/ReporterView.js rename to vendor/jasmine/ReporterView.js index 419accecc6476..21790dfd5a41a 100644 --- a/src/test/ReporterView.js +++ b/vendor/jasmine/ReporterView.js @@ -1,3 +1,5 @@ +var jasmine = require("jasmine"); + jasmine.HtmlReporter.ReporterView = function(dom) { this.startedAt = new Date(); this.runningSpecCount = 0; diff --git a/src/test/SpecView.js b/vendor/jasmine/SpecView.js similarity index 98% rename from src/test/SpecView.js rename to vendor/jasmine/SpecView.js index b9bcf08ac2a8c..8580bd2a59e3c 100644 --- a/src/test/SpecView.js +++ b/vendor/jasmine/SpecView.js @@ -1,3 +1,5 @@ +var jasmine = require("./jasmine"); + jasmine.HtmlReporter.SpecView = function(spec, dom, views) { this.spec = spec; this.dom = dom; diff --git a/src/test/SuiteView.js b/vendor/jasmine/SuiteView.js similarity index 94% rename from src/test/SuiteView.js rename to vendor/jasmine/SuiteView.js index a7f433f553246..917609e5f56f9 100644 --- a/src/test/SuiteView.js +++ b/vendor/jasmine/SuiteView.js @@ -1,3 +1,5 @@ +var jasmine = require("./jasmine"); + jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { this.suite = suite; this.dom = dom; diff --git a/vendor/jasmine/all.js b/vendor/jasmine/all.js new file mode 100644 index 0000000000000..2efe39c514395 --- /dev/null +++ b/vendor/jasmine/all.js @@ -0,0 +1,38 @@ +require("./phantom"); +require("./console"); + +// TODO Also bundle jasmine.css here. +var jasmine = require("./jasmine"); +require("./jasmine-html"); +require("./jasmine-support"); + +require("./HtmlReporter"); +require("./PrintReporter"); +require("./ReporterView"); +require("./SpecView"); +require("./SuiteView"); + +var env = jasmine.getEnv(); +env.addReporter(new jasmine.HtmlReporter); +env.addReporter(new jasmine.PrintReporter); + +function exposeFrom(obj) { + obj.spyOn = jasmine.spyOn; + obj.it = jasmine.it; + obj.xit = jasmine.xit; + obj.expect = jasmine.expect; + obj.runs = jasmine.runs; + obj.waits = jasmine.waits; + obj.waitsFor = jasmine.waitsFor; + obj.beforeEach = jasmine.beforeEach; + obj.afterEach = jasmine.afterEach; + obj.describe = jasmine.describe; + obj.xdescribe = jasmine.xdescribe; + obj.jasmine = jasmine; + return obj; +} +jasmine.exposeFrom = exposeFrom; +var global = Function("return this")(); +exposeFrom(global); + +module.exports = jasmine; diff --git a/src/test/console.js b/vendor/jasmine/console.js similarity index 100% rename from src/test/console.js rename to vendor/jasmine/console.js diff --git a/src/test/diff.js b/vendor/jasmine/diff.js similarity index 100% rename from src/test/diff.js rename to vendor/jasmine/diff.js diff --git a/vendor/jasmine/jasmine-html.js b/vendor/jasmine/jasmine-html.js index 543d56963eb4a..86688f32753f4 100644 --- a/vendor/jasmine/jasmine-html.js +++ b/vendor/jasmine/jasmine-html.js @@ -1,3 +1,5 @@ +var jasmine = require("./jasmine"); + jasmine.HtmlReporterHelpers = {}; jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { diff --git a/src/test/jasmine-support.js b/vendor/jasmine/jasmine-support.js similarity index 91% rename from src/test/jasmine-support.js rename to vendor/jasmine/jasmine-support.js index 07f47e622f768..d6d1e3ef3b873 100644 --- a/src/test/jasmine-support.js +++ b/vendor/jasmine/jasmine-support.js @@ -1,5 +1,4 @@ -var global = Function("return this")(); -var jasmine = global.jasmine; +var jasmine = require("./jasmine"); var spec = false; // TODO // Add some matcher for mock functions @@ -38,7 +37,7 @@ var _xit = jasmine.Env.prototype.xit; jasmine.Env.prototype.it = function(desc, func) { // If spec is provided, only run matching specs if (!spec || desc.match(new RegExp(spec, 'i'))) { - return _it.bind(this, desc, func)(); + return _it.call(this, desc, func); } else { return this.xit(desc, func); } @@ -51,7 +50,7 @@ jasmine.Env.prototype.xit = function(desc, func) { this.reporter.subReporters_[0].totalCount += matches.length; } } - return _xit.bind(this, desc, func)(); + return _xit.call(this, desc, func); } // Mainline Jasmine sets __Jasmine_been_here_before__ on each object to detect @@ -126,9 +125,3 @@ if (typeof WeakMap !== "undefined") { return (mismatchKeys.length == 0 && mismatchValues.length == 0); }; } - -var HtmlReporter = require("./HtmlReporter").HtmlReporter; -var PrintReporter = require("./PrintReporter").PrintReporter; - -jasmine.getEnv().addReporter(new HtmlReporter); -jasmine.getEnv().addReporter(new PrintReporter); diff --git a/vendor/jasmine/jasmine.js b/vendor/jasmine/jasmine.js index faee8f3eb19bb..684c7cdf5b437 100644 --- a/vendor/jasmine/jasmine.js +++ b/vendor/jasmine/jasmine.js @@ -1,12 +1,10 @@ -var isCommonJS = typeof window == "undefined" && typeof exports == "object"; - /** * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. * * @namespace */ var jasmine = {}; -if (isCommonJS) exports.jasmine = jasmine; +exports = module.exports = jasmine; /** * @private */ @@ -480,7 +478,7 @@ jasmine.log = function() { var spyOn = function(obj, methodName) { return jasmine.getEnv().currentSpec.spyOn(obj, methodName); }; -if (isCommonJS) exports.spyOn = spyOn; +exports.spyOn = spyOn; /** * Creates a Jasmine spec that will be added to the current suite. @@ -498,7 +496,7 @@ if (isCommonJS) exports.spyOn = spyOn; var it = function(desc, func) { return jasmine.getEnv().it(desc, func); }; -if (isCommonJS) exports.it = it; +exports.it = it; /** * Creates a disabled Jasmine spec. @@ -511,7 +509,7 @@ if (isCommonJS) exports.it = it; var xit = function(desc, func) { return jasmine.getEnv().xit(desc, func); }; -if (isCommonJS) exports.xit = xit; +exports.xit = xit; /** * Starts a chain for a Jasmine expectation. @@ -525,7 +523,7 @@ if (isCommonJS) exports.xit = xit; var expect = function(actual) { return jasmine.getEnv().currentSpec.expect(actual); }; -if (isCommonJS) exports.expect = expect; +exports.expect = expect; /** * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. @@ -535,7 +533,7 @@ if (isCommonJS) exports.expect = expect; var runs = function(func) { jasmine.getEnv().currentSpec.runs(func); }; -if (isCommonJS) exports.runs = runs; +exports.runs = runs; /** * Waits a fixed time period before moving to the next block. @@ -546,7 +544,7 @@ if (isCommonJS) exports.runs = runs; var waits = function(timeout) { jasmine.getEnv().currentSpec.waits(timeout); }; -if (isCommonJS) exports.waits = waits; +exports.waits = waits; /** * Waits for the latchFunction to return true before proceeding to the next block. @@ -558,7 +556,7 @@ if (isCommonJS) exports.waits = waits; var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); }; -if (isCommonJS) exports.waitsFor = waitsFor; +exports.waitsFor = waitsFor; /** * A function that is called before each spec in a suite. @@ -570,7 +568,7 @@ if (isCommonJS) exports.waitsFor = waitsFor; var beforeEach = function(beforeEachFunction) { jasmine.getEnv().beforeEach(beforeEachFunction); }; -if (isCommonJS) exports.beforeEach = beforeEach; +exports.beforeEach = beforeEach; /** * A function that is called after each spec in a suite. @@ -582,7 +580,7 @@ if (isCommonJS) exports.beforeEach = beforeEach; var afterEach = function(afterEachFunction) { jasmine.getEnv().afterEach(afterEachFunction); }; -if (isCommonJS) exports.afterEach = afterEach; +exports.afterEach = afterEach; /** * Defines a suite of specifications. @@ -602,7 +600,7 @@ if (isCommonJS) exports.afterEach = afterEach; var describe = function(description, specDefinitions) { return jasmine.getEnv().describe(description, specDefinitions); }; -if (isCommonJS) exports.describe = describe; +exports.describe = describe; /** * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. @@ -613,7 +611,7 @@ if (isCommonJS) exports.describe = describe; var xdescribe = function(description, specDefinitions) { return jasmine.getEnv().xdescribe(description, specDefinitions); }; -if (isCommonJS) exports.xdescribe = xdescribe; +exports.xdescribe = xdescribe; // Provide the XMLHttpRequest class for IE 5.x-6.x: diff --git a/src/test/phantom.js b/vendor/jasmine/phantom.js similarity index 71% rename from src/test/phantom.js rename to vendor/jasmine/phantom.js index 1741beae81f2f..af54e6f75e36a 100644 --- a/src/test/phantom.js +++ b/vendor/jasmine/phantom.js @@ -19,26 +19,6 @@ var console = require("./console"); var global = Function("return this")(); -var Ap = Array.prototype; -var slice = Ap.slice; -var Fp = Function.prototype; - -if (!Fp.bind) { - // PhantomJS doesn't support Function.prototype.bind natively, so - // polyfill it whenever this module is required. - Fp.bind = function(context) { - var func = this; - var args = slice.call(arguments, 1); - return args.length > 0 ? function() { - return func.apply( - context || this, - args.concat(slice.call(arguments)) - ); - } : function() { - return func.apply(context || this, arguments); - }; - }; -} if (global.callPhantom) { // Phantom's onConsoleMessage support is lacking (only one argument