diff --git a/packages/test-in-browser/driver.js b/packages/test-in-browser/driver.js
index 41471e0d7e..e8a83180f5 100644
--- a/packages/test-in-browser/driver.js
+++ b/packages/test-in-browser/driver.js
@@ -1,12 +1,39 @@
-var running = true;
+////
+//// Setup
+////
-var resultTree = [];
-var failedTests = [];
-var resultsDeps = new Deps.Dependency;
-var countDeps = new Deps.Dependency;
+
+// dependency for the count of tests running/passed/failed, etc. drives
+// the navbar and the like.
+var countDep = new Deps.Dependency;
+// things that change on countDep
+var running = true;
var totalCount = 0;
var passedCount = 0;
var failedCount = 0;
+var failedTests = [];
+
+// Dependency for when a new top level group is added. Each group and
+// each test have their own dependency objects.
+var topLevelGroupsDep = new Deps.Dependency;
+
+// An array of top-level groups.
+//
+// Each group is an object with:
+// - name: string
+// - path: array of strings (names of parent groups)
+// - parent: parent group object (back reference)
+// - dep: Deps.Dependency object for this group. fires when new tests added.
+// - groups: list of sub-groups
+// - tests: list of tests in this group
+//
+// Each test is an object with:
+// - name: string
+// - parent: parent group object (back reference)
+// - server: boolean
+// - fullName: string
+// - dep: Deps.Dependency object for this test. fires when the test completes.
+var resultTree = [];
Session.setDefault("groupPath", ["tinytest"]);
@@ -17,7 +44,7 @@ Meteor.startup(function () {
Meteor._runTestsEverywhere(reportResults, function () {
running = false;
Meteor.onTestsComplete && Meteor.onTestsComplete();
- resultsDeps.changed();
+ countDep.changed();
Deps.flush();
Meteor.default_connection._unsubscribeAll();
@@ -25,38 +52,245 @@ Meteor.startup(function () {
});
+
+////
+//// Take incoming results and drive resultsTree
+////
+
+// report a series of events in a single test, or just the existence of
+// that test if no events. this is the entry point for test results to
+// this module.
+var reportResults = function(results) {
+ var test = _findTestForResults(results);
+
+ if (_.isArray(results.events)) {
+ // append events, if present
+ Array.prototype.push.apply((test.events || (test.events = [])),
+ results.events);
+ // sort and de-duplicate, based on sequence number
+ test.events.sort(function (a, b) {
+ return a.sequence - b.sequence;
+ });
+ var out = [];
+ _.each(test.events, function (e) {
+ if (out.length === 0 || out[out.length - 1].sequence !== e.sequence)
+ out.push(e);
+ });
+ test.events = out;
+ }
+ var status = _testStatus(test);
+ if (status === "failed") {
+ failedCount++;
+ // Expand a failed test (but only set this if the user hasn't clicked on the
+ // test name yet).
+ if (test.expanded === undefined)
+ test.expanded = true;
+ if (!_.contains(failedTests, test.fullName))
+ failedTests.push(test.fullName);
+
+ countDep.changed();
+ test.dep.changed();
+ } else if (status === "succeeded") {
+ passedCount++;
+ countDep.changed();
+ test.dep.changed();
+ } else if (test.expanded) {
+ // re-render the test if new results come in and the test is
+ // currently expanded.
+ test.dep.changed();
+ }
+};
+
+// forget all of the events for a particular test
+var forgetEvents = function (results) {
+ var test = _findTestForResults(results);
+ var status = _testStatus(test);
+ if (status === "failed") {
+ failedCount--;
+ countDep.changed();
+ } else if (status === "succeeded") {
+ passedCount--;
+ countDep.changed();
+ }
+ delete test.events;
+ test.dep.changed();
+};
+
+// given a 'results' as delivered via reportResults, find the
+// corresponding leaf object in resultTree, creating one if it doesn't
+// exist. it will be an object with attributes 'name', 'parent', and
+// possibly 'events'.
+var _findTestForResults = function (results) {
+ var groupPath = results.groupPath; // array
+ if ((! _.isArray(groupPath)) || (groupPath.length < 1)) {
+ throw new Error("Test must be part of a group");
+ }
+
+ var group;
+ var i = 0;
+ _.each(groupPath, function(gname) {
+ var array = (group ? (group.groups || (group.groups = []))
+ : resultTree);
+ var newGroup = _.find(array, function(g) { return g.name === gname; });
+ if (! newGroup) {
+ newGroup = {
+ name: gname,
+ parent: (group || null),
+ path: groupPath.slice(0, i+1),
+ dep: new Deps.Dependency
+ }; // create group
+ array.push(newGroup);
+
+ if (group)
+ group.dep.changed();
+ else
+ topLevelGroupsDep.changed();
+ }
+ group = newGroup;
+ i++;
+ });
+
+ var testName = results.test;
+ var server = !!results.server;
+ var test = _.find(group.tests || (group.tests = []),
+ function(t) { return t.name === testName &&
+ t.server === server; });
+ if (! test) {
+ // create test
+ var nameParts = _.clone(groupPath);
+ nameParts.push(testName);
+ var fullName = nameParts.join(' - ');
+ test = {
+ name: testName,
+ parent: group,
+ server: server,
+ fullName: fullName,
+ dep: new Deps.Dependency
+ };
+ group.tests.push(test);
+ group.dep.changed();
+ totalCount++;
+ countDep.changed();
+ }
+
+ return test;
+};
+
+
+
+////
+//// Helpers on test objects
+////
+
+var _testTime = function(t) {
+ if (t.events && t.events.length > 0) {
+ var lastEvent = _.last(t.events);
+ if (lastEvent.type === "finish") {
+ if ((typeof lastEvent.timeMs) === "number") {
+ return lastEvent.timeMs;
+ }
+ }
+ }
+ return null;
+};
+
+var _testStatus = function(t) {
+ var events = t.events || [];
+ if (_.find(events, function(x) { return x.type === "exception"; })) {
+ // "exception" should be last event, except race conditions on the
+ // server can make this not the case. Technically we can't tell
+ // if the test is still running at this point, but it can only
+ // result in FAIL.
+ return "failed";
+ } else if (events.length == 0 || (_.last(events).type != "finish")) {
+ return "running";
+ } else if (_.any(events, function(e) {
+ return e.type == "fail" || e.type == "exception"; })) {
+ return "failed";
+ } else {
+ return "succeeded";
+ }
+};
+
+
+
+////
+//// Templates
+////
+
+//// Template - navBars
+
+Template.navBars.running = function() {
+ countDep.depend();
+ return running;
+};
+
+Template.navBars.passed = function() {
+ countDep.depend();
+ return failedCount === 0;
+};
+
+Template.navBars.total_test_time = function() {
+ countDep.depend();
+
+ // walk whole tree to get all tests
+ var walk = function (groups) {
+ var total = 0;
+
+ _.each(groups || [], function (group) {
+ _.each(group.tests || [], function (t) {
+ total += _testTime(t);
+ });
+
+ total += walk(group.groups);
+ });
+
+ return total;
+ };
+
+ return walk(resultTree);
+};
+
+
+//// Template - progressBar
+
Template.progressBar.running = function () {
- countDeps.depend();
- return passedCount + failedCount < totalCount;
+ countDep.depend();
+ return running;
};
Template.progressBar.percentPass = function () {
- countDeps.depend();
+ countDep.depend();
if (totalCount === 0)
return 0;
return 100*passedCount/totalCount;
};
Template.progressBar.totalCount = function () {
+ countDep.depend();
return totalCount;
};
Template.progressBar.passedCount = function () {
+ countDep.depend();
return passedCount;
};
Template.progressBar.percentFail = function () {
- countDeps.depend();
+ countDep.depend();
if (totalCount === 0)
return 0;
return 100*failedCount/totalCount;
};
Template.progressBar.anyFail = function () {
- countDeps.depend();
+ countDep.depend();
return failedCount > 0;
};
+
+//// Template - groupNav
+
Template.groupNav.groupPaths = function () {
var groupPath = Session.get("groupPath");
var ret = [];
@@ -88,76 +322,47 @@ Template.groupNav.events({
}
});
+
+//// Template - failedTests
+
+Template.failedTests.failedTests = function() {
+ countDep.depend();
+ return failedTests;
+};
+
+
+
+//// Template - testTable
+
+Template.testTable.data = function() {
+ topLevelGroupsDep.depend();
+ return resultTree;
+};
+
+
+//// Template - test_group
+
+Template.test_group.groupDep = function () {
+ // this template just establishes a dependency. It doesn't actually
+ // render anything.
+ this.dep.depend();
+ return "";
+};
+
Template.test_group.events({
"click .groupname": function () {
changeToPath(this.path);
}
});
-Template.test_table.running = function() {
- resultsDeps.depend();
- return running;
-};
-Template.test_table.passed = function() {
- resultsDeps.depend();
+//// Template - test
- // walk whole tree to look for failed tests
- var walk = function (groups) {
- var ret = true;
-
- _.each(groups || [], function (group) {
- if (!ret)
- return;
-
- _.each(group.tests || [], function (t) {
- if (!ret)
- return;
- if (_testStatus(t) === "failed")
- ret = false;
- });
-
- if (!walk(group.groups))
- ret = false;
- });
-
- return ret;
- };
-
- return walk(resultTree);
-};
-
-
-Template.test_table.total_test_time = function() {
- resultsDeps.depend();
-
- // walk whole tree to get all tests
- var walk = function (groups) {
- var total = 0;
-
- _.each(groups || [], function (group) {
- _.each(group.tests || [], function (t) {
- total += _testTime(t);
- });
-
- total += walk(group.groups);
- });
-
- return total;
- };
-
- return walk(resultTree);
-};
-
-
-
-Template.test_table.data = function() {
- resultsDeps.depend();
- return resultTree;
-};
-Template.test_table.failedTests = function() {
- resultsDeps.depend();
- return failedTests;
+Template.test.testDep = function () {
+ // this template just establishes a dependency. It doesn't actually
+ // render anything.
+ this.dep.depend();
+ return "";
};
Template.test.test_status_display = function() {
@@ -192,7 +397,7 @@ Template.test.test_class = function() {
Template.test.events({
'click .testname': function() {
this.expanded = ! this.expanded;
- resultsDeps.changed();
+ this.dep.changed();
}
});
@@ -231,6 +436,9 @@ Template.test.eventsArray = function() {
});
};
+
+//// Template - event
+
Template.event.events({
'click .debug': function () {
// the way we manage groupPath, shortName, cookies, etc, is really
@@ -285,136 +493,3 @@ Template.event.is_debuggable = function() {
return !!this.cookie;
};
-var _testTime = function(t) {
- if (t.events && t.events.length > 0) {
- var lastEvent = _.last(t.events);
- if (lastEvent.type === "finish") {
- if ((typeof lastEvent.timeMs) === "number") {
- return lastEvent.timeMs;
- }
- }
- }
- return null;
-};
-
-var _testStatus = function(t) {
- var events = t.events || [];
- if (_.find(events, function(x) { return x.type === "exception"; })) {
- // "exception" should be last event, except race conditions on the
- // server can make this not the case. Technically we can't tell
- // if the test is still running at this point, but it can only
- // result in FAIL.
- return "failed";
- } else if (events.length == 0 || (_.last(events).type != "finish")) {
- return "running";
- } else if (_.any(events, function(e) {
- return e.type == "fail" || e.type == "exception"; })) {
- return "failed";
- } else {
- return "succeeded";
- }
-};
-
-// given a 'results' as delivered via setReporter, find the
-// corresponding leaf object in resultTree, creating one if it doesn't
-// exist. it will be an object with attributes 'name', 'parent', and
-// possibly 'events'.
-var _findTestForResults = function (results) {
- var groupPath = results.groupPath; // array
- if ((! _.isArray(groupPath)) || (groupPath.length < 1)) {
- throw new Error("Test must be part of a group");
- }
-
- var group;
- var i = 0;
- _.each(groupPath, function(gname) {
- var array = (group ? (group.groups || (group.groups = []))
- : resultTree);
- var newGroup = _.find(array, function(g) { return g.name === gname; });
- if (! newGroup) {
- newGroup = {
- name: gname,
- parent: (group || null),
- path: groupPath.slice(0, i+1)
- }; // create group
- array.push(newGroup);
- }
- group = newGroup;
- i++;
- });
-
- var testName = results.test;
- var server = !!results.server;
- var test = _.find(group.tests || (group.tests = []),
- function(t) { return t.name === testName &&
- t.server === server; });
- if (! test) {
- // create test
- var nameParts = _.clone(groupPath);
- nameParts.push(testName);
- var fullName = nameParts.join(' - ');
- test = {name: testName, parent: group, server: server, fullName: fullName};
- group.tests.push(test);
- totalCount++;
- countDeps.changed();
- }
-
- return test;
-};
-
-// report a series of events in a single test, or just
-// the existence of that test if no events
-var reportResults = function(results) {
- var test = _findTestForResults(results);
-
- if (_.isArray(results.events)) {
- // append events, if present
- Array.prototype.push.apply((test.events || (test.events = [])),
- results.events);
- // sort and de-duplicate, based on sequence number
- test.events.sort(function (a, b) {
- return a.sequence - b.sequence;
- });
- var out = [];
- _.each(test.events, function (e) {
- if (out.length === 0 || out[out.length - 1].sequence !== e.sequence)
- out.push(e);
- });
- test.events = out;
- }
- var status = _testStatus(test);
- if (status === "failed") {
- failedCount++;
- countDeps.changed();
- // Expand a failed test (but only set this if the user hasn't clicked on the
- // test name yet).
- if (test.expanded === undefined)
- test.expanded = true;
- if (!_.contains(failedTests, test.fullName))
- failedTests.push(test.fullName);
- } else if (status === "succeeded") {
- passedCount++;
- countDeps.changed();
- }
-
- _throttled_update();
-};
-
-// forget all of the events for a particular test
-var forgetEvents = function (results) {
- var test = _findTestForResults(results);
- var status = _testStatus(test);
- if (status === "failed") {
- failedCount--;
- countDeps.changed();
- } else if (status === "succeeded") {
- passedCount--;
- countDeps.changed();
- }
- delete test.events;
- resultsDeps.changed();
-};
-
-var _throttled_update = _.throttle(function() {
- resultsDeps.changed();
-}, 1000);