diff options
| author | Elliott Sprehn | 2010-10-08 16:43:40 -0700 |
|---|---|---|
| committer | Elliott Sprehn | 2010-10-14 09:47:39 -0700 |
| commit | 03df6cbddbb80186caf571e29957370b2ef9881c (patch) | |
| tree | d5a321c8b207b464a5c8a300c422186e20e8ae31 /test | |
| parent | 0f104317dff5628765e26cc68df7dd1175b2aa5e (diff) | |
| download | angular.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')
| -rw-r--r-- | test/AngularSpec.js | 4 | ||||
| -rw-r--r-- | test/scenario/ApplicationSpec.js | 75 | ||||
| -rw-r--r-- | test/scenario/DSLSpec.js | 369 | ||||
| -rw-r--r-- | test/scenario/DescribeSpec.js | 85 | ||||
| -rw-r--r-- | test/scenario/FutureSpec.js | 38 | ||||
| -rw-r--r-- | test/scenario/HtmlUISpec.js | 87 | ||||
| -rw-r--r-- | test/scenario/MatcherSpec.js | 38 | ||||
| -rw-r--r-- | test/scenario/RunnerSpec.js | 302 | ||||
| -rw-r--r-- | test/scenario/SpecRunnerSpec.js | 165 | ||||
| -rw-r--r-- | test/scenario/TestContext.js | 15 | ||||
| -rw-r--r-- | test/scenario/matchersSpec.js | 43 | ||||
| -rw-r--r-- | test/testabilityPatch.js | 19 |
12 files changed, 806 insertions, 434 deletions
diff --git a/test/AngularSpec.js b/test/AngularSpec.js index e0228e16..6faed707 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -86,6 +86,10 @@ describe('equals', function(){ expect(equals({name:'misko'}, {name:'misko', $id:2})).toEqual(true); expect(equals({name:'misko', $id:1}, {name:'misko'})).toEqual(true); }); + + it('should ignore functions', function(){ + expect(equals({func: function() {}}, {bar: function() {}})).toEqual(true); + }); }); describe('parseKeyValue', function() { 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); }); + }); +}); diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index 955dccfa..47bc0d0d 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -22,6 +22,19 @@ beforeEach(function(){ return "Expected to not have class 'ng-validation-error' but found."; }; return !hasClass; + }, + + toEqualData: function(expected) { + return equals(this.actual, expected); + }, + + toHaveClass: function(clazz) { + this.message = function(){ + return "Expected '" + sortedHtml(this.actual) + "' to have class '" + clazz + "'."; + }; + return this.actual.hasClass ? + this.actual.hasClass(clazz) : + jqLite(this.actual).hasClass(clazz); } }); }); @@ -194,3 +207,9 @@ function click(element) { JQLite.prototype.trigger.call(element, 'click'); } } + +function rethrow(e) { + if(e) { + throw e; + } +} |
