diff options
| author | Vojta Jina | 2011-05-19 17:33:25 +0200 |
|---|---|---|
| committer | Igor Minar | 2011-05-19 09:43:56 -0700 |
| commit | 1abdc097b235366759a889bdcc68359653a9b8a3 (patch) | |
| tree | ed53346c171de6c60748e65c7f7f065cc8837103 /src/scenario | |
| parent | 9f56af9c15e1096033c91c2619f7f7f0115d0032 (diff) | |
| download | angular.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.js | 4 | ||||
| -rw-r--r-- | src/scenario/ObjectModel.js | 132 | ||||
| -rw-r--r-- | src/scenario/Runner.js | 5 | ||||
| -rw-r--r-- | src/scenario/Scenario.js | 23 | ||||
| -rw-r--r-- | src/scenario/angular-bootstrap.js | 8 | ||||
| -rw-r--r-- | src/scenario/angular.suffix | 9 | ||||
| -rw-r--r-- | src/scenario/output/Html.js | 48 | ||||
| -rw-r--r-- | src/scenario/output/Json.js | 6 | ||||
| -rw-r--r-- | src/scenario/output/Object.js | 4 | ||||
| -rw-r--r-- | src/scenario/output/Xml.js | 5 |
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); |
