aboutsummaryrefslogtreecommitdiffstats
path: root/test/scenario
diff options
context:
space:
mode:
authorElliott Sprehn2010-10-08 16:43:40 -0700
committerElliott Sprehn2010-10-14 09:47:39 -0700
commit03df6cbddbb80186caf571e29957370b2ef9881c (patch)
treed5a321c8b207b464a5c8a300c422186e20e8ae31 /test/scenario
parent0f104317dff5628765e26cc68df7dd1175b2aa5e (diff)
downloadangular.js-03df6cbddbb80186caf571e29957370b2ef9881c.tar.bz2
New Angular Scenario runner and DSL system with redesigned HTML UI.
Uses the Jasmine syntax for tests, ex: describe('widgets', function() { it('should verify that basic widgets work', function(){ navigateTo('widgets.html'); input('text.basic').enter('Carlos'); expect(binding('text.basic')).toEqual('Carlos'); input('text.basic').enter('Carlos Santana'); expect(binding('text.basic')).not().toEqual('Carlos Boozer'); input('text.password').enter('secret'); expect(binding('text.password')).toEqual('secret'); expect(binding('text.hidden')).toEqual('hiddenValue'); expect(binding('gender')).toEqual('male'); input('gender').select('female'); expect(binding('gender')).toEqual('female'); }); }); Note: To create new UI's implement the interface shown in angular.scenario.ui.Html.
Diffstat (limited to 'test/scenario')
-rw-r--r--test/scenario/ApplicationSpec.js75
-rw-r--r--test/scenario/DSLSpec.js369
-rw-r--r--test/scenario/DescribeSpec.js85
-rw-r--r--test/scenario/FutureSpec.js38
-rw-r--r--test/scenario/HtmlUISpec.js87
-rw-r--r--test/scenario/MatcherSpec.js38
-rw-r--r--test/scenario/RunnerSpec.js302
-rw-r--r--test/scenario/SpecRunnerSpec.js165
-rw-r--r--test/scenario/TestContext.js15
-rw-r--r--test/scenario/matchersSpec.js43
10 files changed, 783 insertions, 434 deletions
diff --git a/test/scenario/ApplicationSpec.js b/test/scenario/ApplicationSpec.js
new file mode 100644
index 00000000..706fbc36
--- /dev/null
+++ b/test/scenario/ApplicationSpec.js
@@ -0,0 +1,75 @@
+describe('angular.scenario.Application', function() {
+ var app, frames;
+
+ beforeEach(function() {
+ frames = _jQuery("<div></div>");
+ app = new angular.scenario.Application(frames);
+ });
+
+ it('should return new $window and $document after navigate', function() {
+ var testWindow, testDocument, counter = 0;
+ app.navigateTo = noop;
+ app.getWindow = function() {
+ return {x:counter++, document:{x:counter++}};
+ };
+ app.navigateTo('http://www.google.com/');
+ app.executeAction(function($document, $window) {
+ testWindow = $window;
+ testDocument = $document;
+ });
+ app.navigateTo('http://www.google.com/');
+ app.executeAction(function($document, $window) {
+ expect($window).not.toEqual(testWindow);
+ expect($document).not.toEqual(testDocument);
+ });
+ });
+
+ it('should execute callback on $window of frame', function() {
+ var testWindow = {document: {}};
+ app.getWindow = function() {
+ return testWindow;
+ };
+ app.executeAction(function($document, $window) {
+ expect(this).toEqual($window);
+ expect(this).toEqual(testWindow);
+ });
+ });
+
+ it('should create a new iframe each time', function() {
+ app.navigateTo('about:blank');
+ var frame = app.getFrame();
+ frame.attr('test', true);
+ app.navigateTo('about:blank');
+ expect(app.getFrame().attr('test')).toBeFalsy();
+ });
+
+ it('should URL description bar', function() {
+ app.navigateTo('about:blank');
+ var anchor = frames.find('> h2 a');
+ expect(anchor.attr('href')).toEqual('about:blank');
+ expect(anchor.text()).toEqual('about:blank');
+ });
+
+ it('should call onload handler when frame loads', function() {
+ var called;
+ app.getFrame = function() {
+ // Mock a little jQuery
+ var result = {
+ remove: function() {
+ return result;
+ },
+ attr: function(key, value) {
+ return (!value) ? 'attribute value' : result;
+ },
+ load: function() {
+ called = true;
+ }
+ };
+ return result;
+ };
+ app.navigateTo('about:blank', function() {
+ called = true;
+ });
+ expect(called).toBeTruthy();
+ });
+});
diff --git a/test/scenario/DSLSpec.js b/test/scenario/DSLSpec.js
index 7a8e2e3b..9b011847 100644
--- a/test/scenario/DSLSpec.js
+++ b/test/scenario/DSLSpec.js
@@ -1,181 +1,232 @@
-describe("DSL", function() {
+/**
+ * Very basic Mock of angular.
+ */
+function AngularMock() {
+ this.reset();
+ this.service = this;
+}
- var lastDocument, executeFuture, Expect;
+AngularMock.prototype.reset = function() {
+ this.log = [];
+};
- beforeEach(function() {
- setUpContext();
- executeFuture = function(future, html, callback) {
- lastDocument = _jQuery('<div>' + html + '</div>');
- lastFrame = _jQuery('<iframe>' + lastDocument + '</iframe>');
- _jQuery(document.body).append(lastDocument);
- var specThis = {
- testWindow: window,
- testDocument: lastDocument,
- testFrame: lastFrame,
- jQuery: _jQuery
- };
- future.behavior.call(specThis, callback || noop);
- };
- Expect = _window.expect;
- });
-
- describe("input", function() {
+AngularMock.prototype.element = function(node) {
+ this.log.push('element(' + node.nodeName.toLowerCase() + ')');
+ return this;
+};
- var input = angular.scenario.dsl.input;
+AngularMock.prototype.trigger = function(value) {
+ this.log.push('element().trigger(' + value + ')');
+};
- it('should enter', function() {
- var future = input('name').enter('John');
- expect(future.name).toEqual("input 'name' enter 'John'");
- executeFuture(future, '<input type="text" name="name" />');
- expect(lastDocument.find('input').val()).toEqual('John');
- });
+AngularMock.prototype.$browser = function() {
+ this.log.push('$brower()');
+ return this;
+};
- it('should select', function() {
- var future = input('gender').select('female');
- expect(future.name).toEqual("input 'gender' select 'female'");
- executeFuture(future,
- '<input type="radio" name="0@gender" value="male" checked/>' +
- '<input type="radio" name="0@gender" value="female"/>');
- expect(lastDocument.find(':radio:checked').length).toEqual(1);
- expect(lastDocument.find(':radio:checked').val()).toEqual('female');
- });
- });
+AngularMock.prototype.poll = function() {
+ this.log.push('$brower.poll()');
+ return this;
+};
- describe('browser', function() {
- var browser = angular.scenario.dsl.browser;
- it('shoud return true if location with empty hash provided is same ' +
- 'as location of the page', function() {
- browser.location.href = "http://server";
- expect(browser.location.toEqual("http://server")).toEqual(true);
- });
- it('shoud return true if location with hash provided is same ' +
- 'as location of the page', function() {
- browser.location.href = "http://server";
- browser.location.hash = "hashPath";
- expect(browser.location.toEqual("http://server/#/hashPath")).toEqual(true);
- });
- it('should return true if the location provided is the same as which ' +
- 'browser navigated to', function() {
- var future = browser.navigateTo("http://server/#/hashPath");
- expect(future.name).toEqual("Navigate to: http://server/#/hashPath");
- executeFuture(future, '<input type="text" name="name" />');
- expect(browser.location.toEqual("http://server/#/hashPath")).toEqual(true);
- expect(browser.location.toEqual("http://server/")).toEqual(false);
+AngularMock.prototype.notifyWhenNoOutstandingRequests = function(fn) {
+ this.log.push('$brower.notifyWhenNoOutstandingRequests()');
+ fn();
+};
- future = browser.navigateTo("http://server/");
- expect(future.name).toEqual("Navigate to: http://server/");
- executeFuture(future, '<input type="text" name="name" />');
- expect(browser.location.toEqual("http://server/")).toEqual(true);
- });
+describe("angular.scenario.dsl", function() {
+ var $window;
+ var $root;
+ var application;
+
+ beforeEach(function() {
+ $window = {
+ document: _jQuery("<div></div>"),
+ angular: new AngularMock()
+ };
+ $root = angular.scope({}, angular.service);
+ $root.futures = [];
+ $root.addFuture = function(name, fn) {
+ this.futures.push(name);
+ fn.call(this, function(error, result) {
+ $root.futureError = error;
+ $root.futureResult = result;
+ });
+ };
+ $root.application = new angular.scenario.Application($window.document);
+ $root.application.getWindow = function() {
+ return $window;
+ };
+ $root.application.navigateTo = function(url, callback) {
+ $window.location = url;
+ callback();
+ };
+ // Just use the real one since it delegates to this.addFuture
+ $root.addFutureAction = angular.scenario.
+ SpecRunner.prototype.addFutureAction;
});
-
- describe('repeater', function() {
-
- var repeater = angular.scenario.dsl.repeater;
- var html;
+
+ describe('Pause', function() {
beforeEach(function() {
- html = "<table>" +
- "<tr class='epic'>" +
- "<td class='hero-name'>" +
- "<span ng:bind='hero'>John Marston</span>" +
- "</td>" +
- "<td class='game-name'>" +
- "<span ng:bind='game'>Red Dead Redemption</span>" +
- "</td>" +
- "</tr>" +
- "<tr class='epic'>" +
- "<td class='hero-name'>" +
- "<span ng:bind='hero'>Nathan Drake</span>" +
- "</td>" +
- "<td class='game-name'>" +
- "<span ng:bind='game'>Uncharted</span>" +
- "</td>" +
- "</tr>" +
- "</table>";
+ $root.setTimeout = function(fn, value) {
+ $root.timerValue = value;
+ fn();
+ };
});
- it('should count', function() {
- var future = repeater('.repeater-row').count();
- expect(future.name).toEqual("repeater '.repeater-row' count");
- executeFuture(future,
- "<div class='repeater-row'>a</div>" +
- "<div class='repeater-row'>b</div>",
- function(value) {
- future.fulfill(value);
- });
- expect(future.fulfilled).toBeTruthy();
- expect(future.value).toEqual(2);
+
+ it('should pause for specified seconds', function() {
+ angular.scenario.dsl.pause.call($root).call($root, 10);
+ expect($root.timerValue).toEqual(10000);
+ expect($root.futureResult).toEqual(10000);
+ });
+ });
+
+ describe('Expect', function() {
+ it('should chain and execute matcher', function() {
+ var future = {value: 10};
+ var result = angular.scenario.dsl.expect.call($root).call($root, future);
+ result.toEqual(10);
+ expect($root.futureError).toBeUndefined();
+ expect($root.futureResult).toBeUndefined();
+ var result = angular.scenario.dsl.expect.call($root).call($root, future);
+ result.toEqual(20);
+ expect($root.futureError).toBeDefined();
});
-
- function assertFutureState(future, expectedName, expectedValue) {
- expect(future.name).toEqual(expectedName);
- executeFuture(future, html, function(value) {
- future.fulfill(value);
- });
- expect(future.fulfilled).toBeTruthy();
- expect(future.value).toEqual(expectedValue);
- }
- it('should collect bindings', function() {
- assertFutureState(repeater('.epic').collect('{{hero}}'),
- "repeater '.epic' collect '{{hero}}'",
- ['John Marston', 'Nathan Drake']);
- assertFutureState(repeater('.epic').collect('{{game}}'),
- "repeater '.epic' collect '{{game}}'",
- ['Red Dead Redemption', 'Uncharted']);
+ });
+
+ describe('NavigateTo', function() {
+ it('should allow a string url', function() {
+ angular.scenario.dsl.navigateTo.call($root).call($root, 'http://myurl');
+ expect($window.location).toEqual('http://myurl');
+ expect($root.futureResult).toEqual('http://myurl');
+ });
+
+ it('should allow a future url', function() {
+ var future = {name: 'future name', value: 'http://myurl'};
+ angular.scenario.dsl.navigateTo.call($root).call($root, future);
+ expect($window.location).toEqual('http://myurl');
+ expect($root.futureResult).toEqual('http://myurl');
});
- it('should collect normal selectors', function() {
- assertFutureState(repeater('.epic').collect('.hero-name'),
- "repeater '.epic' collect '.hero-name'",
- ['John Marston', 'Nathan Drake']);
- assertFutureState(repeater('.epic').collect('.game-name'),
- "repeater '.epic' collect '.game-name'",
- ['Red Dead Redemption', 'Uncharted']);
+
+ it('should complete if angular is missing from app frame', function() {
+ delete $window.angular;
+ angular.scenario.dsl.navigateTo.call($root).call($root, 'http://myurl');
+ expect($window.location).toEqual('http://myurl');
+ expect($root.futureResult).toEqual('http://myurl');
});
- it('should collect normal attributes', function() {
- //TODO(shyamseshadri) : Left as an exercise to the user
+
+ it('should wait for angular notify when no requests pending', function() {
+ angular.scenario.dsl.navigateTo.call($root).call($root, 'url');
+ expect($window.angular.log).toContain('$brower.poll()');
+ expect($window.angular.log)
+ .toContain('$brower.notifyWhenNoOutstandingRequests()');
});
});
-
- describe('element', function() {
- var element = angular.scenario.dsl.element;
- var html;
+
+ describe('Element Finding', function() {
+ var doc;
+ //TODO(esprehn): Work around a bug in jQuery where attribute selectors
+ // only work if they are executed on a real document, not an element.
+ //
+ // ex. jQuery('#foo').find('[name="bar"]') // fails
+ // ex. jQuery('#foo [name="bar"]') // works, wtf?
+ //
beforeEach(function() {
- html = '<div class="container">' +
- '<div class="reports-detail">' +
- '<span class="desc">Description : ' +
- '<span ng:bind="report.description">Details...</span>' +
- '</span>' +
- '<span>Date created: ' +
- '<span ng:bind="report.creationDate">01/01/01</span>' +
- '</span>' +
- '</div>' +
- '</div>';
+ doc = _jQuery('<div id="angular-scenario-binding"></div>');
+ _jQuery(document.body).append(doc);
+ $window.document = window.document;
});
- function timeTravel(future) {
- executeFuture(future, html, function(value) { future.fulfill(value); });
- expect(future.fulfilled).toBeTruthy();
- }
- it('should find elements on the page and provide jquery api', function() {
- var future = element('.reports-detail').text();
- expect(future.name).toEqual("Element '.reports-detail'.text()");
- timeTravel(future);
- expect(future.value).
- toEqual('Description : Details...Date created: 01/01/01');
-// expect(future.value.find('.desc').text()).
-// toEqual('Description : Details...');
+
+ afterEach(function() {
+ _jQuery(document.body)
+ .find('#angular-scenario-binding')
+ .remove();
});
- it('should find elements with angular syntax', function() {
- var future = element('{{report.description}}').text();
- expect(future.name).toEqual("Element '{{report.description}}'.text()");
- timeTravel(future);
- expect(future.value).toEqual('Details...');
-// expect(future.value.attr('ng:bind')).toEqual('report.description');
+
+ describe('Binding', function() {
+ it('should select binding by name', function() {
+ doc.append('<span ng:bind="foo.bar">some value</span>');
+ angular.scenario.dsl.binding.call($root).call($root, 'foo.bar');
+ expect($root.futureResult).toEqual('some value');
+ });
+
+ it('should return error if no binding exists', function() {
+ angular.scenario.dsl.binding.call($root).call($root, 'foo.bar');
+ expect($root.futureError).toMatch(/does not exist/);
+ });
});
- it('should be able to click elements', function(){
- var future = element('.link-class').click();
- expect(future.name).toEqual("Element '.link-class'.click()");
- executeFuture(future, html, function(value) { future.fulfill(value); });
- expect(future.fulfilled).toBeTruthy();
- // TODO(rajat): look for some side effect from click happening?
+
+ describe('Input', function() {
+ it('should change value in text input', function() {
+ doc.append('<input name="test.input" value="something">');
+ var chain = angular.scenario.dsl.input
+ .call($root).call($root, 'test.input');
+ chain.enter('foo');
+ expect($window.angular.log).toContain('element(input)');
+ expect($window.angular.log).toContain('element().trigger(change)');
+ expect(_jQuery('input[name="test.input"]').val()).toEqual('foo');
+ });
+
+ it('should return error if no input exists', function() {
+ var chain = angular.scenario.dsl.input
+ .call($root).call($root, 'test.input');
+ chain.enter('foo');
+ expect($root.futureError).toMatch(/does not exist/);
+ });
+
+ it('should toggle checkbox state', function() {
+ doc.append('<input type="checkbox" name="test.input" checked>');
+ expect(_jQuery('input[name="test.input"]')
+ .attr('checked')).toBeTruthy();
+ var chain = angular.scenario.dsl.input
+ .call($root).call($root, 'test.input');
+ chain.check();
+ expect($window.angular.log).toContain('element(input)');
+ expect($window.angular.log).toContain('element().trigger(click)');
+ expect(_jQuery('input[name="test.input"]')
+ .attr('checked')).toBeFalsy();
+ $window.angular.reset();
+ chain.check();
+ expect($window.angular.log).toContain('element(input)');
+ expect($window.angular.log).toContain('element().trigger(click)');
+ expect(_jQuery('input[name="test.input"]')
+ .attr('checked')).toBeTruthy();
+ });
+
+ it('should return error if checkbox does not exist', function() {
+ var chain = angular.scenario.dsl.input
+ .call($root).call($root, 'test.input');
+ chain.check();
+ expect($root.futureError).toMatch(/does not exist/);
+ });
+
+ it('should select option from radio group', function() {
+ doc.append(
+ '<input type="radio" name="0@test.input" value="foo">' +
+ '<input type="radio" name="0@test.input" value="bar" checked>'
+ );
+ expect(_jQuery('input[name="0@test.input"][value="bar"]')
+ .attr('checked')).toBeTruthy();
+ expect(_jQuery('input[name="0@test.input"][value="foo"]')
+ .attr('checked')).toBeFalsy();
+ var chain = angular.scenario.dsl.input
+ .call($root).call($root, 'test.input');
+ chain.select('foo');
+ expect($window.angular.log).toContain('element(input)');
+ expect($window.angular.log).toContain('element().trigger(click)');
+ expect(_jQuery('input[name="0@test.input"][value="bar"]')
+ .attr('checked')).toBeFalsy();
+ expect(_jQuery('input[name="0@test.input"][value="foo"]')
+ .attr('checked')).toBeTruthy();
+ });
+
+ it('should return error if radio button does not exist', function() {
+ var chain = angular.scenario.dsl.input
+ .call($root).call($root, 'test.input');
+ chain.select('foo');
+ expect($root.futureError).toMatch(/does not exist/);
+ });
});
});
+
});
diff --git a/test/scenario/DescribeSpec.js b/test/scenario/DescribeSpec.js
new file mode 100644
index 00000000..05129cfe
--- /dev/null
+++ b/test/scenario/DescribeSpec.js
@@ -0,0 +1,85 @@
+describe('angular.scenario.Describe', function() {
+ var log;
+ var root;
+
+ beforeEach(function() {
+ root = new angular.scenario.Describe();
+
+ /**
+ * Simple callback logging system. Use to assert proper order of calls.
+ */
+ log = function(text) {
+ log.text = log.text + text;
+ };
+ log.fn = function(text) {
+ return function(done){
+ log(text);
+ (done || angular.noop)();
+ };
+ };
+ log.reset = function() {
+ log.text = '';
+ };
+ log.reset();
+ });
+
+ it('should handle basic nested case', function() {
+ root.describe('A', function(){
+ this.beforeEach(log.fn('{'));
+ this.afterEach(log.fn('}'));
+ this.it('1', log.fn('1'));
+ this.describe('B', function(){
+ this.beforeEach(log.fn('('));
+ this.afterEach(log.fn(')'));
+ this.it('2', log.fn('2'));
+ });
+ });
+ var specs = root.getSpecs();
+ expect(specs.length).toEqual(2);
+
+ expect(specs[0].name).toEqual('2');
+ specs[0].fn();
+ expect(log.text).toEqual('{(2)}');
+
+ log.reset();
+ expect(specs[1].name).toEqual('1');
+ specs[1].fn();
+ expect(log.text).toEqual('{1}');
+ });
+
+ it('should link nested describe blocks with parent and children', function() {
+ root.describe('A', function() {
+ this.it('1', angular.noop);
+ this.describe('B', function() {
+ this.it('2', angular.noop);
+ this.describe('C', function() {
+ this.it('3', angular.noop);
+ });
+ });
+ });
+ var specs = root.getSpecs();
+ expect(specs[2].definition.parent).toEqual(root);
+ expect(specs[0].definition.parent).toEqual(specs[2].definition.children[0]);
+ });
+
+ it('should not process xit and xdescribe', function() {
+ root.describe('A', function() {
+ this.xit('1', angular.noop);
+ this.xdescribe('B', function() {
+ this.it('2', angular.noop);
+ this.describe('C', function() {
+ this.it('3', angular.noop);
+ });
+ });
+ });
+ var specs = root.getSpecs();
+ expect(specs.length).toEqual(0);
+ });
+
+ it('should create uniqueIds in the tree', function() {
+ angular.scenario.Describe.id = 0;
+ var a = new angular.scenario.Describe();
+ var b = new angular.scenario.Describe();
+ expect(a.id).toNotEqual(b.id);
+ });
+});
diff --git a/test/scenario/FutureSpec.js b/test/scenario/FutureSpec.js
new file mode 100644
index 00000000..ae475779
--- /dev/null
+++ b/test/scenario/FutureSpec.js
@@ -0,0 +1,38 @@
+describe('angular.scenario.Future', function() {
+ var future;
+
+ it('should set the name and behavior', function() {
+ var behavior = function() {};
+ var future = new angular.scenario.Future('test name', behavior);
+ expect(future.name).toEqual('test name');
+ expect(future.behavior).toEqual(behavior);
+ expect(future.value).toBeUndefined();
+ expect(future.fulfilled).toBeFalsy();
+ });
+
+ it('should be fulfilled after execution and done callback', function() {
+ var future = new angular.scenario.Future('test name', function(done) {
+ done();
+ });
+ future.execute(angular.noop);
+ expect(future.fulfilled).toBeTruthy();
+ });
+
+ it('should take callback with (error, result) and forward', function() {
+ var future = new angular.scenario.Future('test name', function(done) {
+ done(10, 20);
+ });
+ future.execute(function(error, result) {
+ expect(error).toEqual(10);
+ expect(result).toEqual(20);
+ });
+ });
+
+ it('should use error as value if provided', function() {
+ var future = new angular.scenario.Future('test name', function(done) {
+ done(10, 20);
+ });
+ future.execute(angular.noop);
+ expect(future.value).toEqual(10);
+ });
+});
diff --git a/test/scenario/HtmlUISpec.js b/test/scenario/HtmlUISpec.js
new file mode 100644
index 00000000..b2e2652f
--- /dev/null
+++ b/test/scenario/HtmlUISpec.js
@@ -0,0 +1,87 @@
+describe('angular.scenario.HtmlUI', function() {
+ var ui;
+ var context;
+ var spec;
+
+ beforeEach(function() {
+ spec = {
+ name: 'test spec',
+ definition: {
+ id: 10,
+ name: 'child',
+ children: [],
+ parent: {
+ id: 20,
+ name: 'parent',
+ children: []
+ }
+ }
+ };
+ context = _jQuery("<div></div>");
+ ui = new angular.scenario.ui.Html(context);
+ });
+
+ it('should create nested describe context', function() {
+ ui.addSpec(spec);
+ expect(context.find('#describe-20 #describe-10 > h2').text())
+ .toEqual('describe: child');
+ expect(context.find('#describe-20 > h2').text()).toEqual('describe: parent');
+ expect(context.find('#describe-10 .tests > li .test-info .test-name').text())
+ .toEqual('it test spec');
+ expect(context.find('#describe-10 .tests > li').hasClass('status-pending'))
+ .toBeTruthy();
+ });
+
+ it('should update totals when steps complete', function() {
+ // Error
+ ui.addSpec(spec).error('error');
+ // Error
+ specUI = ui.addSpec(spec);
+ specUI.addStep('some step').finish();
+ specUI.finish('error');
+ // Failure
+ specUI = ui.addSpec(spec);
+ specUI.addStep('some step').finish('failure');
+ specUI.finish('failure');
+ // Failure
+ specUI = ui.addSpec(spec);
+ specUI.addStep('some step').finish('failure');
+ specUI.finish('failure');
+ // Failure
+ specUI = ui.addSpec(spec);
+ specUI.addStep('some step').finish('failure');
+ specUI.finish('failure');
+ // Success
+ specUI = ui.addSpec(spec);
+ specUI.addStep('some step').finish();
+ specUI.finish();
+
+ expect(parseInt(context.find('#status-legend .status-failure').text()))
+ .toEqual(3);
+ expect(parseInt(context.find('#status-legend .status-error').text()))
+ .toEqual(2);
+ expect(parseInt(context.find('#status-legend .status-success').text()))
+ .toEqual(1);
+ });
+
+ it('should update timer when test completes', function() {
+ // Success
+ specUI = ui.addSpec(spec);
+ specUI.addStep('some step').finish();
+ specUI.finish();
+
+ // Failure
+ specUI = ui.addSpec(spec);
+ specUI.addStep('some step').finish('failure');
+ specUI.finish('failure');
+
+ // Error
+ specUI = ui.addSpec(spec).error('error');
+
+ context.find('#describe-10 .tests > li .test-info .timer-result')
+ .each(function(index, timer) {
+ expect(timer.innerHTML).toMatch(/ms$/);
+ });
+ });
+
+});
diff --git a/test/scenario/MatcherSpec.js b/test/scenario/MatcherSpec.js
deleted file mode 100644
index 2eddd2bc..00000000
--- a/test/scenario/MatcherSpec.js
+++ /dev/null
@@ -1,38 +0,0 @@
-describe('Matcher', function () {
- function executeFutures() {
- for(var i in $scenario.currentSpec.futures) {
- var future = $scenario.currentSpec.futures[i];
- future.behavior.call({}, function(value) { future.fulfill(value); });
- }
- }
- var matcher;
- beforeEach(function() {
- setUpContext();
- var future = $scenario.addFuture('Calculate first future', function(done) {
- done(123);
- });
- matcher = new Matcher(this, future);
-
- });
- it('should correctly match toEqual', function() {
- matcher.toEqual(123);
- executeFutures();
- });
- it('should throw an error when incorrect match toEqual', function() {
- matcher.toEqual(456);
- try {
- executeFutures();
- fail();
- } catch (e) {
- expect(e).toEqual('Expected 456 but was 123');
- }
- });
- it('should correctly match arrays', function() {
- var future = $scenario.addFuture('Calculate first future', function(done) {
- done(['a', 'b']);
- });
- matcher = new Matcher(this, future);
- matcher.toEqual(['a', 'b']);
- executeFutures();
- });
-}); \ No newline at end of file
diff --git a/test/scenario/RunnerSpec.js b/test/scenario/RunnerSpec.js
index 2986add6..43d97257 100644
--- a/test/scenario/RunnerSpec.js
+++ b/test/scenario/RunnerSpec.js
@@ -1,238 +1,96 @@
-describe('Runner', function() {
-
- var Describe, It, BeforeEach, AfterEach, body;
-
+/**
+ * Mock spec runner.
+ */
+function MockSpecRunner() {}
+MockSpecRunner.prototype.run = function(ui, spec, specDone) {
+ spec.fn.call(this);
+ specDone();
+};
+
+describe('angular.scenario.Runner', function() {
+ var $window;
+ var runner;
+
beforeEach(function() {
- setUpContext();
- Describe = _window.describe;
- It = _window.it;
- BeforeEach = _window.beforeEach;
- AfterEach = _window.afterEach;
- body = _jQuery('<div></div>');
- });
-
- describe('describe', function() {
- it('should consume the describe functions', function() {
- Describe('describe name', logger('body'));
- expect(log).toEqual('body');
+ // Trick to get the scope out of a DSL statement
+ angular.scenario.dsl('dslScope', function() {
+ var scope = this;
+ return function() { return scope; };
});
-
- describe('it', function() {
- it('should consume it', function() {
- Describe('describe name', function() {
- It('should text', logger('body'));
- });
- expect(log).toEqual('body');
- var spec = $scenario.specs['describe name: it should text'];
- expect(spec.futures).toEqual([]);
- expect(spec.name).toEqual('describe name: it should text');
- });
-
- it('should complain on duplicate it', function() {
- // WRITE ME!!!!
- });
-
- it('should create a failing future if there is a javascript error', function() {
- var spec;
- Describe('D1', function() {
- It('I1', function() {
- spec = $scenario.currentSpec;
- throw {message: 'blah'};
- });
- });
- var future = spec.futures[0];
- expect(future.name).toEqual('blah');
- try {
- future.behavior();
- fail();
- } catch (e) {
- expect(e.message).toEqual('blah');
- }
- });
- });
-
- describe('beforeEach', function() {
- it('should execute beforeEach before every it', function() {
- Describe('describe name', function() {
- BeforeEach(logger('before;'));
- It('should text', logger('body;'));
- It('should text2', logger('body2;'));
- });
- expect(log).toEqual('before;body;before;body2;');
- });
+ // Trick to get the scope out of a DSL statement
+ angular.scenario.dsl('dslChain', function() {
+ return function() {
+ this.chained = 0;
+ this.chain = function() { this.chained++; return this; };
+ return this;
+ };
});
- describe('afterEach', function() {
- it('should execute afterEach after every it', function() {
- Describe('describe name', function() {
- AfterEach(logger('after;'));
- It('should text1', logger('body1;'));
- It('should text2', logger('body2;'));
- });
- expect(log).toEqual('body1;after;body2;after;');
- });
-
- it('should always execute afterEach after every it', function() {
- Describe('describe name', function() {
- AfterEach(logger('after;'));
- It('should text', function() {
- logger('body1;')();
- throw "MyError";
- });
- It('should text2', logger('body2;'));
- });
- expect(log).toEqual('body1;after;body2;after;');
- });
-
- it('should report an error if afterEach fails', function() {
- var next;
- Describe('describe name', function() {
- AfterEach(function() {
- $scenario.addFuture('afterEachLog', logger('after;'));
- $scenario.addFuture('afterEachThrow', function() {
- throw "AfterError";
- });
- });
- It('should text1', function() {
- $scenario.addFuture('future1', logger('future1;'));
- });
- It('should text2', function() {
- $scenario.addFuture('future2', logger('future2;'));
- });
- });
- $scenario.run(body);
- expect(log).toEqual('future1;after;future2;after;');
- expect(_window.$testrun.results).toEqual([
- { name : 'describe name: it should text1',
- passed : false,
- error : 'AfterError',
- steps : [ 'future1', 'afterEachLog', 'afterEachThrow' ] },
- { name : 'describe name: it should text2',
- passed : false,
- error : 'AfterError',
- steps : [ 'future2', 'afterEachLog', 'afterEachThrow' ] }]);
- });
+ $window = {};
+ runner = new angular.scenario.Runner($window);
+ });
+
+ afterEach(function() {
+ delete angular.scenario.dsl.dslScope;
+ delete angular.scenario.dsl.dslChain;
+ });
+
+ it('should publish the functions in the public API', function() {
+ angular.foreach(runner.api, function(fn, name) {
+ var func;
+ if (name in $window) {
+ func = $window[name];
+ }
+ expect(angular.isFunction(func)).toBeTruthy();
});
});
-
- describe('future building', function() {
- it('should queue futures', function() {
- function behavior(){}
- Describe('name', function() {
- It('should', function() {
- $scenario.addFuture('futureName', behavior);
+
+ it('should construct valid describe trees with public API', function() {
+ var before = [];
+ var after = [];
+ $window.describe('A', function() {
+ $window.beforeEach(function() { before.push('A'); });
+ $window.afterEach(function() { after.push('A'); });
+ $window.it('1', angular.noop);
+ $window.describe('B', function() {
+ $window.beforeEach(function() { before.push('B'); });
+ $window.afterEach(function() { after.push('B'); });
+ $window.it('2', angular.noop);
+ $window.describe('C', function() {
+ $window.beforeEach(function() { before.push('C'); });
+ $window.afterEach(function() { after.push('C'); });
+ $window.it('3', angular.noop);
});
});
- expect($scenario.specs['name: it should'].futures[0].name).
- toEqual('futureName');
});
+ var specs = runner.rootDescribe.getSpecs();
+ specs[0].fn();
+ expect(before).toEqual(['A', 'B', 'C']);
+ expect(after).toEqual(['C', 'B', 'A']);
+ expect(specs[2].definition.parent).toEqual(runner.rootDescribe);
+ expect(specs[0].definition.parent).toEqual(specs[2].definition.children[0]);
});
-
- describe('execution', function() {
- it('should execute the queued futures', function() {
- var next, firstThis, secondThis, doneThis, spec;
- $scenario.specs['spec'] = {
- futures: [
- new Future('future1', function(done) {
- next = done;
- log += 'first;';
- firstThis = this;
- }),
- new Future('future2', function(done) {
- next = done;
- log += 'second;';
- secondThis = this;
- })
- ]
- };
-
- spec = $scenario.execute('spec', function(done){
- log += 'done;';
- doneThis = this;
+
+ it('should publish the DSL statements to the $window', function() {
+ $window.describe('describe', function() {
+ $window.it('1', function() {
+ expect($window.dslScope).toBeDefined();
});
- expect(log).toEqual('first;');
- next();
- expect(log).toEqual('first;second;');
- next();
- expect(log).toEqual('first;second;done;');
- expect(spec === window).toEqual(false);
- expect(spec).toEqual(firstThis);
- expect(spec).toEqual(secondThis);
- expect(spec).toEqual(doneThis);
-
- expect(spec.result.failed).toEqual(false);
- expect(spec.result.finished).toEqual(true);
- expect(spec.result.error).toBeUndefined();
- expect(spec.result.passed).toEqual(true);
- });
-
- it('should handle exceptions in a future', function() {
- $scenario.specs['spec'] = {
- futures: [
- new Future('first future', function(done) {
- done();
- }),
- new Future('error', function(done) {
- throw "MyError";
- }),
- new Future('should not execute', function(done) {
- done();
- })
- ]
- };
-
- var spec = $scenario.execute('spec');
-
- expect(spec.result.passed).toEqual(false);
- expect(spec.result.failed).toEqual(true);
- expect(spec.result.finished).toEqual(true);
- expect(spec.result.error).toEqual("MyError");
- expect(_window.$testrun.results).toEqual([{
- name: 'spec',
- passed: false,
- error: 'MyError',
- steps: ['first future', 'error']}]);
});
+ runner.run(null/*ui*/, null/*application*/, MockSpecRunner, rethrow);
});
-
- describe('run', function() {
- var next;
- beforeEach(function() {
- Describe('d1', function() {
- It('it1', function() { $scenario.addFuture('s1', logger('s1,')); });
- It('it2', function() {
- $scenario.addFuture('s2', logger('s2,'));
- $scenario.addFuture('s2.2', function(done){ next = done; });
- });
+
+ it('should create a new scope for each DSL chain', function() {
+ $window.describe('describe', function() {
+ $window.it('1', function() {
+ var scope = $window.dslScope();
+ scope.test = "foo";
+ expect($window.dslScope().test).toBeUndefined();
});
- Describe('d2', function() {
- It('it3', function() { $scenario.addFuture('s3', logger('s3,')); });
- It('it4', function() { $scenario.addFuture('s4', logger('s4,')); });
+ $window.it('2', function() {
+ var scope = $window.dslChain().chain().chain();
+ expect(scope.chained).toEqual(2);
});
});
- it('should execute all specs', function() {
- $scenario.run(body);
-
- expect(log).toEqual('s1,s2,');
- next();
- expect(log).toEqual('s1,s2,s3,s4,');
- });
- it('should publish done state and results as tests are run', function() {
- expect(_window.$testrun.done).toBeFalsy();
- expect(_window.$testrun.results).toEqual([]);
- $scenario.run(body);
- expect(_window.$testrun.done).toBeFalsy();
- expect(_window.$testrun.results).toEqual([
- {name: 'd1: it it1', passed: true, error: undefined, steps: ['s1']}
- ]);
- next();
- expect(_window.$testrun.done).toBeTruthy();
- expect(_window.$testrun.results).toEqual([
- {name: 'd1: it it1', passed: true, error: undefined, steps: ['s1']},
- {name: 'd1: it it2', passed: true, error: undefined, steps: ['s2', 's2.2']},
- {name: 'd2: it it3', passed: true, error: undefined, steps: ['s3']},
- {name: 'd2: it it4', passed: true, error: undefined, steps: ['s4']}
- ]);
- });
+ runner.run(null/*ui*/, null/*application*/, MockSpecRunner, rethrow);
});
-
-}); \ No newline at end of file
+});
diff --git a/test/scenario/SpecRunnerSpec.js b/test/scenario/SpecRunnerSpec.js
new file mode 100644
index 00000000..81b66956
--- /dev/null
+++ b/test/scenario/SpecRunnerSpec.js
@@ -0,0 +1,165 @@
+/**
+ * Mock of all required UI classes/methods. (UI, Spec, Step).
+ */
+function UIMock() {
+ this.log = [];
+}
+UIMock.prototype = {
+ addSpec: function(spec) {
+ var log = this.log;
+ log.push('addSpec:' + spec.name);
+ return {
+ addStep: function(name) {
+ log.push('addStep:' + name);
+ return {
+ finish: function(e) {
+ log.push('step finish:' + (e ? e : ''));
+ return this;
+ },
+ error: function(e) {
+ log.push('step error:' + (e ? e : ''));
+ return this;
+ }
+ };
+ },
+ finish: function(e) {
+ log.push('spec finish:' + (e ? e : ''));
+ return this;
+ },
+ error: function(e) {
+ log.push('spec error:' + (e ? e : ''));
+ return this;
+ }
+ };
+ },
+};
+
+/**
+ * Mock Application
+ */
+function ApplicationMock($window) {
+ this.$window = $window;
+}
+ApplicationMock.prototype = {
+ executeAction: function(callback) {
+ callback.call(this.$window);
+ }
+};
+
+describe('angular.scenario.SpecRunner', function() {
+ var $window;
+ var runner;
+
+ beforeEach(function() {
+ $window = {};
+ runner = angular.scope();
+ runner.application = new ApplicationMock($window);
+ runner.$become(angular.scenario.SpecRunner);
+ });
+
+ it('should bind futures to the spec', function() {
+ runner.addFuture('test future', function(done) {
+ this.application.value = 10;
+ done();
+ });
+ runner.futures[0].execute(angular.noop);
+ expect(runner.application.value).toEqual(10);
+ });
+
+ it('should pass done to future action behavior', function() {
+ runner.addFutureAction('test future', function(done) {
+ expect(angular.isFunction(done)).toBeTruthy();
+ done(10, 20);
+ });
+ runner.futures[0].execute(function(error, result) {
+ expect(error).toEqual(10);
+ expect(result).toEqual(20);
+ });
+ });
+
+ it('should pass execute future action on the $window', function() {
+ runner.addFutureAction('test future', function(done) {
+ this.test = 'test value';
+ done();
+ });
+ runner.futures[0].execute(angular.noop);
+ expect($window.test).toEqual('test value');
+ });
+
+ it('should execute spec function and notify UI', function() {
+ var finished = false;
+ var ui = new UIMock();
+ var spec = {name: 'test spec', fn: function() {
+ this.test = 'some value';
+ }};
+ runner.addFuture('test future', function(done) {
+ done();
+ });
+ runner.run(ui, spec, function() {
+ finished = true;
+ });
+ expect(runner.test).toEqual('some value');
+ expect(finished).toBeTruthy();
+ expect(ui.log).toEqual([
+ 'addSpec:test spec',
+ 'addStep:test future',
+ 'step finish:',
+ 'spec finish:'
+ ]);
+ });
+
+ it('should execute notify UI on spec setup error', function() {
+ var finished = false;
+ var ui = new UIMock();
+ var spec = {name: 'test spec', fn: function() {
+ throw 'message';
+ }};
+ runner.run(ui, spec, function() {
+ finished = true;
+ });
+ expect(finished).toBeTruthy();
+ expect(ui.log).toEqual([
+ 'addSpec:test spec',
+ 'spec error:message'
+ ]);
+ });
+
+ it('should execute notify UI on step failure', function() {
+ var finished = false;
+ var ui = new UIMock();
+ var spec = {name: 'test spec', fn: angular.noop};
+ runner.addFuture('test future', function(done) {
+ done('failure message');
+ });
+ runner.run(ui, spec, function() {
+ finished = true;
+ });
+ expect(finished).toBeTruthy();
+ expect(ui.log).toEqual([
+ 'addSpec:test spec',
+ 'addStep:test future',
+ 'step finish:failure message',
+ 'spec finish:failure message'
+ ]);
+ });
+
+ it('should execute notify UI on step error', function() {
+ var finished = false;
+ var ui = new UIMock();
+ var spec = {name: 'test spec', fn: angular.noop};
+ runner.addFuture('test future', function(done) {
+ throw 'error message';
+ });
+ runner.run(ui, spec, function() {
+ finished = true;
+ });
+ expect(finished).toBeTruthy();
+ expect(ui.log).toEqual([
+ 'addSpec:test spec',
+ 'addStep:test future',
+ 'step error:error message',
+ 'spec finish:error message'
+ ]);
+ });
+
+});
diff --git a/test/scenario/TestContext.js b/test/scenario/TestContext.js
deleted file mode 100644
index 0c8e6143..00000000
--- a/test/scenario/TestContext.js
+++ /dev/null
@@ -1,15 +0,0 @@
-var _window, runner, log, $scenario;
-
-function logger(text) {
- return function(done){
- log += text;
- (done||noop)();
- };
-}
-
-function setUpContext() {
- _window = {};
- runner = new angular.scenario.Runner(_window, _jQuery);
- $scenario = _window.$scenario;
- log = '';
-}
diff --git a/test/scenario/matchersSpec.js b/test/scenario/matchersSpec.js
new file mode 100644
index 00000000..faabd1a2
--- /dev/null
+++ b/test/scenario/matchersSpec.js
@@ -0,0 +1,43 @@
+describe('angular.scenario.matchers', function () {
+ var matchers;
+
+ function expectMatcher(value, test) {
+ delete matchers.error;
+ delete matchers.future.value;
+ if (value !== undefined) {
+ matchers.future.value = value;
+ }
+ test();
+ expect(matchers.error).toBeUndefined();
+ }
+
+ beforeEach(function() {
+ /**
+ * Mock up the future system wrapped around matchers.
+ *
+ * @see Scenario.js#angular.scenario.matcher
+ */
+ matchers = {
+ future: { name: 'test' }
+ };
+ matchers.addFuture = function(name, callback) {
+ callback(function(error) {
+ matchers.error = error;
+ });
+ };
+ angular.extend(matchers, angular.scenario.matcher);
+ });
+
+ it('should handle basic matching', function() {
+ expectMatcher(10, function() { matchers.toEqual(10); });
+ expectMatcher('value', function() { matchers.toBeDefined(); });
+ expectMatcher([1], function() { matchers.toBeTruthy(); });
+ expectMatcher("", function() { matchers.toBeFalsy(); });
+ expectMatcher(0, function() { matchers.toBeFalsy(); });
+ expectMatcher('foo', function() { matchers.toMatch('.o.'); });
+ expectMatcher(null, function() { matchers.toBeNull(); });
+ expectMatcher([1, 2, 3], function() { matchers.toContain(2); });
+ expectMatcher(3, function() { matchers.toBeLessThan(10); });
+ expectMatcher(3, function() { matchers.toBeGreaterThan(-5); });
+ });
+});