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/scenario | |
| 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/scenario')
| -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 | 
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); }); +  }); +}); | 
