diff options
| -rw-r--r-- | scenario/widgets-scenario.js | 87 | ||||
| -rw-r--r-- | scenario/widgets.html | 9 | ||||
| -rw-r--r-- | src/jqLite.js | 16 | ||||
| -rw-r--r-- | src/scenario/Application.js | 2 | ||||
| -rw-r--r-- | src/scenario/DSL.js | 134 | ||||
| -rw-r--r-- | src/scenario/Future.js | 32 | ||||
| -rw-r--r-- | src/scenario/Runner.js | 34 | ||||
| -rw-r--r-- | src/scenario/SpecRunner.js | 56 | ||||
| -rw-r--r-- | src/scenario/dsl.js | 270 | ||||
| -rw-r--r-- | test/scenario/ApplicationSpec.js | 13 | ||||
| -rw-r--r-- | test/scenario/FutureSpec.js | 38 | ||||
| -rw-r--r-- | test/scenario/SpecRunnerSpec.js | 17 | ||||
| -rw-r--r-- | test/scenario/dslSpec.js (renamed from test/scenario/DSLSpec.js) | 196 | 
13 files changed, 648 insertions, 256 deletions
diff --git a/scenario/widgets-scenario.js b/scenario/widgets-scenario.js index 69fdc10e..befc481c 100644 --- a/scenario/widgets-scenario.js +++ b/scenario/widgets-scenario.js @@ -1,58 +1,55 @@  describe('widgets', function() {    it('should verify that basic widgets work', function(){      navigateTo('widgets.html'); -    input('text.basic').enter('Carlos'); +             +    using('#text-basic-box').input('text.basic').enter('Carlos');      expect(binding('text.basic')).toEqual('Carlos'); -    pause(2);      input('text.basic').enter('Carlos Santana'); -    pause(2);      expect(binding('text.basic')).not().toEqual('Carlos Boozer'); -    pause(2); +          input('text.password').enter('secret');      expect(binding('text.password')).toEqual('secret'); +          expect(binding('text.hidden')).toEqual('hiddenValue'); +          expect(binding('gender')).toEqual('male'); -    pause(2);      input('gender').select('female'); -    expect(binding('gender')).toEqual('female'); -    pause(2); -  }); -  describe('do it again', function() { -    it('should verify that basic widgets work', function(){ -      navigateTo('widgets.html'); -      input('text.basic').enter('Carlos'); -      expect(binding('text.basic')).toEqual('Carlos'); -      pause(2); -      input('text.basic').enter('Carlos Santana'); -      pause(2); -      expect(binding('text.basic')).toEqual('Carlos Santana'); -      pause(2); -      input('text.password').enter('secret'); -      expect(binding('text.password')).toEqual('secret'); -      expect(binding('text.hidden')).toEqual('hiddenValue'); -      expect(binding('gender')).toEqual('male'); -      pause(2); -      input('gender').select('female'); -      expect(binding('gender')).toEqual('female'); -      pause(2); -    }); -  }); -  it('should verify that basic widgets work', function(){ -    navigateTo('widgets.html'); -    input('text.basic').enter('Carlos'); -    expect(binding('text.basic')).toEqual('Carlos'); -    pause(2); -    input('text.basic').enter('Carlos Santana'); -    pause(2); -    expect(binding('text.basic')).toEqual('Carlos Santana'); -    pause(2); -    input('text.password').enter('secret'); -    expect(binding('text.password')).toEqual('secret'); -    expect(binding('text.hidden')).toEqual('hiddenValue'); -    expect(binding('gender')).toEqual('male'); -    pause(2); -    input('gender').select('female'); -    expect(binding('gender')).toEqual('female'); -    pause(2); +    expect(using('#gender-box').binding('gender')).toEqual('female'); + +    expect(repeater('#repeater-row ul li').count()).toEqual(2); +    expect(repeater('#repeater-row ul li').row(1)).toEqual(['adam']); +    expect(repeater('#repeater-row ul li').column('name')).toEqual(['misko', 'adam']); +     +    select('select').option('B'); +    expect(binding('select')).toEqual('B'); +     +    select('multiselect').options('A', 'C'); +    expect(binding('multiselect').fromJson()).toEqual(['A', 'C']); + +    expect(binding('button').fromJson()).toEqual({'count': 0}); +    element('form a').click(); +    expect(binding('button').fromJson()).toEqual({'count': 1}); +    element('input[value="submit"]').click(); +    expect(binding('button').fromJson()).toEqual({'count': 2}); +    element('input[value="button"]').click(); +    expect(binding('button').fromJson()).toEqual({'count': 3}); +    element('input[type="image"]').click(); +    expect(binding('button').fromJson()).toEqual({'count': 4}); +     +    /** +     * Custom value parser for futures. +     */ +    function checkboxParser(value) { +      return angular.fromJson(value.substring(value.indexOf('=')+1)); +    } +     +    input('checkbox.tea').check(); +    expect(binding('checkbox').parsedWith(checkboxParser)).toEqual({coffee: false, tea: false}); +    input('checkbox.coffee').check(); +    expect(binding('checkbox').parsedWith(checkboxParser)).toEqual({coffee: true, tea: false}); +    input('checkbox.tea').check(); +    input('checkbox.tea').check(); +    input('checkbox.tea').check(); +    expect(binding('checkbox').parsedWith(checkboxParser)).toEqual({coffee: true, tea: true});    });  }); diff --git a/scenario/widgets.html b/scenario/widgets.html index 08443d2a..80a0a22f 100644 --- a/scenario/widgets.html +++ b/scenario/widgets.html @@ -2,6 +2,7 @@  <html xmlns:ng="http://angularjs.org">    <head>      <link rel="stylesheet" type="text/css" href="style.css"/> +    <script type="text/javascript" src="../lib/jquery/jquery-1.4.2.js"></script>      <script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script>    </head>    <body ng:init="$window.$scope = this"> @@ -14,7 +15,7 @@        <tr><th colspan="3">Input text field</th></tr>        <tr>          <td>basic</td> -        <td> +        <td id="text-basic-box">            <input type="text" name="text.basic"/>          </td>          <td>text.basic={{text.basic}}</td> @@ -30,7 +31,7 @@          <td>text.hidden={{text.hidden}}</td>        </tr>        <tr><th colspan="3">Input selection field</th></tr> -      <tr> +      <tr id="gender-box">          <td>radio</td>          <td>           <input type="radio" name="gender" value="female"/> Female <br/> @@ -78,13 +79,13 @@             <input type="button" value="button" ng:change="button.count = button.count + 1"/> <br/>             <input type="submit" value="submit" ng:change="button.count = button.count + 1"/><br/>             <input type="image" src="" ng:change="button.count = button.count + 1"/><br/> -           <a href=""   ng:click="button.count = button.count + 1">action</a> +           <a href="" ng:click="button.count = button.count + 1">action</a>            </form>          </td>          <td>button={{button}}</td>        </tr>        <tr><th colspan="3">Repeaters</th></tr> -      <tr> +      <tr id="repeater-row">          <td>ng:repeat</td>          <td>            <ul> diff --git a/src/jqLite.js b/src/jqLite.js index 2f32b121..a2ea286b 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -119,10 +119,14 @@ JQLite.prototype = {    },    trigger: function(type) { -    var evnt = document.createEvent('MouseEvents'), -        element = this[0]; -    evnt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element); -    element.dispatchEvent(evnt); +    if (msie) { +      this[0].fireEvent('on' + type); +    } else { +      var evnt = document.createEvent('MouseEvents'), +          element = this[0]; +      evnt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element); +      element.dispatchEvent(evnt); +    }    },    replaceWith: function(replaceNode) { @@ -249,10 +253,6 @@ if (msie) {          if (isDefined(value)) e.innerText = value;          return e.innerText;        } -    }, - -    trigger: function(type) { -      this[0].fireEvent('on' + type);      }    });  } diff --git a/src/scenario/Application.js b/src/scenario/Application.js index 24ae99e9..4ee0dd03 100644 --- a/src/scenario/Application.js +++ b/src/scenario/Application.js @@ -47,5 +47,5 @@ angular.scenario.Application.prototype.navigateTo = function(url, onloadFn) {   */  angular.scenario.Application.prototype.executeAction = function(action) {    var $window = this.getWindow(); -  return action.call($window, _jQuery($window.document), $window); +  return action.call(this, $window, _jQuery($window.document));  }; diff --git a/src/scenario/DSL.js b/src/scenario/DSL.js deleted file mode 100644 index a7571afe..00000000 --- a/src/scenario/DSL.js +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Shared DSL statements that are useful to all scenarios. - */ - -/** -* Usage: -*    pause(seconds) pauses the test for specified number of seconds -*/ -angular.scenario.dsl('pause', function() { - return function(time) { -   return this.addFuture('pause for ' + time + ' seconds', function(done) { -     this.setTimeout(function() { done(null, time * 1000); }, time * 1000); -   }); - }; -}); - -/** - * Usage: - *    expect(future).{matcher} where matcher is one of the matchers defined - *    with angular.scenario.matcher - * - * ex. expect(binding("name")).toEqual("Elliott") - */ -angular.scenario.dsl('expect', function() { -  var chain = angular.extend({}, angular.scenario.matcher); -   -  chain.not = function() { -    this.inverse = true; -    return chain; -  }; -   -  return function(future) { -    this.future = future; -    return chain; -  }; -}); - -/** - * Usage: - *    navigateTo(future|string) where url a string or future with a value  - *    of a  URL to navigate to - */ -angular.scenario.dsl('navigateTo', function() { -  return function(url) { -    var application = this.application; -    var name = url; -    if (url.name) { -      name = ' value of ' + url.name; -    } -    return this.addFuture('navigate to ' + name, function(done) { -      application.navigateTo(url.value || url, function() { -        application.executeAction(function() { -          if (this.angular) { -            var $browser = this.angular.service.$browser(); -            $browser.poll(); -            $browser.notifyWhenNoOutstandingRequests(function() { -              done(null, url.value || url); -            }); -          } else { -            done(null, url.value || url); -          } -        }); -      }); -    }); -  }; -}); - -/** - * Usage: - *    input(name).enter(value) enters value in input with specified name - *    input(name).check() checks checkbox - *    input(name).select(value) selects the readio button with specified name/value - */ -angular.scenario.dsl('input', function() { -  var chain = {}; -   -  chain.enter = function(value) { -    var spec = this; -    return this.addFutureAction("input '" + this.name + "' enter '" + value + "'", function(done) { -      var input = _jQuery(this.document).find('input[name=' + spec.name + ']'); -      if (!input.length) -        return done("Input named '" + spec.name + "' does not exist."); -      input.val(value); -      this.angular.element(input[0]).trigger('change'); -      done(); -    }); -  }; -   -  chain.check = function() { -    var spec = this; -    return this.addFutureAction("checkbox '" + this.name + "' toggle", function(done) { -      var input = _jQuery(this.document). -        find('input:checkbox[name=' + spec.name + ']'); -      if (!input.length) -        return done("Input named '" + spec.name + "' does not exist."); -      this.angular.element(input[0]).trigger('click'); -      input.attr('checked', !input.attr('checked')); -      done(); -    }); -  }; -   -  chain.select = function(value) { -    var spec = this; -    return this.addFutureAction("radio button '" + this.name + "' toggle '" + value + "'", function(done) { -      var input = _jQuery(this.document). -        find('input:radio[name$="@' + spec.name + '"][value="' + value + '"]'); -      if (!input.length) -        return done("Input named '" + spec.name + "' does not exist."); -      this.angular.element(input[0]).trigger('click'); -      input.attr('checked', !input.attr('checked')); -      done(); -    }); -  }; -   -  return function(name) { -    this.name = name; -    return chain; -  }; -}); - -/** - * Usage: - *    binding(name) returns the value of a binding - */ -angular.scenario.dsl('binding', function() { -  return function(name) { -    return this.addFutureAction("select binding '" + name + "'", function(done) { -      var element = _jQuery(this.document).find('[ng\\:bind="' + name + '"]'); -      if (!element.length) -        return done("Binding named '" + name + "' does not exist."); -      done(null, element.text()); -    }); -  }; -}); diff --git a/src/scenario/Future.js b/src/scenario/Future.js index 60fad9c5..30c2d902 100644 --- a/src/scenario/Future.js +++ b/src/scenario/Future.js @@ -6,6 +6,7 @@ angular.scenario.Future = function(name, behavior) {    this.behavior = behavior;    this.fulfilled = false;    this.value = undefined; +  this.parser = angular.identity;  };  /** @@ -16,7 +17,38 @@ angular.scenario.Future = function(name, behavior) {  angular.scenario.Future.prototype.execute = function(doneFn) {    this.behavior(angular.bind(this, function(error, result) {      this.fulfilled = true; +    if (result) { +      try { +        result = this.parser(result); +      } catch(e) { +        error = e; +      } +    }      this.value = error || result;      doneFn(error, result);    }));  }; + +/** + * Configures the future to convert it's final with a function fn(value) + */ +angular.scenario.Future.prototype.parsedWith = function(fn) { +  this.parser = fn; +  return this; +}; + +/** + * Configures the future to parse it's final value from JSON + * into objects. + */ +angular.scenario.Future.prototype.fromJson = function() { +  return this.parsedWith(angular.fromJson); +}; + +/** + * Configures the future to convert it's final value from objects + * into JSON. + */ +angular.scenario.Future.prototype.toJson = function() { +  return this.parsedWith(angular.toJson); +}; diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index ff20d1d1..55360592 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -82,14 +82,36 @@ angular.scenario.Runner.prototype.run = function(ui, application, specRunnerClas    $root.setTimeout = function() {      return self.$window.setTimeout.apply(self.$window, arguments);    }; -  asyncForEach(specs, angular.bind(this, function(spec, specDone) { +  asyncForEach(specs, function(spec, specDone) { +    var dslCache = {};      var runner = angular.scope($root);      runner.$become(specRunnerClass); -    angular.foreach(angular.scenario.dsl, angular.bind(this, function(fn, key) { -      this.$window[key] = function() { -        return fn.call($root).apply(angular.scope(runner), arguments); +    angular.foreach(angular.scenario.dsl, function(fn, key) { +      dslCache[key] = fn.call($root); +    }); +    angular.foreach(angular.scenario.dsl, function(fn, key) { +      self.$window[key] = function() { +        var scope = angular.scope(runner); + +        // Make the dsl accessible on the current chain +        scope.dsl = {}; +        angular.foreach(dslCache, function(fn, key) { +          scope.dsl[key] = function() { +            return dslCache[key].apply(scope, arguments); +          }; +        }); + +        // Make these methods work on the current chain +        scope.addFuture = function() { +          return angular.scenario.SpecRunner.prototype.addFuture.apply(scope, arguments); +        }; +        scope.addFutureAction = function() { +          return angular.scenario.SpecRunner.prototype.addFutureAction.apply(scope, arguments); +        }; + +        return scope.dsl[key].apply(scope, arguments);        }; -    })); +    });      runner.run(ui, spec, specDone); -  }), specsDone || angular.noop); +  }, specsDone || angular.noop);  }; diff --git a/src/scenario/SpecRunner.js b/src/scenario/SpecRunner.js index 8b6d4ef1..d6cbdcdc 100644 --- a/src/scenario/SpecRunner.js +++ b/src/scenario/SpecRunner.js @@ -41,7 +41,7 @@ angular.scenario.SpecRunner.prototype.run = function(ui, spec, specDone) {          });        } catch (e) {          stepUI.error(e); -        rethrow(e); +        throw e;        }      },       function(e) { @@ -71,8 +71,56 @@ angular.scenario.SpecRunner.prototype.addFuture = function(name, behavior) {   */  angular.scenario.SpecRunner.prototype.addFutureAction = function(name, behavior) {    return this.addFuture(name, function(done) { -    this.application.executeAction(function() { -      behavior.call(this, done); -    }); +    this.application.executeAction(angular.bind(this, function($window, $document) { + +      $document.elements = angular.bind(this, function(selector) { +        var args = Array.prototype.slice.call(arguments, 1); +        if (this.selector) { +          selector = this.selector + ' ' + (selector || ''); +        } +        angular.foreach(args, function(value, index) { +          selector = selector.replace('$' + (index + 1), value); +        }); +        var result = $document.find(selector); +        if (!result.length) { +          throw { +            type: 'selector', +            message: 'Selector ' + selector + ' did not match any elements.' +          }; +        } + +        result.trigger = function(type) { +          result.each(function(index, node) { +            var element = $window.angular.element(node); +            //TODO(esprehn): HACK!!! Something is broken in angular event dispatching +            //  and if the real jQuery is used we need to set the attribtue after too +            if (angular.isDefined(element.selector)) { +              if (type === 'click' && node.nodeName.toLowerCase() === 'input') { +                element.attr('checked', !element.attr('checked')); +              } +            } +            //TODO(esprehn): HACK!! See above comment. +            element.trigger(type); +            if (angular.isDefined(element.selector)) { +              if (type === 'click' && node.nodeName.toLowerCase() === 'input') { +                element.attr('checked', !element.attr('checked')); +              } +            } +          }); +        }; + +        return result; +      }); +       +      try { +        behavior.call(this, $window, $document, done); +      } catch(e) { +        if (e.type && e.type === 'selector') { +          done(e.message); +        } else { +          throw e; +        } +      } +    }));    });  }; diff --git a/src/scenario/dsl.js b/src/scenario/dsl.js new file mode 100644 index 00000000..69af39db --- /dev/null +++ b/src/scenario/dsl.js @@ -0,0 +1,270 @@ +/** + * Shared DSL statements that are useful to all scenarios. + */ + +/** +* Usage: +*    pause(seconds) pauses the test for specified number of seconds +*/ +angular.scenario.dsl('pause', function() { + return function(time) { +   return this.addFuture('pause for ' + time + ' seconds', function(done) { +     this.setTimeout(function() { done(null, time * 1000); }, time * 1000); +   }); + }; +}); + +/** + * Usage: + *    expect(future).{matcher} where matcher is one of the matchers defined + *    with angular.scenario.matcher + * + * ex. expect(binding("name")).toEqual("Elliott") + */ +angular.scenario.dsl('expect', function() { +  var chain = angular.extend({}, angular.scenario.matcher); + +  chain.not = function() { +    this.inverse = true; +    return chain; +  }; + +  return function(future) { +    this.future = future; +    return chain; +  }; +}); + +/** + * Usage: + *    navigateTo(future|string) where url a string or future with a value + *    of a  URL to navigate to + */ +angular.scenario.dsl('navigateTo', function() { +  return function(url) { +    var application = this.application; +    var name = url; +    if (url.name) { +      name = ' value of ' + url.name; +    } +    return this.addFuture('navigate to ' + name, function(done) { +      application.navigateTo(url.value || url, function() { +        application.executeAction(function($window) { +          if ($window.angular) { +            var $browser = $window.angular.service.$browser(); +            $browser.poll(); +            $browser.notifyWhenNoOutstandingRequests(function() { +              done(null, url.value || url); +            }); +          } else { +            done(null, url.value || url); +          } +        }); +      }); +    }); +  }; +}); + +/** + * Usage: + *    using(selector) scopes the next DSL element selection + * + * ex. + *   using('#foo').input('bar') + */ +angular.scenario.dsl('using', function() { +  return function(selector) { +    this.selector = (this.selector||'') + ' ' + selector; +    return this.dsl; +  }; +}); + +/** + * Usage: + *    binding(name) returns the value of a binding + */ +angular.scenario.dsl('binding', function() { +  return function(name) { +    return this.addFutureAction("select binding '" + name + "'", function($window, $document, done) { +      var element; +      try { +        element = $document.elements('[ng\\:bind-template*="{{$1}}"]', name); +      } catch(e) { +        if (e.type !== 'selector') +          throw e; +        element = $document.elements('[ng\\:bind="$1"]', name); +      } +      done(null, element.text()); +    }); +  }; +}); + +/** + * Usage: + *    input(name).enter(value) enters value in input with specified name + *    input(name).check() checks checkbox + *    input(name).select(value) selects the readio button with specified name/value + */ +angular.scenario.dsl('input', function() { +  var chain = {}; + +  chain.enter = function(value) { +    return this.addFutureAction("input '" + this.name + "' enter '" + value + "'", function($window, $document, done) { +      var input = $document.elements('input[name="$1"]', this.name); +      input.val(value); +      input.trigger('change'); +      done(); +    }); +  }; + +  chain.check = function() { +    return this.addFutureAction("checkbox '" + this.name + "' toggle", function($window, $document, done) { +      var input = $document.elements('input:checkbox[name="$1"]', this.name); +      input.trigger('click'); +      done(); +    }); +  }; + +  chain.select = function(value) { +    return this.addFutureAction("radio button '" + this.name + "' toggle '" + value + "'", function($window, $document, done) { +      var input = $document. +        elements('input:radio[name$="@$1"][value="$2"]', this.name, value); +      input.trigger('click'); +      done(); +    }); +  }; + +  return function(name) { +    this.name = name; +    return chain; +  }; +}); + +/** + * Usage: + *    repeater('#products table').count() // number of rows + *    repeater('#products table').row(1) // all bindings in row as an array + *    repeater('#products table').column('product.name') // all values across all rows in an array + */ +angular.scenario.dsl('repeater', function() { +  var chain = {}; + +  chain.count = function() { +    return this.addFutureAction('repeater ' + this.selector + ' count', function($window, $document, done) { +      done(null, $document.elements().size()); +    }); +  }; + +  chain.column = function(binding) { +    return this.addFutureAction('repeater ' + this.selector + ' column ' + binding, function($window, $document, done) { +      var values = []; +      $document.elements().each(function() { +        _jQuery(this).find(':visible').each(function() { +          var element = _jQuery(this); +          if (element.attr('ng:bind') === binding) { +            values.push(element.text()); +          } +        }); +      }); +      done(null, values); +    }); +  }; + +  chain.row = function(index) { +    return this.addFutureAction('repeater ' + this.selector + ' row ' + index, function($window, $document, done) { +      var values = []; +      var matches = $document.elements().slice(index, index + 1); +      if (!matches.length) +        return done('row ' + index + ' out of bounds'); +      _jQuery(matches[0]).find(':visible').each(function() { +        var element = _jQuery(this); +        if (element.attr('ng:bind')) { +          values.push(element.text()); +        } +      }); +      done(null, values); +    }); +  }; + +  return function(selector) { +    this.dsl.using(selector); +    return chain; +  }; +}); + +/** + * Usage: + *    select(selector).option('value') // select one option + *    select(selector).options('value1', 'value2', ...) // select options from a multi select + */ +angular.scenario.dsl('select', function() { +  var chain = {}; + +  chain.option = function(value) { +    return this.addFutureAction('select ' + this.name + ' option ' + value, function($window, $document, done) { +      var select = $document.elements('select[name="$1"]', this.name); +      select.val(value); +      select.trigger('change'); +      done(); +    }); +  }; + +  chain.options = function() { +    var values = arguments; +    return this.addFutureAction('select ' + this.name + ' options ' + values, function($window, $document, done) { +      var select = $document.elements('select[multiple][name="$1"]', this.name); +      select.val(values); +      select.trigger('change'); +      done(); +    }); +  }; + +  return function(name) { +    this.name = name; +    return chain; +  }; +}); + +/** + * Usage: + *    element(selector).click() // clicks an element + *    element(selector).attr(name) // gets the value of an attribute + *    element(selector).attr(name, value) // sets the value of an attribute + *    element(selector).val() // gets the value (as defined by jQuery) + *    element(selector).val(value) // sets the value (as defined by jQuery) + */ +angular.scenario.dsl('element', function() { +  var chain = {}; + +  chain.click = function() { +    return this.addFutureAction('element ' + this.selector + ' click', function($window, $document, done) { +      $document.elements().trigger('click'); +      done(); +    }); +  }; + +  chain.attr = function(name, value) { +    var futureName = 'element ' + this.selector + ' get attribute ' + name; +    if (value) { +      futureName = 'element ' + this.selector + ' set attribute ' + name + ' to ' + value; +    } +    return this.addFutureAction(futureName, function($window, $document, done) { +      done(null, $document.elements().attr(name, value)); +    }); +  }; + +  chain.val = function(value) { +    var futureName = 'element ' + this.selector + ' value'; +    if (value) { +      futureName = 'element ' + this.selector + ' set value to ' + value; +    } +    return this.addFutureAction(futureName, function($window, $document, done) { +      done(null, $document.elements().val(value)); +    }); +  }; + +  return function(selector) { +    this.dsl.using(selector); +    return chain; +  }; +}); diff --git a/test/scenario/ApplicationSpec.js b/test/scenario/ApplicationSpec.js index 706fbc36..2fb9881f 100644 --- a/test/scenario/ApplicationSpec.js +++ b/test/scenario/ApplicationSpec.js @@ -18,20 +18,21 @@ describe('angular.scenario.Application', function() {        testDocument = $document;      });      app.navigateTo('http://www.google.com/'); -    app.executeAction(function($document, $window) { +    app.executeAction(function($window, $document) {        expect($window).not.toEqual(testWindow);        expect($document).not.toEqual(testDocument);      });    }); -  it('should execute callback on $window of frame', function() { +  it('should execute callback with correct arguments', function() {      var testWindow = {document: {}};      app.getWindow = function() {         return testWindow;       }; -    app.executeAction(function($document, $window) { -      expect(this).toEqual($window); -      expect(this).toEqual(testWindow); +    app.executeAction(function($window, $document) { +      expect(this).toEqual(app); +      expect($document).toEqual(_jQuery($window.document)); +      expect($window).toEqual(testWindow);      });    }); @@ -52,7 +53,7 @@ describe('angular.scenario.Application', function() {    it('should call onload handler when frame loads', function() {      var called; -    app.getFrame = function() {  +    app.getFrame = function() {        // Mock a little jQuery        var result = {          remove: function() {  diff --git a/test/scenario/FutureSpec.js b/test/scenario/FutureSpec.js index ae475779..1e6af7a1 100644 --- a/test/scenario/FutureSpec.js +++ b/test/scenario/FutureSpec.js @@ -1,13 +1,14 @@  describe('angular.scenario.Future', function() {    var future; -  it('should set the name and behavior', function() { +  it('should set the sane defaults', 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(); +    expect(future.parser).toEqual(angular.identity);    });    it('should be fulfilled after execution and done callback', function() { @@ -35,4 +36,39 @@ describe('angular.scenario.Future', function() {      future.execute(angular.noop);      expect(future.value).toEqual(10);    }); +   +  it('should parse json with fromJson', function() { +    var future = new angular.scenario.Future('test name', function(done) { +      done(null, "{test: 'foo'}"); +    }); +    future.fromJson().execute(angular.noop); +    expect(future.value).toEqual({test: 'foo'}); +  }); +   +  it('should convert to json with toJson', function() { +    var future = new angular.scenario.Future('test name', function(done) { +      done(null, {test: 'foo'}); +    }); +    future.toJson().execute(angular.noop); +    expect(future.value).toEqual('{"test":"foo"}'); +  }); +   +  it('should convert with custom parser', function() { +    var future = new angular.scenario.Future('test name', function(done) { +      done(null, 'foo'); +    }); +    future.parsedWith(function(value) { +      return value.toUpperCase(); +    }).execute(angular.noop); +    expect(future.value).toEqual('FOO'); +  }); +   +  it('should pass error if parser fails', function() { +    var future = new angular.scenario.Future('test name', function(done) { +      done(null, '{'); +    }); +    future.fromJson().execute(function(error, result) { +      expect(error).toBeDefined(); +    }); +  });    }); diff --git a/test/scenario/SpecRunnerSpec.js b/test/scenario/SpecRunnerSpec.js index 0926c3f8..e62bb392 100644 --- a/test/scenario/SpecRunnerSpec.js +++ b/test/scenario/SpecRunnerSpec.js @@ -42,7 +42,7 @@ function ApplicationMock($window) {  }  ApplicationMock.prototype = {    executeAction: function(callback) { -    callback.call(this.$window); +    callback.call(this.$window, _jQuery(this.$window.document), this.$window);    }  }; @@ -59,15 +59,15 @@ describe('angular.scenario.SpecRunner', function() {    it('should bind futures to the spec', function() {      runner.addFuture('test future', function(done) { -      this.application.value = 10; +      this.value = 10;        done();      });      runner.futures[0].execute(angular.noop); -    expect(runner.application.value).toEqual(10); +    expect(runner.value).toEqual(10);    });    it('should pass done to future action behavior', function() { -    runner.addFutureAction('test future', function(done) { +    runner.addFutureAction('test future', function($window, $document, done) {        expect(angular.isFunction(done)).toBeTruthy();        done(10, 20);      }); @@ -77,15 +77,6 @@ describe('angular.scenario.SpecRunner', function() {      });    }); -  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(); diff --git a/test/scenario/DSLSpec.js b/test/scenario/dslSpec.js index b144a3ce..a30fe165 100644 --- a/test/scenario/DSLSpec.js +++ b/test/scenario/dslSpec.js @@ -12,11 +12,20 @@ AngularMock.prototype.reset = function() {  AngularMock.prototype.element = function(node) {    this.log.push('element(' + node.nodeName.toLowerCase() + ')'); -  return this; -}; - -AngularMock.prototype.trigger = function(value) { -  this.log.push('element().trigger(' + value + ')'); +  var mock = this; +  return { +    selector: '', +    attr: function(name, value) { +      mock.log.push('attr(' + name + (angular.isDefined(value) ? ',' + value : '') + ')'); +      return _jQuery.fn.attr.apply(_jQuery(node), arguments);  +    }, +    trigger: function(type) { +      mock.log.push('element().trigger(' + type + ')'); +      //TODO(esprehn): See the HACK!! in the SpecRunner. This avoids +      // triggering the second part of the hack in tests +      delete this.selector; +    } +  };  };  AngularMock.prototype.$browser = function() { @@ -53,6 +62,12 @@ describe("angular.scenario.dsl", function() {          $root.futureResult = result;        });      }; +    $root.dsl = {}; +    angular.foreach(angular.scenario.dsl, function(fn, name) { +      $root.dsl[name] = function() { +        return fn.call($root).apply($root, arguments); +      }; +    });      $root.application = new angular.scenario.Application($window.document);      $root.application.getWindow = function() {        return $window; @@ -75,7 +90,7 @@ describe("angular.scenario.dsl", function() {      });      it('should pause for specified seconds', function() { -      angular.scenario.dsl.pause.call($root).call($root, 10); +      $root.dsl.pause(10);        expect($root.timerValue).toEqual(10000);        expect($root.futureResult).toEqual(10000);      }); @@ -84,11 +99,11 @@ describe("angular.scenario.dsl", function() {    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); +      var result = $root.dsl.expect(future);        result.toEqual(10);        expect($root.futureError).toBeUndefined();        expect($root.futureResult).toBeUndefined(); -      result = angular.scenario.dsl.expect.call($root).call($root, future); +      result = $root.dsl.expect(future);        result.toEqual(20);        expect($root.futureError).toBeDefined();      }); @@ -96,27 +111,27 @@ describe("angular.scenario.dsl", function() {    describe('NavigateTo', function() {      it('should allow a string url', function() { -      angular.scenario.dsl.navigateTo.call($root).call($root, 'http://myurl'); +      $root.dsl.navigateTo('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); +      $root.dsl.navigateTo(future);        expect($window.location).toEqual('http://myurl');        expect($root.futureResult).toEqual('http://myurl');      });      it('should complete if angular is missing from app frame', function() {        delete $window.angular; -      angular.scenario.dsl.navigateTo.call($root).call($root, 'http://myurl'); +      $root.dsl.navigateTo('http://myurl');        expect($window.location).toEqual('http://myurl');        expect($root.futureResult).toEqual('http://myurl');      });      it('should wait for angular notify when no requests pending', function() { -      angular.scenario.dsl.navigateTo.call($root).call($root, 'url'); +      $root.dsl.navigateTo('url');        expect($window.angular.log).toContain('$brower.poll()');        expect($window.angular.log).          toContain('$brower.notifyWhenNoOutstandingRequests()'); @@ -143,24 +158,141 @@ describe("angular.scenario.dsl", function() {          remove();      }); +    describe('Select', function() { +      it('should select single option', function() { +        doc.append( +          '<select name="test">' + +          '  <option>A</option>' + +          '  <option selected>B</option>' + +          '</select>' +        ); +        $root.dsl.select('test').option('A'); +        expect(_jQuery('[name="test"]').val()).toEqual('A'); +      }); + +      it('should select multiple options', function() { +        doc.append( +          '<select name="test" multiple>' + +          '  <option>A</option>' + +          '  <option selected>B</option>' + +          '  <option>C</option>' + +          '</select>' +        ); +        $root.dsl.select('test').options('A', 'B'); +        expect(_jQuery('[name="test"]').val()).toEqual(['A','B']); +      }); +       +      it('should fail to select multiple options on non-multiple select', function() { +        doc.append('<select name="test"></select>'); +        $root.dsl.select('test').options('A', 'B'); +        expect($root.futureError).toMatch(/did not match/); +      }); +    }); + +    describe('Element', function() { +      it('should execute click', function() { +        var clicked; +        doc.append('<a href=""></a>'); +        doc.find('a').click(function() { +          clicked = true; +        }); +        $root.dsl.element('a').click(); +      }); +       +      it('should get attribute', function() { +        doc.append('<div id="test" class="foo"></div>'); +        $root.dsl.element('#test').attr('class'); +        expect($root.futureResult).toEqual('foo'); +      }); + +      it('should set attribute', function() { +        doc.append('<div id="test" class="foo"></div>'); +        $root.dsl.element('#test').attr('class', 'bam'); +        expect(doc.find('div').attr('class')).toEqual('bam'); +      }); +       +      it('should get val', function() { +        doc.append('<input value="bar">'); +        $root.dsl.element('input').val(); +        expect($root.futureResult).toEqual('bar'); +      }); +       +      it('should set val', function() { +        doc.append('<input value="bar">'); +        $root.dsl.element('input').val('baz'); +        expect(doc.find('input').val()).toEqual('baz'); +      }); + +    }); + +    describe('Repeater', function() { +      var chain; +      beforeEach(function() { +        doc.append( +          '<ul>' + +          '  <li ng:repeat-index="0"><span ng:bind="name">misko</span><span ng:bind="gender">male</span></li>' + +          '  <li ng:repeat-index="1"><span ng:bind="name">felisa</span><span ng:bind="gender">female</span></li>' + +          '</ul>' +        ); +        chain = $root.dsl.repeater('ul li'); +      }); + +      it('should get the row count', function() { +        chain.count(); +        expect($root.futureResult).toEqual(2); +      }); + +      it('should get a row of bindings', function() { +        chain.row(1); +        expect($root.futureResult).toEqual(['felisa', 'female']); +      }); + +      it('should get a column of bindings', function() { +        chain.column('gender'); +        expect($root.futureResult).toEqual(['male', 'female']); +      }); +    }); +      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'); +        $root.dsl.binding('foo.bar');          expect($root.futureResult).toEqual('some value');        }); +       +      it('should select binding in template by name', function() { +        doc.append('<pre ng:bind-template="foo {{bar}} baz">foo some baz</pre>'); +        $root.dsl.binding('bar'); +        expect($root.futureResult).toEqual('foo some baz'); +      });        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/); +        $root.dsl.binding('foo.bar'); +        expect($root.futureError).toMatch(/did not match/); +      }); +    }); +     +    describe('Using', function() { +      it('should prefix selector in $document.elements()', function() { +        var chain; +        doc.append( +          '<div id="test1"><input name="test.input" value="something"></div>' + +          '<div id="test2"><input name="test.input" value="something"></div>' +        ); +        chain = $root.dsl.using('div#test2'); +        chain.input('test.input').enter('foo'); +        expect($window.angular.log).toContain('element(input)'); +        expect($window.angular.log).toContain('element().trigger(change)'); +        var inputs = _jQuery('input[name="test.input"]'); +        expect(inputs.first().val()).toEqual('something'); +        expect(inputs.last().val()).toEqual('foo');        });      });      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'); +        var chain = $root.dsl.input('test.input');          chain.enter('foo');          expect($window.angular.log).toContain('element(input)');          expect($window.angular.log).toContain('element().trigger(change)'); @@ -168,18 +300,16 @@ describe("angular.scenario.dsl", function() {        });        it('should return error if no input exists', function() { -        var chain = angular.scenario.dsl.input. -          call($root).call($root, 'test.input'); +        var chain = $root.dsl.input('test.input');          chain.enter('foo'); -        expect($root.futureError).toMatch(/does not exist/); +        expect($root.futureError).toMatch(/did not match/);        });        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'); +        var chain = $root.dsl.input('test.input');          chain.check();          expect($window.angular.log).toContain('element(input)');          expect($window.angular.log).toContain('element().trigger(click)'); @@ -193,25 +323,24 @@ describe("angular.scenario.dsl", function() {            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'); +      it('should return error if checkbox did not match', function() { +        var chain = $root.dsl.input('test.input');          chain.check(); -        expect($root.futureError).toMatch(/does not exist/); +        expect($root.futureError).toMatch(/did not match/);        });        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="checked">'); +          '<input type="radio" name="0@test.input" value="bar" checked="checked">' +        );          // HACK! We don't know why this is sometimes false on chrome          _jQuery('input[name="0@test.input"][value="bar"]').attr('checked', true);          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'); +        var chain = $root.dsl.input('test.input');          chain.select('foo');          expect($window.angular.log).toContain('element(input)');          expect($window.angular.log).toContain('element().trigger(click)'); @@ -221,13 +350,12 @@ describe("angular.scenario.dsl", function() {            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'); +      it('should return error if radio button did not match', function() { +        var chain = $root.dsl.input('test.input');          chain.select('foo'); -        expect($root.futureError).toMatch(/does not exist/); +        expect($root.futureError).toMatch(/did not match/);        });      }); +        }); -  });  | 
