Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run each test in its own <iframe> #1

Merged
merged 9 commits into from
Jun 3, 2013
2 changes: 2 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
]);

Expand Down
16 changes: 15 additions & 1 deletion grunt/config/browserify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -88,6 +101,7 @@ var test = {

module.exports = {
basic: basic,
jasmine: jasmine,
test: test,
min: min,
transformer: transformer
Expand Down
22 changes: 19 additions & 3 deletions grunt/config/jsx/jsx.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,40 @@ 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 = {
rootIDs: rootIDs.concat([
"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
};
5 changes: 4 additions & 1 deletion grunt/config/phantom.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
};
18 changes: 14 additions & 4 deletions grunt/tasks/browserify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions grunt/tasks/jsx.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ module.exports = function() {

var args = [
"bin/jsx",
"src",
"build/modules"
config.sourceDir,
config.outputDir
];

var rootIDs = expand({
Expand Down
8 changes: 8 additions & 0 deletions grunt/tasks/phantom.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 48 additions & 9 deletions src/test/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
4 changes: 4 additions & 0 deletions src/utils/__tests__/ImmutableObject-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,20 +100,23 @@ describe('ImmutableObject', function() {
});

testDev('should prevent shallow field addition when strict', function() {
if (window.callPhantom) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These become no-ops internally, yea?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests will pass, but there will be fewer expect-ations counted in the final tally.

These tests can still be run in the browser if you do grunt test --debug.

expect(function() {
var io = new ImmutableObject({oldField: 'asdf'});
io.newField = 'this will not work';
}).toThrow();
});

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!';
}).toThrow();
});

testDev('should prevent deep field addition when strict', function() {
if (window.callPhantom) return;
expect(function() {
var io =
new ImmutableObject({shallowField: {deepField: {oldField: null}}});
Expand All @@ -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}}});
Expand Down
18 changes: 18 additions & 0 deletions test/frame.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<script>
jasmine = parent.jasmine;
jasmine.exposeFrom(window);

console = parent.console;
callPhantom = parent.callPhantom;
</script>
<script src="react-test.js"></script>
</head>
<body>
<script>
require(window.frameElement.getAttribute("test"));
</script>
</body>
</html>
12 changes: 9 additions & 3 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="jasmine.css" />
<style type="text/css">
iframe {
visibility: hidden;
position: absolute;
left: -1000px;
top: -1000px;
}
</style>
<script src="jasmine.js"></script>
<script src="jasmine-html.js"></script>
<script src="react-test.js"></script>
<script>
window.onload = function() {
jasmine.getEnv().execute();
require("jasmine").getEnv().execute();
};
</script>
</head>
Expand Down
44 changes: 33 additions & 11 deletions test/phantom-harness.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <iframe>s.
var indexHtml = fs.read("index.html").replace(
/<body>([\s\S]*?)<\/body>/im,
function(outer, inner) {
return "<body>" + tests.map(function(test) {
return '\n <iframe src="frame.html" test=' +
JSON.stringify(test) + '></iframe>';
}).join("") + inner + "</body>";
}
);

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)) {
Expand All @@ -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();
});

Expand Down
2 changes: 2 additions & 0 deletions src/test/HtmlReporter.js → vendor/jasmine/HtmlReporter.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
var jasmine = require("./jasmine");

exports.HtmlReporter =
jasmine.HtmlReporter = function(_doc) {
var self = this;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
var jasmine = require("./jasmine");
var diff = require('./diff');

var red = '\u001b[1;41m';
Expand Down Expand Up @@ -54,7 +55,7 @@ PrintReporter.prototype.reportRunnerResults = function(runner) {
this.failCount + " fail"
].join(" "));

require("test/phantom").exit(this.failCount);
require("./phantom").exit(this.failCount);
};


Expand Down
2 changes: 2 additions & 0 deletions src/test/ReporterView.js → vendor/jasmine/ReporterView.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
var jasmine = require("jasmine");

jasmine.HtmlReporter.ReporterView = function(dom) {
this.startedAt = new Date();
this.runningSpecCount = 0;
Expand Down
Loading