diff options
| author | Misko Hevery | 2010-04-09 16:20:15 -0700 | 
|---|---|---|
| committer | Misko Hevery | 2010-04-09 16:20:15 -0700 | 
| commit | 843bd355d25ebf2369aec79f98cb6704d38497e9 (patch) | |
| tree | 3850d13b9ad8ab6c5dd975c20cf9d849c7429ed2 | |
| parent | 41a5c408c242269bf31bc0b774c7304fdf7c2f1c (diff) | |
| download | angular.js-843bd355d25ebf2369aec79f98cb6704d38497e9.tar.bz2 | |
various bug fixes
| -rw-r--r-- | angular-debug.js | 86 | ||||
| -rw-r--r-- | example/temp.html | 9 | ||||
| -rw-r--r-- | jsTestDriver.conf | 4 | ||||
| -rw-r--r-- | lib/jasmine-jstd-adapter/JasmineAdapter.js | 181 | ||||
| -rw-r--r-- | lib/jasmine/jasmine-0.10.3.js (renamed from lib/jasmine/jasmine-0.10.1.js) | 99 | ||||
| -rw-r--r-- | lib/jstestdriver/JsTestDriver.jar | bin | 3133666 -> 3133701 bytes | |||
| -rw-r--r-- | src/Angular.js | 2 | ||||
| -rw-r--r-- | src/Compiler.js | 14 | ||||
| -rw-r--r-- | src/Scope.js | 5 | ||||
| -rw-r--r-- | src/angular-bootstrap.js | 2 | ||||
| -rw-r--r-- | src/apis.js | 4 | ||||
| -rw-r--r-- | src/services.js | 26 | ||||
| -rw-r--r-- | src/widgets.js | 35 | ||||
| -rw-r--r-- | test/ApiTest.js | 4 | ||||
| -rw-r--r-- | test/ValidatorsTest.js | 2 | ||||
| -rw-r--r-- | test/markupSpec.js | 5 | ||||
| -rw-r--r-- | test/servicesSpec.js | 14 | ||||
| -rw-r--r-- | test/widgetsSpec.js | 9 | 
18 files changed, 320 insertions, 181 deletions
| diff --git a/angular-debug.js b/angular-debug.js index c3f419f7..dd74428a 100644 --- a/angular-debug.js +++ b/angular-debug.js @@ -343,7 +343,7 @@ function merge(src, dst) {  }  function compile(element, parentScope, overrides) { -  var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget); +  var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget),        $element = jqLite(element),        parent = extend({}, parentScope);    parent.$element = $element; @@ -641,8 +641,8 @@ Compiler.prototype = {  };  function eachTextNode(element, fn){ -  var i, chldNodes = element[0].childNodes || [], size = chldNodes.length, chld; -  for (i = 0; i < size; i++) { +  var i, chldNodes = element[0].childNodes || [], chld; +  for (i = 0; i < chldNodes.length; i++) {      if(isTextNode(chld = chldNodes[i])) {        fn(jqLite(chld), i);      } @@ -650,8 +650,8 @@ function eachTextNode(element, fn){  }  function eachNode(element, fn){ -  var i, chldNodes = element[0].childNodes || [], size = chldNodes.length, chld; -  for (i = 0; i < size; i++) { +  var i, chldNodes = element[0].childNodes || [], chld; +  for (i = 0; i < chldNodes.length; i++) {      if(!isTextNode(chld = chldNodes[i])) {        fn(jqLite(chld), i);      } @@ -659,12 +659,12 @@ function eachNode(element, fn){  }  function eachAttribute(element, fn){ -  var i, attrs = element[0].attributes || [], size = attrs.length, chld, attr, attrValue = {}; -  for (i = 0; i < size; i++) { +  var i, attrs = element[0].attributes || [], chld, attr, attrValue = {}; +  for (i = 0; i < attrs.length; i++) {      attr = attrs[i];      attrValue[attr.name] = attr.value;    } -  foreach(attrValue, fn); +  foreachSorted(attrValue, fn);  }  function getter(instance, path, unboundFn) { @@ -848,7 +848,10 @@ function createScope(parent, services, existing) {    }    foreach(services, function(_, name){ -    instance[name] = inject(name); +    var service = inject(name); +    if (service) { +      instance[name] = service; +    }    });    return instance; @@ -2026,7 +2029,9 @@ var angularGlobal = {  var angularCollection = {    'size': size  }; -var angularObject = {}; +var angularObject = { +  'extend': extend +};  var angularArray = {    'indexOf': indexOf,    'include': includes, @@ -3135,9 +3140,11 @@ function valueAccessor(scope, element) {    required = required || required === '';    if (!validator) throw "Validator named '" + validatorName + "' not found.";    function validate(value) { -    var error = required && !trim(value) ? -            "Required" : -             validator({state:scope, scope:{get:scope.$get, set:scope.$set}}, value); +    var error, +        validateScope = extend(new (extend(function(){}, {prototype:scope}))(), {$element:element}); +    error = required && !trim(value) ? +          "Required" : +           validator({state:validateScope, scope:{get:validateScope.$get, set:validateScope.$set}}, value);      if (error !== lastError) {        elementError(element, NG_VALIDATION_ERROR, error);        lastError = error; @@ -3298,7 +3305,8 @@ angularWidget('NG:INCLUDE', function(element){  angularWidget('NG:SWITCH', function ngSwitch(element){    var compiler = this,        watchExpr = element.attr("on"), -      whenFn = ngSwitch[element.attr("using") || 'equals']; +      whenExpr = (element.attr("using") || 'equals').split(":"); +      whenFn = ngSwitch[whenExpr.shift()];        changeExpr = element.attr('change') || '',        cases = [];    if (!whenFn) throw "Using expression '" + usingExpr + "' unknown."; @@ -3307,7 +3315,11 @@ angularWidget('NG:SWITCH', function ngSwitch(element){      if (when) {        cases.push({          when: function(scope, value){ -          return whenFn.call(scope, value, when); +          var args = [value, when]; +          foreach(whenExpr, function(arg){ +            args.push(arg); +          }); +          return whenFn.apply(scope, args);          },          change: changeExpr,          element: caseElement, @@ -3320,13 +3332,10 @@ angularWidget('NG:SWITCH', function ngSwitch(element){      var scope = this, childScope;      this.$watch(watchExpr, function(value){        element.html(''); -      childScope = null; -      var params = {}; +      childScope = createScope(scope);        foreach(cases, function(switchCase){ -        if (switchCase.when(params, value)) { +        if (switchCase.when(childScope, value)) {            element.append(switchCase.element); -          childScope = createScope(scope); -          extend(childScope, params);            childScope.$tryEval(switchCase.change, element);            switchCase.template(switchCase.element, childScope);            childScope.$init(); @@ -3341,13 +3350,15 @@ angularWidget('NG:SWITCH', function ngSwitch(element){    equals: function(on, when) {      return on == when;    }, -  route: function(on, when) { -    var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$', params = [], self = this; +  route: function(on, when, dstName) { +    var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$', +        params = [], +        dst = {};      foreach(when.split(/\W/), function(param){        if (param) {          var paramRegExp = new RegExp(":" + param + "([\\W])");          if (regex.match(paramRegExp)) { -          regex = regex.replace(paramRegExp, "(.*)$1"); +          regex = regex.replace(paramRegExp, "([^\/]*)$1");            params.push(param);          }        } @@ -3355,8 +3366,9 @@ angularWidget('NG:SWITCH', function ngSwitch(element){      var match = on.match(new RegExp(regex));      if (match) {        foreach(params, function(name, index){ -        self[name] = match[index + 1]; +        dst[name] = match[index + 1];        }); +      if (dstName) this.$set(dstName, dst);      }      return match;    } @@ -3370,6 +3382,7 @@ var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.]*)(:([0-9]+))  var DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21};  angularService("$location", function(browser){    var scope = this, location = {parse:parse, toString:toString}; +  var lastHash;    function parse(url){      if (isDefined(url)) {        var match = URL_MATCH.exec(url); @@ -3381,16 +3394,25 @@ angularService("$location", function(browser){          location.path = match[6];          location.search = parseKeyValue(match[8]);          location.hash = match[9]; -        if (location.hash) location.hash = location.hash.substr(1); +        if (location.hash) +          location.hash = location.hash.substr(1); +        lastHash = location.hash;          location.hashPath = match[11] || '';          location.hashSearch = parseKeyValue(match[13]);        }      }    }    function toString() { -    var hashKeyValue = toKeyValue(location.hashSearch), -        hash = (location.hashPath ? location.hashPath : '') + (hashKeyValue ? '?' + hashKeyValue : ''); -    return location.href.split('#')[0] + '#' + (hash ? hash : ''); +    if (lastHash === location.hash) { +      var hashKeyValue = toKeyValue(location.hashSearch), +          hash = (location.hashPath ? location.hashPath : '') + (hashKeyValue ? '?' + hashKeyValue : ''), +          url = location.href.split('#')[0] + '#' + (hash ? hash : ''); +      if (url !== location.href) parse(url); +      return url; +    } else { +      parse(location.href.split('#')[0] + '#' + location.hash); +      return toString(); +    }    }    browser.watchUrl(function(url){      parse(url); @@ -3398,11 +3420,7 @@ angularService("$location", function(browser){    });    parse(browser.getUrl());    this.$onEval(PRIORITY_LAST, function(){ -    var href = toString(); -    if (href != location.href) { -      browser.setUrl(href); -      location.href = href; -    } +    browser.setUrl(toString());    });    return location;  }, {inject: ['$browser']}); @@ -3432,6 +3450,7 @@ angularService("$hover", function(browser) {          tooltip.arrow.addClass('ng-arrow-right');          tooltip.arrow.css({left: (width + 1)+'px'});          tooltip.callout.css({ +          position: 'fixed',            left: (elementRect.left - arrowWidth - width - 4) + "px",            top: (elementRect.top - 3) + "px",            width: width + "px" @@ -3439,6 +3458,7 @@ angularService("$hover", function(browser) {        } else {          tooltip.arrow.addClass('ng-arrow-left');          tooltip.callout.css({ +          position: 'fixed',            left: (elementRect.right + arrowWidth) + "px",            top: (elementRect.top - 3) + "px",            width: width + "px" diff --git a/example/temp.html b/example/temp.html new file mode 100644 index 00000000..3580249d --- /dev/null +++ b/example/temp.html @@ -0,0 +1,9 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +  <head> +    <script type="text/javascript" src="../src/angular-bootstrap.js#autobind"></script> +  </head> +  <body> +    <a href="#"> {{'first'}}<br/>{{'second'}}</a> +  </body> +</html> diff --git a/jsTestDriver.conf b/jsTestDriver.conf index a0cae9a4..9c2ef63f 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -1,9 +1,9 @@  server: http://localhost:9876  load: -  - lib/jasmine/jasmine-0.10.1.js +  - lib/jasmine/jasmine-0.10.3.js    - lib/jasmine-jstd-adapter/JasmineAdapter.js -  - lib/webtoolkit/webtoolkit.base64.js +#  - lib/webtoolkit/webtoolkit.base64.js  #  - lib/jquery/jquery-1.4.2.js  #  - lib/underscore/underscore.js    - src/Angular.js diff --git a/lib/jasmine-jstd-adapter/JasmineAdapter.js b/lib/jasmine-jstd-adapter/JasmineAdapter.js index 83a1deed..ba54251a 100644 --- a/lib/jasmine-jstd-adapter/JasmineAdapter.js +++ b/lib/jasmine-jstd-adapter/JasmineAdapter.js @@ -1,96 +1,103 @@  /**   * @fileoverview Jasmine JsTestDriver Adapter.   * @author ibolmo@gmail.com (Olmo Maldonado) + * @author misko@hevery.com (Misko Hevery)   */  (function() { -// Suite/TestCase before and after function stacks. -var before = []; -var after = []; - -jasmine.Env.prototype.describe = (function(describe){ - -	// TODO(ibolmo): Support nested describes. -	return function(description, specDefinitions){ -		this.currentTestCase = TestCase(description); -		return describe.call(this, description, specDefinitions); -	}; - -})(jasmine.Env.prototype.describe); - - -jasmine.Env.prototype.it = (function(it){ - -	return function(desc, func){ -		var spec = it.call(this, desc, func); -		this.currentTestCase.prototype['test that it ' + desc] = func; -		return spec; -	}; - -})(jasmine.Env.prototype.it); - - -jasmine.Env.prototype.beforeEach = (function(beforeEach){ - -	// TODO(ibolmo): Support beforeEach TestCase. -	return function(beforeEachFunction) { -		beforeEach.call(this, beforeEachFunction); -		if (this.currentTestCase) { -			this.currentTestCase.prototype.setUp = beforeEachFunction; -		} else { -			before.push(beforeEachFunction); -		} -	}; - -})(jasmine.Env.prototype.beforeEach); - - -jasmine.Env.prototype.afterEach = (function(afterEach){ - -	// TODO(ibolmo): Support afterEach TestCase. -	return function(afterEachFunction) { -		afterEach.call(this, afterEachFunction); -		if (this.currentTestCase) { -			this.currentTestCase.prototype.tearDown = afterEachFunction; -		} else { -			after.push(afterEachFunction); -		} -	}; - -})(jasmine.Env.prototype.afterEach); - - -jasmine.NestedResults.prototype.addResult = (function(addResult){ - -	return function(result) { -		addResult.call(this, result); -		if (result.type != 'MessageResult' && !result.passed()) fail(result.message); -	}; - -})(jasmine.NestedResults.prototype.addResult); - - -jstestdriver.plugins.TestRunnerPlugin.prototype.runTestConfiguration = (function(runTestConfiguration){ - -	return function(testRunConfiguration, onTestDone, onTestRunConfigurationComplete){ -		for (var i = 0, l = before.length; i < l; i++) before[i](); -		onTestRunConfigurationComplete = (function(configurationComplete){ - -			return function() { -				for (var i = 0, l = after.length; i < l; i++) after[i](); -				configurationComplete(); -			}; - -		})(onTestRunConfigurationComplete); -		runTestConfiguration.call(this, testRunConfiguration, onTestDone, onTestRunConfigurationComplete); -	}; - -})(jstestdriver.plugins.TestRunnerPlugin.prototype.runTestConfiguration); - - -// Reset environment with overriden methods. -jasmine.currentEnv_ = null; -jasmine.getEnv(); +  function bind(_this, _function){ +    return function(){ +      return _function.call(_this); +    } +  } + +  var currentFrame = frame(null, null); + +  function frame(parent, name){ +    var caseName = (parent && parent.caseName ? parent.caseName + " " : '') + (name ? name : ''); +    var frame = { +      name: name, +      caseName: caseName, +      parent: parent, +      testCase: TestCase(caseName), +      before: [], +      after: [], +      runBefore: function(){ +        if (parent) parent.runBefore.apply(this); +        for ( var i = 0; i < frame.before.length; i++) { +          frame.before[i].apply(this); +        } +      }, +      runAfter: function(){ +        for ( var i = 0; i < frame.after.length; i++) { +          frame.after[i].apply(this); +        } +        if (parent) parent.runAfter.apply(this); +      } +    }; +    return frame; +  }; + +  jasmine.Env.prototype.describe = (function(describe){ +    return function(description){ +      currentFrame = frame(currentFrame, description); +      var val = describe.apply(this, arguments); +      currentFrame = currentFrame.parent; +      return val; +    }; + +  })(jasmine.Env.prototype.describe); + + +  jasmine.Env.prototype.it = (function(it){ +    return function(desc, itFn){ +      var self = this; +      var spec = it.apply(this, arguments); +      var currentSpec = this.currentSpec; +      var frame = this.jstdFrame = currentFrame; +      this.jstdFrame.testCase.prototype['test that it ' + desc] = function(){ +        frame.runBefore.apply(currentSpec); +        try { +          itFn.apply(currentSpec); +        } finally { +          frame.runAfter.apply(currentSpec); +        } +      }; +      return spec; +    }; + +  })(jasmine.Env.prototype.it); + + +  jasmine.Env.prototype.beforeEach = (function(beforeEach){ +    return function(beforeEachFunction) { +      beforeEach.apply(this, arguments); +      currentFrame.before.push(beforeEachFunction); +    }; + +  })(jasmine.Env.prototype.beforeEach); + + +  jasmine.Env.prototype.afterEach = (function(afterEach){ +    return function(afterEachFunction) { +      afterEach.apply(this, arguments); +      currentFrame.after.push(afterEachFunction); +    }; + +  })(jasmine.Env.prototype.afterEach); + + +  jasmine.NestedResults.prototype.addResult = (function(addResult){ +    return function(result) { +      addResult.call(this, result); +      if (result.type != 'MessageResult' && !result.passed()) fail(result.message); +    }; + +  })(jasmine.NestedResults.prototype.addResult); + +  // Reset environment with overriden methods. +  jasmine.currentEnv_ = null; +  jasmine.getEnv();  })(); diff --git a/lib/jasmine/jasmine-0.10.1.js b/lib/jasmine/jasmine-0.10.3.js index f9bd7d6f..f309493f 100644 --- a/lib/jasmine/jasmine-0.10.1.js +++ b/lib/jasmine/jasmine-0.10.3.js @@ -13,7 +13,7 @@ jasmine.unimplementedMethod_ = function() {  };  /** - * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code is just + * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just   * a plain old variable and may be redefined by somebody else.   *   * @private @@ -89,7 +89,38 @@ jasmine.getEnv = function() {   * @returns {Boolean}   */  jasmine.isArray_ = function(value) { -  return Object.prototype.toString.apply(value) === '[object Array]'; +  return jasmine.isA_("Array", value);   +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function(value) { +  return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function(value) { +  return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function(typeName, value) { +  return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';  };  /** @@ -527,6 +558,7 @@ jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {   *   * @param {String} url path to the file to include   * @param {Boolean} opt_global + * @deprecated We suggest you use a different method of including JS source files. <code>jasmine.include</code> will be removed soon.   */  jasmine.include = function(url, opt_global) {    if (opt_global) { @@ -660,6 +692,18 @@ jasmine.Env.prototype.version = function () {  };  /** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function() { +  if (jasmine.version_) { +    var version = this.version(); +    return version.major + "." + version.minor + "." + version.build + " revision " + version.revision; +  } else { +    return "version unknown"; +  } +}; + +/**   * @returns a sequential integer starting at 0   */  jasmine.Env.prototype.nextSpecId = function () { @@ -794,6 +838,12 @@ jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {    mismatchKeys = mismatchKeys || [];    mismatchValues = mismatchValues || []; +  for (var i = 0; i < this.equalityTesters_.length; i++) { +    var equalityTester = this.equalityTesters_[i]; +    var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); +    if (result !== jasmine.undefined) return result; +  } +    if (a === b) return true;    if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { @@ -816,14 +866,16 @@ jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {      return b.matches(a);    } -  if (typeof a === "object" && typeof b === "object") { -    return this.compareObjects_(a, b, mismatchKeys, mismatchValues); +  if (jasmine.isString_(a) && jasmine.isString_(b)) { +    return (a == b);    } -  for (var i = 0; i < this.equalityTesters_.length; i++) { -    var equalityTester = this.equalityTesters_[i]; -    var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); -    if (result !== jasmine.undefined) return result; +  if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { +    return (a == b); +  } + +  if (typeof a === "object" && typeof b === "object") { +    return this.compareObjects_(a, b, mismatchKeys, mismatchValues);    }    //Straight check @@ -1009,11 +1061,13 @@ jasmine.Matchers = function(env, actual, spec, opt_isNot) {    this.reportWasCalled_ = false;  }; +// todo: @deprecated as of Jasmine 0.11, remove soon [xw]  jasmine.Matchers.pp = function(str) { -  return jasmine.util.htmlEscape(jasmine.pp(str)); +  throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +  this.report();  }; -/** @deprecated */ +/** @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. */  jasmine.Matchers.prototype.report = function(result, failing_message, details) {    // todo: report a deprecation warning [xw] @@ -1180,7 +1234,7 @@ jasmine.Matchers.prototype.wasCalled = function() {    }    if (!jasmine.isSpy(this.actual)) { -    throw new Error('Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.'); +    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');    }    this.message = function() { @@ -1199,7 +1253,7 @@ jasmine.Matchers.prototype.wasNotCalled = function() {    }    if (!jasmine.isSpy(this.actual)) { -    throw new Error('Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.'); +    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');    }    this.message = function() { @@ -1218,7 +1272,7 @@ jasmine.Matchers.prototype.wasNotCalled = function() {  jasmine.Matchers.prototype.wasCalledWith = function() {    var expectedArgs = jasmine.util.argsToArray(arguments);    if (!jasmine.isSpy(this.actual)) { -    throw new Error('Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.'); +    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');    }    this.message = function() {      if (this.actual.callCount == 0) { @@ -1234,7 +1288,7 @@ jasmine.Matchers.prototype.wasCalledWith = function() {  jasmine.Matchers.prototype.wasNotCalledWith = function() {    var expectedArgs = jasmine.util.argsToArray(arguments);    if (!jasmine.isSpy(this.actual)) { -    throw new Error('Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.'); +    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');    }    this.message = function() { @@ -1882,8 +1936,7 @@ jasmine.Spec.prototype.finish = function(onComplete) {    }  }; -jasmine.Spec.prototype.after = function(doAfter, test) { - +jasmine.Spec.prototype.after = function(doAfter) {    if (this.queue.isRunning()) {      this.queue.add(new jasmine.Block(this.env, doAfter, this));    } else { @@ -1911,23 +1964,25 @@ jasmine.Spec.prototype.execute = function(onComplete) {  jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {    var runner = this.env.currentRunner(); +  var i; +    for (var suite = this.suite; suite; suite = suite.parentSuite) { -    for (var i = 0; i < suite.before_.length; i++) { +    for (i = 0; i < suite.before_.length; i++) {        this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));      }    } -  for (var i = 0; i < runner.before_.length; i++) { +  for (i = 0; i < runner.before_.length; i++) {      this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));    }    for (i = 0; i < this.afterCallbacks.length; i++) {      this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));    }    for (suite = this.suite; suite; suite = suite.parentSuite) { -    for (var i = 0; i < suite.after_.length; i++) { +    for (i = 0; i < suite.after_.length; i++) {        this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));      }    } -  for (var i = 0; i < runner.after_.length; i++) { +  for (i = 0; i < runner.after_.length; i++) {      this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));    }  }; @@ -2271,6 +2326,6 @@ window.clearInterval = function(timeoutKey) {  jasmine.version_= {    "major": 0,    "minor": 10, -  "build": 1, -  "revision": 1267503060 +  "build": 3, +  "revision": 1270162784    }; diff --git a/lib/jstestdriver/JsTestDriver.jar b/lib/jstestdriver/JsTestDriver.jarBinary files differ index ead31593..00482eda 100644 --- a/lib/jstestdriver/JsTestDriver.jar +++ b/lib/jstestdriver/JsTestDriver.jar diff --git a/src/Angular.js b/src/Angular.js index 7fb59f86..11ebc4bc 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -319,7 +319,7 @@ function merge(src, dst) {  }  function compile(element, parentScope, overrides) { -  var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget); +  var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget),        $element = jqLite(element),        parent = extend({}, parentScope);    parent.$element = $element; diff --git a/src/Compiler.js b/src/Compiler.js index ae2bcdb6..8c95ee8e 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -143,8 +143,8 @@ Compiler.prototype = {  };  function eachTextNode(element, fn){ -  var i, chldNodes = element[0].childNodes || [], size = chldNodes.length, chld; -  for (i = 0; i < size; i++) { +  var i, chldNodes = element[0].childNodes || [], chld; +  for (i = 0; i < chldNodes.length; i++) {      if(isTextNode(chld = chldNodes[i])) {        fn(jqLite(chld), i);      } @@ -152,8 +152,8 @@ function eachTextNode(element, fn){  }  function eachNode(element, fn){ -  var i, chldNodes = element[0].childNodes || [], size = chldNodes.length, chld; -  for (i = 0; i < size; i++) { +  var i, chldNodes = element[0].childNodes || [], chld; +  for (i = 0; i < chldNodes.length; i++) {      if(!isTextNode(chld = chldNodes[i])) {        fn(jqLite(chld), i);      } @@ -161,11 +161,11 @@ function eachNode(element, fn){  }  function eachAttribute(element, fn){ -  var i, attrs = element[0].attributes || [], size = attrs.length, chld, attr, attrValue = {}; -  for (i = 0; i < size; i++) { +  var i, attrs = element[0].attributes || [], chld, attr, attrValue = {}; +  for (i = 0; i < attrs.length; i++) {      attr = attrs[i];      attrValue[attr.name] = attr.value;    } -  foreach(attrValue, fn); +  foreachSorted(attrValue, fn);  } diff --git a/src/Scope.js b/src/Scope.js index 0bc551c4..7529d726 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -179,7 +179,10 @@ function createScope(parent, services, existing) {    }    foreach(services, function(_, name){ -    instance[name] = inject(name); +    var service = inject(name); +    if (service) { +      instance[name] = service; +    }    });    return instance; diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js index d9633854..704c50e2 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -43,7 +43,7 @@    addScript("/JSON.js");    addScript("/Compiler.js");    addScript("/Scope.js"); -  addScript("/jqlite.js"); +  addScript("/jqLite.js");    addScript("/Parser.js");    addScript("/Resource.js");    addScript("/Browser.js"); diff --git a/src/apis.js b/src/apis.js index 3d0c5db3..5864ad60 100644 --- a/src/apis.js +++ b/src/apis.js @@ -14,7 +14,9 @@ var angularGlobal = {  var angularCollection = {    'size': size  }; -var angularObject = {}; +var angularObject = { +  'extend': extend +};  var angularArray = {    'indexOf': indexOf,    'include': includes, diff --git a/src/services.js b/src/services.js index 6e1a1945..e2132f1c 100644 --- a/src/services.js +++ b/src/services.js @@ -7,6 +7,7 @@ var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.]*)(:([0-9]+))  var DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21};  angularService("$location", function(browser){    var scope = this, location = {parse:parse, toString:toString}; +  var lastHash;    function parse(url){      if (isDefined(url)) {        var match = URL_MATCH.exec(url); @@ -18,16 +19,25 @@ angularService("$location", function(browser){          location.path = match[6];          location.search = parseKeyValue(match[8]);          location.hash = match[9]; -        if (location.hash) location.hash = location.hash.substr(1); +        if (location.hash) +          location.hash = location.hash.substr(1); +        lastHash = location.hash;          location.hashPath = match[11] || '';          location.hashSearch = parseKeyValue(match[13]);        }      }    }    function toString() { -    var hashKeyValue = toKeyValue(location.hashSearch), -        hash = (location.hashPath ? location.hashPath : '') + (hashKeyValue ? '?' + hashKeyValue : ''); -    return location.href.split('#')[0] + '#' + (hash ? hash : ''); +    if (lastHash === location.hash) { +      var hashKeyValue = toKeyValue(location.hashSearch), +          hash = (location.hashPath ? location.hashPath : '') + (hashKeyValue ? '?' + hashKeyValue : ''), +          url = location.href.split('#')[0] + '#' + (hash ? hash : ''); +      if (url !== location.href) parse(url); +      return url; +    } else { +      parse(location.href.split('#')[0] + '#' + location.hash); +      return toString(); +    }    }    browser.watchUrl(function(url){      parse(url); @@ -35,11 +45,7 @@ angularService("$location", function(browser){    });    parse(browser.getUrl());    this.$onEval(PRIORITY_LAST, function(){ -    var href = toString(); -    if (href != location.href) { -      browser.setUrl(href); -      location.href = href; -    } +    browser.setUrl(toString());    });    return location;  }, {inject: ['$browser']}); @@ -69,6 +75,7 @@ angularService("$hover", function(browser) {          tooltip.arrow.addClass('ng-arrow-right');          tooltip.arrow.css({left: (width + 1)+'px'});          tooltip.callout.css({ +          position: 'fixed',            left: (elementRect.left - arrowWidth - width - 4) + "px",            top: (elementRect.top - 3) + "px",            width: width + "px" @@ -76,6 +83,7 @@ angularService("$hover", function(browser) {        } else {          tooltip.arrow.addClass('ng-arrow-left');          tooltip.callout.css({ +          position: 'fixed',            left: (elementRect.right + arrowWidth) + "px",            top: (elementRect.top - 3) + "px",            width: width + "px" diff --git a/src/widgets.js b/src/widgets.js index f87c1d02..f5f02813 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -27,9 +27,11 @@ function valueAccessor(scope, element) {    required = required || required === '';    if (!validator) throw "Validator named '" + validatorName + "' not found.";    function validate(value) { -    var error = required && !trim(value) ? -            "Required" : -             validator({state:scope, scope:{get:scope.$get, set:scope.$set}}, value); +    var error, +        validateScope = extend(new (extend(function(){}, {prototype:scope}))(), {$element:element}); +    error = required && !trim(value) ? +          "Required" : +           validator({state:validateScope, scope:{get:validateScope.$get, set:validateScope.$set}}, value);      if (error !== lastError) {        elementError(element, NG_VALIDATION_ERROR, error);        lastError = error; @@ -190,7 +192,8 @@ angularWidget('NG:INCLUDE', function(element){  angularWidget('NG:SWITCH', function ngSwitch(element){    var compiler = this,        watchExpr = element.attr("on"), -      whenFn = ngSwitch[element.attr("using") || 'equals']; +      whenExpr = (element.attr("using") || 'equals').split(":"); +      whenFn = ngSwitch[whenExpr.shift()];        changeExpr = element.attr('change') || '',        cases = [];    if (!whenFn) throw "Using expression '" + usingExpr + "' unknown."; @@ -199,7 +202,11 @@ angularWidget('NG:SWITCH', function ngSwitch(element){      if (when) {        cases.push({          when: function(scope, value){ -          return whenFn.call(scope, value, when); +          var args = [value, when]; +          foreach(whenExpr, function(arg){ +            args.push(arg); +          }); +          return whenFn.apply(scope, args);          },          change: changeExpr,          element: caseElement, @@ -212,13 +219,10 @@ angularWidget('NG:SWITCH', function ngSwitch(element){      var scope = this, childScope;      this.$watch(watchExpr, function(value){        element.html(''); -      childScope = null; -      var params = {}; +      childScope = createScope(scope);        foreach(cases, function(switchCase){ -        if (switchCase.when(params, value)) { +        if (switchCase.when(childScope, value)) {            element.append(switchCase.element); -          childScope = createScope(scope); -          extend(childScope, params);            childScope.$tryEval(switchCase.change, element);            switchCase.template(switchCase.element, childScope);            childScope.$init(); @@ -233,13 +237,15 @@ angularWidget('NG:SWITCH', function ngSwitch(element){    equals: function(on, when) {      return on == when;    }, -  route: function(on, when) { -    var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$', params = [], self = this; +  route: function(on, when, dstName) { +    var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$', +        params = [], +        dst = {};      foreach(when.split(/\W/), function(param){        if (param) {          var paramRegExp = new RegExp(":" + param + "([\\W])");          if (regex.match(paramRegExp)) { -          regex = regex.replace(paramRegExp, "(.*)$1"); +          regex = regex.replace(paramRegExp, "([^\/]*)$1");            params.push(param);          }        } @@ -247,8 +253,9 @@ angularWidget('NG:SWITCH', function ngSwitch(element){      var match = on.match(new RegExp(regex));      if (match) {        foreach(params, function(name, index){ -        self[name] = match[index + 1]; +        dst[name] = match[index + 1];        }); +      if (dstName) this.$set(dstName, dst);      }      return match;    } diff --git a/test/ApiTest.js b/test/ApiTest.js index 19860822..5d85987b 100644 --- a/test/ApiTest.js +++ b/test/ApiTest.js @@ -250,3 +250,7 @@ ApiTest.prototype.testStringFromUTC = function(){    assertEquals("2003-09-10T13:02:03Z", angular.Date.toString(date));    assertEquals("str", angular.String.toDate("str"));  }; + +ApiTest.prototype.testObjectShouldHaveExtend = function(){ +  assertEquals(angular.Object.extend, extend); +}; diff --git a/test/ValidatorsTest.js b/test/ValidatorsTest.js index 2b2f6753..17c67d38 100644 --- a/test/ValidatorsTest.js +++ b/test/ValidatorsTest.js @@ -12,7 +12,7 @@ ValidatorTest.prototype.testItShouldHaveThisSet = function() {    scope.$init();    assertEquals('misko', validator.first);    assertEquals('hevery', validator.last); -  assertSame(scope, validator._this); +  assertSame(scope, validator._this.__proto__);    delete angular.validator.myValidator;    scope.$element.remove();  }; diff --git a/test/markupSpec.js b/test/markupSpec.js index e416b8ea..a1112490 100644 --- a/test/markupSpec.js +++ b/test/markupSpec.js @@ -47,6 +47,11 @@ describe("markups", function(){      expect(element.html()).toEqual('<option value="A">A</option>');    }); +  it('should process all bindings when we have leading space', function(){ +    compile('<a> {{a}}<br/>{{b}}</a>'); +    expect(sortedHtml(scope.$element)).toEqual('<a> <span ng-bind="a"></span><br></br><span ng-bind="b"></span></a>'); +  }); +  }); diff --git a/test/servicesSpec.js b/test/servicesSpec.js index b7dfe4c8..91cc1f0e 100644 --- a/test/servicesSpec.js +++ b/test/servicesSpec.js @@ -42,6 +42,20 @@ describe("services", function(){      expect(scope.$location.toString()).toEqual('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html#');    }); +  it('should update url on hash change', function(){ +    scope.$location.parse('http://server/#path?a=b'); +    scope.$location.hash = ''; +    expect(scope.$location.toString()).toEqual('http://server/#'); +    expect(scope.$location.hashPath).toEqual(''); +  }); + +  it('should update url on hashPath change', function(){ +    scope.$location.parse('http://server/#path?a=b'); +    scope.$location.hashPath = ''; +    expect(scope.$location.toString()).toEqual('http://server/#?a=b'); +    expect(scope.$location.hash).toEqual('?a=b'); +  }); +    xit('should add stylesheets', function(){      scope.$document = {        getElementsByTagName: function(name){ diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index b48656f9..c6158c37 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -207,13 +207,18 @@ describe("input widget", function(){  describe('ng:switch', function(){    it("should match urls", function(){ -    var scope = compile('<ng:switch on="url" using="route"><div ng-switch-when="/Book/:name">{{name}}</div></ng:include>'); +    var scope = compile('<ng:switch on="url" using="route:params"><div ng-switch-when="/Book/:name">{{params.name}}</div></ng:include>');      scope.url = '/Book/Moby';      scope.$init(); -//    jstestdriver.console.log('text');      expect(scope.$element.text()).toEqual('Moby');    }); +  it("should match sandwich ids", function(){ +    var scope = {}; +    var match = angular.widget['NG:SWITCH'].route.call(scope, '/a/123/b', '/a/:id'); +    expect(match).toBeFalsy(); +  }); +    it('should call init on switch', function(){      var scope = compile('<ng:switch on="url" change="name=\'works\'"><div ng-switch-when="a">{{name}}</div></ng:include>');      scope.url = 'a'; | 
