diff options
| -rw-r--r-- | css/angular-scenario.css | 29 | ||||
| -rw-r--r-- | jsTestDriver-jquery.conf | 1 | ||||
| -rw-r--r-- | jsTestDriver.conf | 1 | ||||
| -rw-r--r-- | scenario/Runner.html | 3 | ||||
| -rw-r--r-- | scenario/widgets-scenario2.js | 54 | ||||
| -rw-r--r-- | scenario/widgets-scenarios.js | 22 | ||||
| -rw-r--r-- | scenario/widgets.html | 3 | ||||
| -rw-r--r-- | src/scenario/Runner.js | 250 | ||||
| -rw-r--r-- | src/scenario/Steps.js | 57 | ||||
| -rw-r--r-- | src/scenario/_namespace.js | 6 | ||||
| -rw-r--r-- | src/scenario/bootstrap.js | 19 | ||||
| -rw-r--r-- | test/scenario/RunnerSpec.js | 105 | ||||
| -rw-r--r-- | test/scenario/StepsTest.js | 7 | 
13 files changed, 284 insertions, 273 deletions
| diff --git a/css/angular-scenario.css b/css/angular-scenario.css index ad263d21..70256a5a 100644 --- a/css/angular-scenario.css +++ b/css/angular-scenario.css @@ -2,18 +2,25 @@  /* CSS Document */  #runner { -  position: absolute;  -  top:5px;  -  left:10px;  +  position: absolute; +  top:5px; +  left:10px;    right:10px;    height: 200px;  } +.console { +  display: block; +  overflow: scroll; +  height: 200px; +  border: 1px solid black; +} +  #testView { -  position: absolute;  -  bottom:10px;  -  top:210px;  -  left:10px;  +  position: absolute; +  bottom:10px; +  top:230px; +  left:10px;    right:10px;  } @@ -22,12 +29,8 @@    height: 100%;  } -.console { -  display: block; -  overflow: scroll; -  height: 200px; -  border: 1px solid black; -} + +///////////////  .collapsed .log {    display: none; diff --git a/jsTestDriver-jquery.conf b/jsTestDriver-jquery.conf index 196d282f..dde88e80 100644 --- a/jsTestDriver-jquery.conf +++ b/jsTestDriver-jquery.conf @@ -6,7 +6,6 @@ load:    - lib/jquery/jquery-1.4.2.js    - src/Angular.js    - src/*.js -  - src/scenario/_namespace.js    - src/scenario/*.js    - test/testabilityPatch.js    - test/angular-mocks.js diff --git a/jsTestDriver.conf b/jsTestDriver.conf index e2820ddb..6f959b8f 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -6,7 +6,6 @@ load:  #  - lib/jquery/jquery-1.4.2.js    - src/Angular.js    - src/*.js -  - src/scenario/_namespace.js    - src/scenario/*.js    - test/testabilityPatch.js    - test/angular-mocks.js diff --git a/scenario/Runner.html b/scenario/Runner.html index c5eb6205..5502283a 100644 --- a/scenario/Runner.html +++ b/scenario/Runner.html @@ -2,8 +2,7 @@  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">    <head>      <script type="text/javascript" src="../src/scenario/bootstrap.js"></script> -    <script type="text/javascript" src="widgets-scenarios.js"></script> -    <script type="text/javascript" src="datastore-scenarios.js"></script> +    <script type="text/javascript" src="widgets-scenario2.js"></script>    </head>    <body>    </body> diff --git a/scenario/widgets-scenario2.js b/scenario/widgets-scenario2.js new file mode 100644 index 00000000..e24cabad --- /dev/null +++ b/scenario/widgets-scenario2.js @@ -0,0 +1,54 @@ +browser = { +  navigateTo: function(url){ +    $scenario.addStep('Navigate to: ' + url, function(done){ +      var self = this; +      self.testFrame.load(function(){ +        self.testFrame.unbind(); +        self.testDocument = self.testWindow.angular.element(self.testWindow.document); +        done(); +      }); +      if (this.testFrame.attr('src') == url) { +        this.testWindow.location.reload(); +      } else { +        this.testFrame.attr('src', url); +      } +    }); +  } +}; + +function input(selector) { +  return { +    enter: function(value){ +      $scenario.addStep("Set input text of '" + selector + "' to value '" + value + "'", function(done){ +        var input = this.testDocument.find('input[name=' + selector + ']'); +        input.val(value); +        input.trigger('change'); +        done(); +      }); +    } +  }; +} + +function expect(selector) { +  return { +    toEqual: function(expected) { +      $scenario.addStep("Expect that " + selector + " equals '" + expected + "'", function(done){ +        var attrName = selector.substring(2, selector.length - 2); +        var binding = this.testDocument.find('span[ng-bind=' + attrName + ']'); +        if (binding.text() != expected) { +          this.result.fail("Expected '" + expected + "' but was '" + binding.text() + "'"); +        } +        done(); +      }); +    } +  }; +} + +describe('widgets', function(){ +  it('should verify that basic widgets work', function(){ +    browser.navigateTo('widgets.html'); +    expect('{{text.basic}}').toEqual(''); +    input('text.basic').enter('John'); +    expect('{{text.basic}}').toEqual('JohnXX'); +  }); +}); diff --git a/scenario/widgets-scenarios.js b/scenario/widgets-scenarios.js index 663b06da..a1e6c0ed 100644 --- a/scenario/widgets-scenarios.js +++ b/scenario/widgets-scenarios.js @@ -3,28 +3,28 @@ angular.scenarioDef.widgets = {      {Given:"browser", at:"widgets.html"}    ],    checkWidgetBinding:[ -    {Then:"text", at:"{{name}}", should_be:""}, -    {When:"enter", text:"John", at:":input[name=name]"}, -    {Then:"text", at:"{{name}}", should_be:"John"}, -     -    {Then:"text", at:"{{gender}}", should_be:""}, -    {When:"click", at:"input:radio[value=male]"}, +    {Then:"text", at:"{{text.basic}}", should_be:""}, +    {When:"enter", text:"John", at:":input[name=text.basic]"}, +    {Then:"text", at:"{{text.basic}}", should_be:"John"}, +      {Then:"text", at:"{{gender}}", should_be:"male"}, -     +    {When:"click", at:"input:radio[value=female]"}, +    {Then:"text", at:"{{gender}}", should_be:"female"}, +      {Then:"text", at:"{{tea}}", should_be:"on"},      {When:"click", at:"input[name=tea]"},      {Then:"text", at:"{{tea}}", should_be:""}, -     +      {Then:"text", at:"{{coffee}}", should_be:""},      {When:"click", at:"input[name=coffee]"},      {Then:"text", at:"{{coffee}}", should_be:"on"}, -     +      {Then:"text", at:"{{count}}", should_be:0},      {When:"click", at:"form :button"},      {When:"click", at:"form :submit"},      {When:"click", at:"form :image"},      {Then:"text", at:"{{count}}", should_be:3}, -     +      {Then:"text", at:"{{select}}", should_be:"A"},      {When:"select", at:"select[name=select]", option:"B"},      {Then:"text", at:"{{select}}", should_be:"B"}, @@ -36,7 +36,7 @@ angular.scenarioDef.widgets = {      {Then:"text", at:"{{multiple}}", should_be:["A", "B"]},      {When:"select", at:"select[name=multiple]", option:"A"},      {Then:"text", at:"{{multiple}}", should_be:["B"]}, -     +      {Then:"text", at:"{{hidden}}", should_be:"hiddenValue"},      {Then:"text", at:"{{password}}", should_be:"passwordValue"}, diff --git a/scenario/widgets.html b/scenario/widgets.html index 242fd9e6..5c3afa21 100644 --- a/scenario/widgets.html +++ b/scenario/widgets.html @@ -16,8 +16,7 @@        <tr>          <td>basic</td>          <td> -          <input type="text" name="text.basic" ng-required ng-validate="number" ng-format="number"/> -          <input type="text" name="text.basic" ng-format="number"/> +          <input type="text" name="text.basic"/>          </td>          <td>text.basic={{text.basic}}</td>        </tr> diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index 7caddc98..eeb4330d 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -1,171 +1,109 @@ -var scenario = angular.scenario; -scenario.SuiteRunner = function(scenarios, body) { -  this.scenarios = scenarios; -  this.body = body; -}; +angular['scenario']  = (angular['scenario'] = {}); -scenario.SuiteRunner.prototype = { -  run:function(){ -    this.setUpUI(); -    this.runScenarios(); -  }, +angular.scenario.Runner = function(scope){ +  var self = scope.$scenario = this; +  this.scope = scope; +  var specs = this.specs = {}; +  var path = []; +  this.scope.describe = function describe(name, body){ +    path.push(name); +    body(); +    path.pop(); +  }; +  this.scope.it = function it(name, body) { +    var specName = path.join(' ') + ': it ' + name; +    self.currentSpec = specs[specName] = { +        name: specName, +        steps:[] +     }; +    body(); +    self.currentSpec = null; +  }; +  this.beginSpec = function returnNoop(){ +    return returnNoop; +  }; +}; -  setUpUI:function(){ -    this.body.html( +angular.scenario.Runner.prototype = { +  run: function(body){ +    body.append(        '<div id="runner">' + -        '<div class="console"></div>' + +        '<ul class="console"></ul>' +        '</div>' +        '<div id="testView">' +          '<iframe></iframe>' +        '</div>'); -    this.console = this.body.find(".console"); -    this.testFrame = this.body.find("iframe"); -    this.console.find(".run").live("click", function(){ -      jQuery(this).parent().find('.log').toggle(); -    }); -  }, - - -  runScenarios:function(){ -    var runner = new scenario.Runner(this.console, this.testFrame); -    _.stepper(this.scenarios, function(next, scenarioObj, name){ -        new scenario.Scenario(name, scenarioObj).run(runner, next); -      }, function(){ -      } -    ); -  } -}; - -scenario.Runner = function(console, frame){ -  this.console = console; -  this.current = null; -  this.tests = []; -  this.frame = frame; -}; -scenario.Runner.prototype = { -  start:function(name){ -    var current = this.current = { -      name:name, -      start:new Date().getTime(), -      scenario:jQuery('<div class="scenario"></div>') +    var console = body.find('#runner .console'); +    this.testFrame = body.find('#testView iframe'); +    this.testWindow = this.testFrame[0].contentWindow; +    this.beginSpec = function(name){ +      var specElement = jQuery('<li class="spec"></li>'); +      var stepContainer = jQuery('<ul class="step"></ul>'); +      console.append(specElement); +      specElement.text(name); +      specElement.append(stepContainer); +      return function(name){ +        var stepElement = jQuery('<li class="step"></li>'); +        var logContainer = jQuery('<ul class="log"></ul>'); +        stepContainer.append(stepElement); +        stepElement.text(name); +        stepElement.append(logContainer); +        return function(message) { +          var logElement = jQuery('<li class="log"></li>'); +          logContainer.append(logElement); +          logElement.text(message); +        }; +      };      }; -    current.run = current.scenario.append( -      '<div class="run">' + -        '<span class="name">.</span>' + -        '<span class="time">.</span>' + -        '<span class="state">.</span>' + -      '</run>').find(".run"); -    current.log = current.scenario.append('<div class="log"></div>').find(".log"); -    current.run.find(".name").text(name); -    this.tests.push(current); -    this.console.append(current.scenario); -  }, -  end:function(name){ -    var current = this.current; -    var run = current.run; -    this.current = null; -    current.end = new Date().getTime(); -    current.time = current.end - current.start; -    run.find(".time").text(current.time); -    run.find(".state").text(current.error ? "FAIL" : "PASS"); -    run.addClass(current.error ? "fail" : "pass"); -    if (current.error) -      run.find(".run").append('<span div="error"></span>').text(current.error); -    current.scenario.find(".log").hide(); +    this.execute("widgets: it should verify that basic widgets work");    }, -  log:function(level) { -    var buf = []; -    for ( var i = 1; i < arguments.length; i++) { -      var arg = arguments[i]; -      buf.push(typeof arg == "string" ?arg:toJson(arg)); -    } -    var log = jQuery('<div class="' + level + '"></div>'); -    log.text(buf.join(" ")); -    this.current.log.append(log); -    this.console.scrollTop(this.console[0].scrollHeight); -    if (level == "error") -      this.current.error = buf.join(" "); -  } -}; -scenario.Scenario = function(name, scenario){ -  this.name = name; -  this.scenario = scenario; -}; -scenario.Scenario.prototype = { -  run:function(runner, callback) { -    var self = this; -    _.stepper(this.scenario, function(next, steps, name){ -      if (name.charAt(0) == '$') { -        next(); -      } else { -        runner.start(self.name + "::" + name); -        var allSteps = (self.scenario.$before||[]).concat(steps); -        _.stepper(allSteps, function(next, step){ -          self.executeStep(runner, step, next); -        }, function(){ -          runner.end(); -          next(); -        }); -      } -    }, callback); +  addStep: function(name, step) { +    this.currentSpec.steps.push({name:name, fn:step});    }, - -  verb:function(step){ -    var fn = null; -    if (!step) fn = function (){ throw "Step is null!"; }; -    else if (step.Given) fn = scenario.GIVEN[step.Given]; -    else if (step.When) fn = scenario.WHEN[step.When]; -    else if (step.Then) fn = scenario.THEN[step.Then]; -      return fn || function (){ -         throw "ERROR: Need Given/When/Then got: " + toJson(step); +  execute: function(name, callback) { +   var spec = this.specs[name], +       result = { +           passed: false, +           failed: false, +           finished: false, +           fail: function(error) { +             result.passed = false; +             result.failed = true; +             result.error = error; +             result.log(angular.isString(error) ? error : angular.toJson(error)); +           } +         }; +       specThis = { +         result: result, +         testWindow: this.testWindow, +         testFrame: this.testFrame         }; -  }, - - -  context: function(runner) { -    var frame = runner.frame; -    var window = frame[0].contentWindow; -    var document; -    if (window.jQuery) -      document = window.jQuery(window.document); -    var context = { -        frame:frame, -        window:window, -        log:_.bind(runner.log, runner, "info"), -        document:document, -        assert:function(element, path){ -          if (element.size() != 1) { -            throw "Expected to find '1' found '"+ -              element.size()+"' for '"+path+"'."; -          } -          return element; -        }, -        element:function(path){ -          var exp = path.replace("{{","[ng-bind=").replace("}}", "]"); -          var element = document.find(exp); -          return context.assert(element, path); -        } -    }; -    return context; -  }, - - -  executeStep:function(runner, step, callback) { -    if (!step) { -      callback(); -      return; -    } -    runner.log("info", toJson(step)); -    var fn = this.verb(step); -    var context = this.context(runner); -    _.extend(context, step); -    try { -      (fn.call(context)||function(c){c();})(callback); -    } catch (e) { -      runner.log("error", "ERROR: " + toJson(e)); -    } +   var beginStep = this.beginSpec(name); +   spec.nextStepIndex = 0; +   function done() { +     result.finished = true; +     (callback||angular.noop).call(specThis); +   } +   function next(){ +     var step = spec.steps[spec.nextStepIndex]; +       if (step) { +         spec.nextStepIndex ++; +         result.log = beginStep(step.name); +         try { +           step.fn.call(specThis, next); +         } catch (e) { +           result.fail(e); +           done(); +         } +       } else { +         result.passed = !result.failed; +         done(); +       } +   }; +   next(); +   return specThis;    } -}; +};
\ No newline at end of file diff --git a/src/scenario/Steps.js b/src/scenario/Steps.js deleted file mode 100644 index ffe75933..00000000 --- a/src/scenario/Steps.js +++ /dev/null @@ -1,57 +0,0 @@ -angular.scenario.GIVEN = { -  browser:function(){ -    var self = this; -    if (jQuery.browser.safari && this.frame.attr('src') == this.at) { -      this.window.location.reload(); -    } else { -      this.frame.attr('src', this.at); -    } -    return function(done){ -      self.frame.load(function(){ -        self.frame.unbind(); -        done(); -      }); -    }; -  }, -  dataset:function(){ -    this.frame.name="$DATASET:" + toJson({dataset:this.dataset}); -  } -}; -angular.scenario.WHEN = { -  enter:function(){ -    var element = this.element(this.at); -    element.attr('value', this.text); -    element.change(); -  }, -  click:function(){ -    var element = this.element(this.at); -    var input = element[0]; -    // emulate the browser behavior which causes it -    // to be overridden at the end. -    var checked = input.checked = !input.checked; -    element.trigger('click'); -    input.checked = checked; -  }, -  select:function(){ -    var element = this.element(this.at); -    var path = "option[value=" + this.option + "]"; -    var option = this.assert(element.find(path)); -    option[0].selected = !option[0].selected; -    element.change(); -  } -}; -angular.scenario.THEN = { -  text:function(){ -    var element = this.element(this.at); -    if (typeof this.should_be != undefined ) { -      var should_be = this.should_be; -      if (_.isArray(this.should_be)) -        should_be = JSON.stringify(should_be); -      if (element.text() != should_be) -        throw "Expected " + should_be + -              " but was " + element.text() + "."; -    } -  }, -  drainRequestQueue:function(){ -  } -}; diff --git a/src/scenario/_namespace.js b/src/scenario/_namespace.js deleted file mode 100644 index 7da3a5d8..00000000 --- a/src/scenario/_namespace.js +++ /dev/null @@ -1,6 +0,0 @@ -if (!angular)                       var angular = window['angular'] = {}; -if (!angular['scenario'])           var angularScenario = angular['scenario'] = {}; -if (!angular['scenarioDef'])        var scenarioDef = angular['scenarioDef'] = {}; -if (!angular['scenario']['GIVEN'])  angularScenario['GIVEN'] = {}; -if (!angular['scenario']['WHEN'])   angularScenario['WHEN'] = {}; -if (!angular['scenario']['THEN'])   angularScenario['THEN'] = {}; diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js index 169f1860..81272bdd 100644 --- a/src/scenario/bootstrap.js +++ b/src/scenario/bootstrap.js @@ -19,22 +19,8 @@    }    window.onload = function(){ -    if (!_.stepper) { -      _.stepper = function(collection, iterator, done){ -        var keys = _.keys(collection); -        function next() { -          if (keys.length) { -            var key = keys.shift(); -            iterator(next, collection[key], key); -          } else { -            (done||_.identity)(); -          } -        } -        next(); -      }; -    }      _.defer(function(){ -      new angular.scenario.SuiteRunner(angular.scenarioDef, jQuery(document.body)).run(); +      $scenarioRunner.run(jQuery(document.body));      });      (onLoadDelegate||function(){})();    }; @@ -42,8 +28,7 @@    addScript("../../lib/underscore/underscore.js");    addScript("../../lib/jquery/jquery-1.4.2.js");    addScript("../angular-bootstrap.js"); -  addScript("_namespace.js"); -  addScript("Steps.js");    addScript("Runner.js"); +  document.write('<script type="text/javascript">$scenarioRunner = new angular.scenario.Runner(window);</script>');  })(window.onload); diff --git a/test/scenario/RunnerSpec.js b/test/scenario/RunnerSpec.js new file mode 100644 index 00000000..bd7c0599 --- /dev/null +++ b/test/scenario/RunnerSpec.js @@ -0,0 +1,105 @@ +describe('Runner', function(){ +  var scenario, runner, log, Describe, It, $scenario; + +  function logger(text) { +    return function(){log += text;}; +  } + +  beforeEach(function(){ +    log = ''; +    scenario = {}; +    runner = new angular.scenario.Runner(scenario); +    Describe = scenario.describe; +    It = scenario.it; +    $scenario = scenario.$scenario; +  }); + +  describe('describe', function(){ +    it('should consume the describe functions', function(){ +      Describe('describe name',  logger('body')); + +      expect(log).toEqual('body'); +    }); + +    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.steps).toEqual([]); +        expect(spec.name).toEqual('describe name: it should text'); +      }); +    }); +  }); + +  describe('steps building', function(){ +    it('should queue steps', function(){ +      function step(){}; +      Describe('name', function(){ +        It('should', function(){ +          $scenario.addStep('stepname', step); +        }); +      }); +      expect($scenario.specs['name: it should'].steps).toEqual([{name:'stepname', fn:step}]); +    }); +  }); + +  describe('execution', function(){ +    it('should execute the queued steps', function(){ +      var next, firstThis, secondThis, doneThis, spec; +      $scenario.specs['spec'] = { +        steps: [ +          {name:'step1', fn: function(done) { +            next = done; +            log += 'first;'; +            firstThis = this; +          }}, +          {name:'step2', fn:function(done){ +            next = done; +            log += 'second;'; +            secondThis = this; +          }} +        ] +      }; + +      spec = $scenario.execute('spec', function(done){ +        log += 'done;'; +        doneThis = this; +      }); +      expect(log).toEqual('first;'); +      next(); +      expect(log).toEqual('first;second;'); +      next(); +      expect(log).toEqual('first;second;done;'); +      expect(spec).not.toEqual(window); +      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 step', function(){ +      $scenario.specs['spec'] = { +          steps: [ +            {name:'error', fn:function(done) { +              throw "MyError"; +            }} +          ] +        }; + +        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"); +    }); +  }); + +});
\ No newline at end of file diff --git a/test/scenario/StepsTest.js b/test/scenario/StepsTest.js deleted file mode 100644 index 32ef637d..00000000 --- a/test/scenario/StepsTest.js +++ /dev/null @@ -1,7 +0,0 @@ -StepsTest = TestCase("StepsTest"); - -StepsTest.prototype.testGivenDataset=function(){ -  var self = {frame:{}, dataset:[]}; -  angular.scenario.GIVEN.dataset.call(self); -  assertEquals('$DATASET:{"dataset":[]}', self.frame.name); -}; | 
