aboutsummaryrefslogtreecommitdiffstats
path: root/src/scenario
diff options
context:
space:
mode:
authorVojta Jina2011-05-19 17:33:25 +0200
committerIgor Minar2011-05-19 09:43:56 -0700
commit1abdc097b235366759a889bdcc68359653a9b8a3 (patch)
treeed53346c171de6c60748e65c7f7f065cc8837103 /src/scenario
parent9f56af9c15e1096033c91c2619f7f7f0115d0032 (diff)
downloadangular.js-1abdc097b235366759a889bdcc68359653a9b8a3.tar.bz2
JSTD adapter for running e2e tests
Couple of changes into angular.scenario runner: - add autotest config (runs tests when document ready) - update ObjectModel (forwards events) - use only one ObjectModel instance for all outputters - expose error msg and line number in ObjectModel.Spec and ObjectModel.Step - fix generating spec.ids - fix 'html' output so that it does not mutate ObjectModel Couple of changes into docs / generator: - rename copy -> copyTpl - move docs/static into docs/examples (to avoid conflict with jstd proxy) Running all docs e2e tests: ======================================================== 1/ compile angular-scenario, jstd-scenario-adapter >> rake compile 2/ build docs >> rake docs 3/ start jstd server >> ./server-scenario.sh 4/ capture some browser 5/ run node server to serve static content >> node ../lib/nodeserver/server.js 6/ run tests >> ./test-scenario.sh
Diffstat (limited to 'src/scenario')
-rw-r--r--src/scenario/Describe.js4
-rw-r--r--src/scenario/ObjectModel.js132
-rw-r--r--src/scenario/Runner.js5
-rw-r--r--src/scenario/Scenario.js23
-rw-r--r--src/scenario/angular-bootstrap.js8
-rw-r--r--src/scenario/angular.suffix9
-rw-r--r--src/scenario/output/Html.js48
-rw-r--r--src/scenario/output/Json.js6
-rw-r--r--src/scenario/output/Object.js4
-rw-r--r--src/scenario/output/Xml.js5
10 files changed, 175 insertions, 69 deletions
diff --git a/src/scenario/Describe.js b/src/scenario/Describe.js
index 50cfded6..c6484f2f 100644
--- a/src/scenario/Describe.js
+++ b/src/scenario/Describe.js
@@ -37,6 +37,9 @@ angular.scenario.Describe = function(descName, parent) {
// Shared Unique ID generator for every describe block
angular.scenario.Describe.id = 0;
+// Shared Unique ID generator for every it (spec)
+angular.scenario.Describe.specId = 0;
+
/**
* Defines a block to execute before each it or nested describe.
*
@@ -93,6 +96,7 @@ angular.scenario.Describe.prototype.xdescribe = angular.noop;
*/
angular.scenario.Describe.prototype.it = function(name, body) {
this.its.push({
+ id: angular.scenario.Describe.specId++,
definition: this,
only: this.only,
name: name,
diff --git a/src/scenario/ObjectModel.js b/src/scenario/ObjectModel.js
index 263aa5f9..bff14461 100644
--- a/src/scenario/ObjectModel.js
+++ b/src/scenario/ObjectModel.js
@@ -4,21 +4,26 @@
* @param {Object} runner The scenario Runner instance to connect to.
*
* TODO(esprehn): Every output type creates one of these, but we probably
- * want one glonal shared instance. Need to handle events better too
+ * want one global shared instance. Need to handle events better too
* so the HTML output doesn't need to do spec model.getSpec(spec.id)
* silliness.
+ *
+ * TODO(vojta) refactor on, emit methods (from all objects) - use inheritance
*/
angular.scenario.ObjectModel = function(runner) {
var self = this;
this.specMap = {};
+ this.listeners = [];
this.value = {
name: '',
children: {}
};
runner.on('SpecBegin', function(spec) {
- var block = self.value;
+ var block = self.value,
+ definitions = [];
+
angular.forEach(self.getDefinitionPath(spec), function(def) {
if (!block.children[def.name]) {
block.children[def.name] = {
@@ -29,49 +34,78 @@ angular.scenario.ObjectModel = function(runner) {
};
}
block = block.children[def.name];
+ definitions.push(def.name);
});
- self.specMap[spec.id] = block.specs[spec.name] =
- new angular.scenario.ObjectModel.Spec(spec.id, spec.name);
+
+ var it = self.specMap[spec.id] =
+ block.specs[spec.name] =
+ new angular.scenario.ObjectModel.Spec(spec.id, spec.name, definitions);
+
+ // forward the event
+ self.emit('SpecBegin', it);
});
runner.on('SpecError', function(spec, error) {
var it = self.getSpec(spec.id);
it.status = 'error';
it.error = error;
+
+ // forward the event
+ self.emit('SpecError', it, error);
});
runner.on('SpecEnd', function(spec) {
var it = self.getSpec(spec.id);
complete(it);
+
+ // forward the event
+ self.emit('SpecEnd', it);
});
runner.on('StepBegin', function(spec, step) {
var it = self.getSpec(spec.id);
- it.steps.push(new angular.scenario.ObjectModel.Step(step.name));
+ var step = new angular.scenario.ObjectModel.Step(step.name);
+ it.steps.push(step);
+
+ // forward the event
+ self.emit('StepBegin', it, step);
});
runner.on('StepEnd', function(spec, step) {
var it = self.getSpec(spec.id);
- if (it.getLastStep().name !== step.name)
- throw 'Events fired in the wrong order. Step names don\' match.';
- complete(it.getLastStep());
+ var step = it.getLastStep();
+ if (step.name !== step.name)
+ throw 'Events fired in the wrong order. Step names don\'t match.';
+ complete(step);
+
+ // forward the event
+ self.emit('StepEnd', it, step);
});
runner.on('StepFailure', function(spec, step, error) {
- var it = self.getSpec(spec.id);
- var item = it.getLastStep();
- item.error = error;
- if (!it.status) {
- it.status = item.status = 'failure';
- }
+ var it = self.getSpec(spec.id),
+ modelStep = it.getLastStep();
+
+ modelStep.setErrorStatus('failure', error, step.line());
+ it.setStatusFromStep(modelStep);
+
+ // forward the event
+ self.emit('StepFailure', it, modelStep, error);
});
runner.on('StepError', function(spec, step, error) {
- var it = self.getSpec(spec.id);
- var item = it.getLastStep();
- it.status = 'error';
- item.status = 'error';
- item.error = error;
+ var it = self.getSpec(spec.id),
+ modelStep = it.getLastStep();
+
+ modelStep.setErrorStatus('error', error, step.line());
+ it.setStatusFromStep(modelStep);
+
+ // forward the event
+ self.emit('StepError', it, modelStep, error);
+ });
+
+ runner.on('RunnerEnd', function() {
+ self.emit('RunnerEnd');
});
function complete(item) {
@@ -82,6 +116,36 @@ angular.scenario.ObjectModel = function(runner) {
};
/**
+ * Adds a listener for an event.
+ *
+ * @param {string} eventName Name of the event to add a handler for
+ * @param {Function} listener Function that will be called when event is fired
+ */
+angular.scenario.ObjectModel.prototype.on = function(eventName, listener) {
+ eventName = eventName.toLowerCase();
+ this.listeners[eventName] = this.listeners[eventName] || [];
+ this.listeners[eventName].push(listener);
+};
+
+/**
+ * Emits an event which notifies listeners and passes extra
+ * arguments.
+ *
+ * @param {string} eventName Name of the event to fire.
+ */
+angular.scenario.ObjectModel.prototype.emit = function(eventName) {
+ var self = this,
+ args = Array.prototype.slice.call(arguments, 1),
+ eventName = eventName.toLowerCase();
+
+ if (this.listeners[eventName]) {
+ angular.forEach(this.listeners[eventName], function(listener) {
+ listener.apply(self, args);
+ });
+ }
+};
+
+/**
* Computes the path of definition describe blocks that wrap around
* this spec.
*
@@ -113,12 +177,14 @@ angular.scenario.ObjectModel.prototype.getSpec = function(id) {
*
* @param {string} id Id of the spec
* @param {string} name Name of the spec
+ * @param {Array<string>=} definitionNames List of all describe block names that wrap this spec
*/
-angular.scenario.ObjectModel.Spec = function(id, name) {
+angular.scenario.ObjectModel.Spec = function(id, name, definitionNames) {
this.id = id;
this.name = name;
this.startTime = new Date().getTime();
this.steps = [];
+ this.fullDefinitionName = (definitionNames || []).join(' ');
};
/**
@@ -143,6 +209,19 @@ angular.scenario.ObjectModel.Spec.prototype.getLastStep = function() {
};
/**
+ * Set status of the Spec from given Step
+ *
+ * @param {angular.scenario.ObjectModel.Step} step
+ */
+angular.scenario.ObjectModel.Spec.prototype.setStatusFromStep = function(step) {
+ if (!this.status || step.status == 'error') {
+ this.status = step.status;
+ this.error = step.error;
+ this.line = step.line;
+ }
+};
+
+/**
* A single step inside a Spec.
*
* @param {string} step Name of the step
@@ -151,3 +230,16 @@ angular.scenario.ObjectModel.Step = function(name) {
this.name = name;
this.startTime = new Date().getTime();
};
+
+/**
+ * Helper method for setting all error status related properties
+ *
+ * @param {string} status
+ * @param {string} error
+ * @param {string} line
+ */
+angular.scenario.ObjectModel.Step.prototype.setErrorStatus = function(status, error, line) {
+ this.status = status;
+ this.error = error;
+ this.line = line;
+};
diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js
index 583c8beb..51a81d6a 100644
--- a/src/scenario/Runner.js
+++ b/src/scenario/Runner.js
@@ -1,5 +1,8 @@
/**
- * Runner for scenarios.
+ * Runner for scenarios
+ *
+ * Has to be initialized before any test is loaded,
+ * because it publishes the API into window (global space).
*/
angular.scenario.Runner = function($window) {
this.listeners = [];
diff --git a/src/scenario/Scenario.js b/src/scenario/Scenario.js
index 87dad5b5..6879d787 100644
--- a/src/scenario/Scenario.js
+++ b/src/scenario/Scenario.js
@@ -87,17 +87,20 @@ angular.scenario.matcher = angular.scenario.matcher || function(name, fn) {
};
/**
- * Initialization function for the scenario runner.
+ * Initialize the scenario runner and run !
*
- * @param {angular.scenario.Runner} $scenario The runner to setup
- * @param {Object} config Config options
+ * Access global window and document object
+ * Access $runner through closure
+ *
+ * @param {Object=} config Config options
*/
-function angularScenarioInit($scenario, config) {
+angular.scenario.setUpAndRun = function (config) {
var href = window.location.href;
var body = _jQuery(document.body);
var output = [];
+ var objModel = new angular.scenario.ObjectModel($runner);
- if (config.scenario_output) {
+ if (config && config.scenario_output) {
output = config.scenario_output.split(',');
}
@@ -105,7 +108,7 @@ function angularScenarioInit($scenario, config) {
if (!output.length || indexOf(output,name) != -1) {
var context = body.append('<div></div>').find('div:last');
context.attr('id', name);
- fn.call({}, context, $scenario);
+ fn.call({}, context, $runner, objModel);
}
});
@@ -121,12 +124,12 @@ function angularScenarioInit($scenario, config) {
var appFrame = body.append('<div id="application"></div>').find('#application');
var application = new angular.scenario.Application(appFrame);
- $scenario.on('RunnerEnd', function() {
+ $runner.on('RunnerEnd', function() {
appFrame.css('display', 'none');
appFrame.find('iframe').attr('src', 'about:blank');
});
- $scenario.on('RunnerError', function(error) {
+ $runner.on('RunnerError', function(error) {
if (window.console) {
console.log(formatException(error));
} else {
@@ -135,8 +138,8 @@ function angularScenarioInit($scenario, config) {
}
});
- $scenario.run(application);
-}
+ $runner.run(application);
+};
/**
* Iterates through list with iterator function that must call the
diff --git a/src/scenario/angular-bootstrap.js b/src/scenario/angular-bootstrap.js
index 68dc393e..264ce718 100644
--- a/src/scenario/angular-bootstrap.js
+++ b/src/scenario/angular-bootstrap.js
@@ -23,7 +23,8 @@
try {
if (previousOnLoad) previousOnLoad();
} catch(e) {}
- angularScenarioInit($scenario, angularJsConfig(document));
+ var config = angularJsConfig(document);
+ if (config.autotest) angular.scenario.setUpAndRun(config);
};
addCSS("../../css/angular-scenario.css");
@@ -52,8 +53,7 @@
// Create the runner (which also sets up the global API)
document.write(
'<script type="text/javascript">' +
- 'var $scenario = new angular.scenario.Runner(window, angular.scenario.SpecRunner);' +
- '</script>'
- );
+ ' var $runner = new angular.scenario.Runner(window);' +
+ '</script>');
})(window.onload);
diff --git a/src/scenario/angular.suffix b/src/scenario/angular.suffix
index 3ab796d2..014c2cc9 100644
--- a/src/scenario/angular.suffix
+++ b/src/scenario/angular.suffix
@@ -1,7 +1,10 @@
- var $scenario = new angular.scenario.Runner(window);
+var $runner = new angular.scenario.Runner(window),
+ config = angularJsConfig(document);
+if (config.autotest) {
jqLiteWrap(document).ready(function() {
- angularScenarioInit($scenario, angularJsConfig(document));
+ angular.scenario.setUpAndRun(config);
});
-
+}
})(window, document);
+
diff --git a/src/scenario/output/Html.js b/src/scenario/output/Html.js
index 6e2e20f3..ccf7db6f 100644
--- a/src/scenario/output/Html.js
+++ b/src/scenario/output/Html.js
@@ -4,8 +4,9 @@
* TODO(esprehn): This should be refactored now that ObjectModel exists
* to use angular bindings for the UI.
*/
-angular.scenario.output('html', function(context, runner) {
- var model = new angular.scenario.ObjectModel(runner);
+angular.scenario.output('html', function(context, runner, model) {
+ var specUiMap = {},
+ lastStepUiMap = {};
context.append(
'<div id="header">' +
@@ -22,7 +23,7 @@ angular.scenario.output('html', function(context, runner) {
);
runner.on('InteractiveWait', function(spec, step) {
- var ui = model.getSpec(spec.id).getLastStep().ui;
+ var ui = lastStepUiMap[spec.id];
ui.find('.test-title').
html('waiting for you to <a href="javascript:resume()">resume</a>.');
});
@@ -58,59 +59,62 @@ angular.scenario.output('html', function(context, runner) {
name.removeClass('closed').addClass('open');
}
});
- model.getSpec(spec.id).ui = ui;
+
+ specUiMap[spec.id] = ui;
});
runner.on('SpecError', function(spec, error) {
- var ui = model.getSpec(spec.id).ui;
+ var ui = specUiMap[spec.id];
ui.append('<pre></pre>');
ui.find('> pre').text(formatException(error));
});
runner.on('SpecEnd', function(spec) {
+ var ui = specUiMap[spec.id];
spec = model.getSpec(spec.id);
- spec.ui.removeClass('status-pending');
- spec.ui.addClass('status-' + spec.status);
- spec.ui.find("> .test-info .timer-result").text(spec.duration + "ms");
+ ui.removeClass('status-pending');
+ ui.addClass('status-' + spec.status);
+ ui.find("> .test-info .timer-result").text(spec.duration + "ms");
if (spec.status === 'success') {
- spec.ui.find('> .test-info .test-name').addClass('closed');
- spec.ui.find('> .scrollpane .test-actions').hide();
+ ui.find('> .test-info .test-name').addClass('closed');
+ ui.find('> .scrollpane .test-actions').hide();
}
updateTotals(spec.status);
});
runner.on('StepBegin', function(spec, step) {
+ var ui = specUiMap[spec.id];
spec = model.getSpec(spec.id);
step = spec.getLastStep();
- spec.ui.find('> .scrollpane .test-actions').
- append('<li class="status-pending"></li>');
- step.ui = spec.ui.find('> .scrollpane .test-actions li:last');
- step.ui.append(
+ ui.find('> .scrollpane .test-actions').append('<li class="status-pending"></li>');
+ var stepUi = lastStepUiMap[spec.id] = ui.find('> .scrollpane .test-actions li:last');
+ stepUi.append(
'<div class="timer-result"></div>' +
'<div class="test-title"></div>'
);
- step.ui.find('> .test-title').text(step.name);
- var scrollpane = step.ui.parents('.scrollpane');
+ stepUi.find('> .test-title').text(step.name);
+ var scrollpane = stepUi.parents('.scrollpane');
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
});
runner.on('StepFailure', function(spec, step, error) {
- var ui = model.getSpec(spec.id).getLastStep().ui;
+ var ui = lastStepUiMap[spec.id];
addError(ui, step.line, error);
});
runner.on('StepError', function(spec, step, error) {
- var ui = model.getSpec(spec.id).getLastStep().ui;
+ var ui = lastStepUiMap[spec.id];
addError(ui, step.line, error);
});
runner.on('StepEnd', function(spec, step) {
+ var stepUi = lastStepUiMap[spec.id];
spec = model.getSpec(spec.id);
step = spec.getLastStep();
- step.ui.find('.timer-result').text(step.duration + 'ms');
- step.ui.removeClass('status-pending');
- step.ui.addClass('status-' + step.status);
- var scrollpane = spec.ui.find('> .scrollpane');
+ stepUi.find('.timer-result').text(step.duration + 'ms');
+ stepUi.removeClass('status-pending');
+ stepUi.addClass('status-' + step.status);
+ var scrollpane = specUiMap[spec.id].find('> .scrollpane');
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
});
diff --git a/src/scenario/output/Json.js b/src/scenario/output/Json.js
index 2c852496..1e3bef6b 100644
--- a/src/scenario/output/Json.js
+++ b/src/scenario/output/Json.js
@@ -1,10 +1,8 @@
/**
* Generates JSON output into a context.
*/
-angular.scenario.output('json', function(context, runner) {
- var model = new angular.scenario.ObjectModel(runner);
-
- runner.on('RunnerEnd', function() {
+angular.scenario.output('json', function(context, runner, model) {
+ model.on('RunnerEnd', function() {
context.text(angular.toJson(model.value));
});
});
diff --git a/src/scenario/output/Object.js b/src/scenario/output/Object.js
index 3257cfd7..1f29af9c 100644
--- a/src/scenario/output/Object.js
+++ b/src/scenario/output/Object.js
@@ -1,6 +1,6 @@
/**
* Creates a global value $result with the result of the runner.
*/
-angular.scenario.output('object', function(context, runner) {
- runner.$window.$result = new angular.scenario.ObjectModel(runner).value;
+angular.scenario.output('object', function(context, runner, model) {
+ runner.$window.$result = model.value;
});
diff --git a/src/scenario/output/Xml.js b/src/scenario/output/Xml.js
index e8c7f0e3..1a4c362c 100644
--- a/src/scenario/output/Xml.js
+++ b/src/scenario/output/Xml.js
@@ -1,10 +1,9 @@
/**
* Generates XML output into a context.
*/
-angular.scenario.output('xml', function(context, runner) {
- var model = new angular.scenario.ObjectModel(runner);
+angular.scenario.output('xml', function(context, runner, model) {
var $ = function(args) {return new context.init(args);};
- runner.on('RunnerEnd', function() {
+ model.on('RunnerEnd', function() {
var scenario = $('<scenario></scenario>');
context.append(scenario);
serializeXml(scenario, model.value);