Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| de6d4ca1da | |||
| 776dfc678b | |||
| 9532234bf1 | |||
| 5f5d4feadb | |||
| 791804bdbf | |||
| 7eafbb98c6 | |||
| bb8448c011 | |||
| 2ed53087d7 | |||
| 0af172040e | |||
| e19b04c9ec | |||
| 37e8b12265 | |||
| 1ace5eb396 | |||
| 3c2aee01b0 | |||
| 37bdcc984a | |||
| 027f20be1f | |||
| 9b7c1d0f7c | |||
| 5548328b67 | |||
| 7c6b1e06e8 | |||
| 288b69a314 | |||
| 2a2123441c | |||
| 1d7a95df56 | |||
| 1ed638582d | |||
| 3b14092135 | |||
| ef268196b9 | |||
| 12ba6cec4f | |||
| b7e1fb0515 | |||
| 755beb2b66 | |||
| 6d70ff5c8d | |||
| ace54ff08c | |||
| f5835963d5 | |||
| f3231b9447 | |||
| a8a3efb5f0 | |||
| e47f8d2b96 | |||
| dba6bc73e8 | |||
| c0a0781425 | |||
| 035e0130f3 | |||
| 9e57ce0c7a | |||
| 42a5033c56 | |||
| 6b19e7d527 | |||
| 28273b7f1e | |||
| ec54712ff3 | |||
| 23dd78f8a4 | |||
| d46fe3c23f | |||
| 92ca7efaa4 | |||
| 7090924515 | |||
| df744f3af4 | |||
| 8155c3a29e | |||
| b001c8ece5 | |||
| bec4435945 | |||
| 6fb1054ce6 | |||
| a83eced974 | |||
| 7cc4063303 | |||
| d8e242418d | |||
| 6518369f25 | |||
| 649b892205 | |||
| e0295cfec4 | |||
| 17fc6a70fe | |||
| 5d0f9ce4c7 | |||
| 250aec71f3 | |||
| 3b317c5dcb | |||
| e4cfb9d938 | |||
| 87ba8221ec | |||
| e34519e93b | |||
| 69be39fccf | |||
| deac80a6e8 | |||
| 0539611bac | |||
| d2177ae312 | |||
| f3bff27460 | |||
| 4df45b20d4 |
+122
-2
@@ -1,3 +1,123 @@
|
||||
<a name="1.1.3"></a>
|
||||
# 1.1.3 radioactive-gargle (2013-02-20)
|
||||
|
||||
_Note: 1.1.x releases are [considered unstable](http://blog.angularjs.org/2012/07/angularjs-10-12-roadmap.html).
|
||||
They pass all tests but we reserve the right to change new features/apis in between minor releases. Check them
|
||||
out and please give us feedback._
|
||||
|
||||
_Note: This release also contains all bug fixes available in [1.0.5](#1.0.5)._
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- initialize interpolated attributes before directive linking
|
||||
([bb8448c0](https://github.com/angular/angular.js/commit/bb8448c011127306df08c7479b66e5afe7a0fa94))
|
||||
- interpolate @ locals before the link function runs
|
||||
([2ed53087](https://github.com/angular/angular.js/commit/2ed53087d7dd06d728e333a449265f7685275548))
|
||||
- **$http:**
|
||||
- do not encode special characters `@$:,` in params
|
||||
([288b69a3](https://github.com/angular/angular.js/commit/288b69a314e9bd14458b6647532eb62aad5c5cdf))
|
||||
- **$resource:**
|
||||
- params should expand array values properly
|
||||
([2a212344](https://github.com/angular/angular.js/commit/2a2123441c2b749b8f316a24c3ca3f77a9132a01))
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$http:** allow overriding the XSRF header and cookie name
|
||||
([8155c3a2](https://github.com/angular/angular.js/commit/8155c3a29ea0eb14806913b8ac08ba7727e1969c))
|
||||
- **$parse:** added `constant` and `literal` properties
|
||||
([1ed63858](https://github.com/angular/angular.js/commit/1ed638582d2f2c7f89384d9712f4cfac52cc5b70))
|
||||
- **$resource:** expose promise based api via $then and $resolved
|
||||
([dba6bc73](https://github.com/angular/angular.js/commit/dba6bc73e802fdae685a9f351d3e23c7efa8568a))
|
||||
- **$routeProvider:** add support to catch-all parameters in routes
|
||||
([7eafbb98](https://github.com/angular/angular.js/commit/7eafbb98c64c0dc079d7d3ec589f1270b7f6fea5))
|
||||
- **Scope:**
|
||||
- expose transcluded and isolate scope info for batarang
|
||||
([649b8922](https://github.com/angular/angular.js/commit/649b892205615a144dafff9984c0e6ab10ed341d))
|
||||
- only evaluate constant $watch expressions once
|
||||
([1d7a95df](https://github.com/angular/angular.js/commit/1d7a95df565192fc02a18b0b297b39dd615eaeb5))
|
||||
- **angular.noConflict:** added api to restore previous angular namespace reference
|
||||
([12ba6cec](https://github.com/angular/angular.js/commit/12ba6cec4fb79521101744e02a7e09f9fbb591c4))
|
||||
- **Directives:**
|
||||
- **ngSwitch:** support multiple matches on ngSwitchWhen and ngSwitchDefault
|
||||
([0af17204](https://github.com/angular/angular.js/commit/0af172040e03811c59d01682968241e3df226774),
|
||||
[#1074](https://github.com/angular/angular.js/issues/1074))
|
||||
- **Filters:**
|
||||
- **date:** add `[.,]sss` formatter for milliseconds
|
||||
([df744f3a](https://github.com/angular/angular.js/commit/df744f3af46fc227a934f16cb63c7a6038e7133b))
|
||||
- **filter:** add comparison function to filter
|
||||
([ace54ff0](https://github.com/angular/angular.js/commit/ace54ff08c4593195b49eadb04d258e6409d969e))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$http:** due to [288b69a3](https://github.com/angular/angular.js/commit/288b69a314e9bd14458b6647532eb62aad5c5cdf),
|
||||
$http now follows RFC3986 and does not encode special characters like `$@,:` in params.
|
||||
If your application needs to encode these characters, encode them manually, before sending the request.
|
||||
- **$resource:** due to [2a212344](https://github.com/angular/angular.js/commit/2a2123441c2b749b8f316a24c3ca3f77a9132a01),
|
||||
if the server relied on the buggy behavior of serializing arrays as http query arguments then
|
||||
either the backend should be fixed or a simple serialization of the array should be done
|
||||
on the client before calling the resource service.
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.0.5"></a>
|
||||
# 1.0.5 flatulent-propulsion (2013-02-20)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- sanitize values bound to a[href]
|
||||
([9532234b](https://github.com/angular/angular.js/commit/9532234bf1c408af9a6fd2c4743fdb585b920531))
|
||||
- rename $compileNote to compileNode
|
||||
([92ca7efa](https://github.com/angular/angular.js/commit/92ca7efaa4bc4f37da3008b234e19343a1fa4207),
|
||||
[#1941](https://github.com/angular/angular.js/issues/1941))
|
||||
- should not leak memory when there are top level empty text nodes
|
||||
([791804bd](https://github.com/angular/angular.js/commit/791804bdbfa6da7a39283623bd05628a01cd8720))
|
||||
- allow startingTag method to handle text / comment nodes
|
||||
([755beb2b](https://github.com/angular/angular.js/commit/755beb2b66ce9f9f9a218f2355bbaf96d94fbc15))
|
||||
- **$cookies:** set cookies on Safari&IE when base[href] is undefined
|
||||
([70909245](https://github.com/angular/angular.js/commit/7090924515214752b919b0c5630b3ea5e7c77223),
|
||||
[#1190](https://github.com/angular/angular.js/issues/1190))
|
||||
- **$http:**
|
||||
- patch for Firefox bug w/ CORS and response headers
|
||||
([e19b04c9](https://github.com/angular/angular.js/commit/e19b04c9ec985821edf1269c628cfa261f81d631),
|
||||
[#1468](https://github.com/angular/angular.js/issues/1468))
|
||||
- **$resource:**
|
||||
- update RegExp to allow urlParams with out leading slash
|
||||
([b7e1fb05](https://github.com/angular/angular.js/commit/b7e1fb0515798e1b4f3f2426f6b050951bee2617))
|
||||
- **Directives:**
|
||||
- **a:** workaround IE bug affecting mailto urls
|
||||
([37e8b122](https://github.com/angular/angular.js/commit/37e8b12265291918396bfee65d444a8f63697b73),
|
||||
[#1949](https://github.com/angular/angular.js/issues/1949))
|
||||
- **ngClass:** keep track of old ngClass value manually
|
||||
([5f5d4fea](https://github.com/angular/angular.js/commit/5f5d4feadbfa9d8ecc8150041dfd2bca2b2e9fea),
|
||||
[#1637](https://github.com/angular/angular.js/issues/1637))
|
||||
- **ngSwitch:** make ngSwitch compatible with controller backwards-compatiblity module
|
||||
([9b7c1d0f](https://github.com/angular/angular.js/commit/9b7c1d0f7ce442d4ad2ec587e66d2d335e64fa4e))
|
||||
- **Filters:**
|
||||
- **date:** invert timezone sign and always display sign
|
||||
([b001c8ec](https://github.com/angular/angular.js/commit/b001c8ece5472626bf49cf82753e8ac1aafd2513),
|
||||
[#1261](https://github.com/angular/angular.js/issues/1261))
|
||||
- **number:** fix formatting when "0" passed as fractionSize
|
||||
([f5835963](https://github.com/angular/angular.js/commit/f5835963d5982003a713dd354eefd376ed39ac02))
|
||||
- **scenario runner:** include error messages in XML output
|
||||
([d46fe3c2](https://github.com/angular/angular.js/commit/d46fe3c23fa269dcc10249148f2af14f3db6b066))
|
||||
- **Misc:**
|
||||
- don't use instanceof to detect arrays
|
||||
([3c2aee01](https://github.com/angular/angular.js/commit/3c2aee01b0b299995eb92f4255159585b0f53c10),
|
||||
[#1966](https://github.com/angular/angular.js/issues/1966))
|
||||
- angular.forEach should correctly iterate over objects with length prop
|
||||
([ec54712f](https://github.com/angular/angular.js/commit/ec54712ff3dab1ade44f94fa82d67edeffa79a1d),
|
||||
[#1840](https://github.com/angular/angular.js/issues/1840))
|
||||
|
||||
|
||||
|
||||
<a name="1.1.2"></a>
|
||||
# 1.1.2 tofu-animation (2013-01-22)
|
||||
|
||||
@@ -73,6 +193,8 @@ _Note: This release also contains all bug fixes available in [1.0.4](#1.0.4)._
|
||||
- HTTP method should be case-insensitive
|
||||
([8991680d](https://github.com/angular/angular.js/commit/8991680d8ab632dda60cd70c780868c803c74509),
|
||||
[#1403](https://github.com/angular/angular.js/issues/1403))
|
||||
- correct leading slash removal in resource URLs
|
||||
([b2f46251](https://github.com/angular/angular.js/commit/b2f46251aca76c8568ee7d4bab54edbc9d7a186a))
|
||||
- **$route:**
|
||||
- support route params not separated with slashes.
|
||||
([c6392616](https://github.com/angular/angular.js/commit/c6392616ea5245bd0d2f77dded0b948d9e2637c8))
|
||||
@@ -100,8 +222,6 @@ _Note: This release also contains all bug fixes available in [1.0.4](#1.0.4)._
|
||||
- **ngRepeat:** correctly apply $last if repeating over object
|
||||
([7e746015](https://github.com/angular/angular.js/commit/7e746015ea7dec3e9eb81bc4678fa9b6a83bc47c),
|
||||
[#1789](https://github.com/angular/angular.js/issues/1789))
|
||||
- **ngResource:** correct leading slash removal.
|
||||
([b2f46251](https://github.com/angular/angular.js/commit/b2f46251aca76c8568ee7d4bab54edbc9d7a186a))
|
||||
- **ngSwitch:** don't leak when destroyed while not attached
|
||||
([a26234f7](https://github.com/angular/angular.js/commit/a26234f7183013e2fcc9b35377e181ad96dc9917),
|
||||
[#1621](https://github.com/angular/angular.js/issues/1621))
|
||||
|
||||
@@ -26,10 +26,6 @@ Building AngularJS
|
||||
|
||||
Running Tests
|
||||
-------------
|
||||
Running tests requires installation of [Testacular](http://vojtajina.github.com/testacular):
|
||||
|
||||
sudo npm install -g testacular
|
||||
|
||||
To execute all unit tests, use:
|
||||
|
||||
rake test:unit
|
||||
|
||||
@@ -21,6 +21,8 @@ task :default => [:package]
|
||||
|
||||
desc 'Init the build workspace'
|
||||
task :init do
|
||||
%x(npm install)
|
||||
|
||||
FileUtils.mkdir(BUILD_DIR) unless File.directory?(BUILD_DIR)
|
||||
|
||||
v = YAML::load( File.open( 'version.yaml' ) )
|
||||
@@ -105,11 +107,7 @@ task :minify => [:init, :concat, :concat_scenario] do
|
||||
'angular-bootstrap.js',
|
||||
'angular-bootstrap-prettify.js'
|
||||
].each do |file|
|
||||
unless ENV['TRAVIS']
|
||||
fork { closure_compile(file) }
|
||||
else
|
||||
closure_compile(file)
|
||||
end
|
||||
fork { closure_compile(file) }
|
||||
end
|
||||
|
||||
Process.waitall
|
||||
@@ -347,6 +345,8 @@ end
|
||||
|
||||
|
||||
def start_testacular(config, singleRun, browsers, misc_options)
|
||||
Rake::Task[:init].invoke
|
||||
|
||||
sh "./node_modules/testacular/bin/testacular start " +
|
||||
"#{config} " +
|
||||
"#{'--single-run=true' if singleRun} " +
|
||||
|
||||
@@ -234,7 +234,7 @@ The separation of the controller and the view is important because:
|
||||
|
||||
The model is the data which is used merged with the template to produce the view. To be able to
|
||||
render the model into the view, the model has to be able to be referenced from the scope. Unlike many
|
||||
other frameworks Angular makes no restrictions or requirements an the model. There are no classes
|
||||
other frameworks Angular makes no restrictions or requirements on the model. There are no classes
|
||||
to inherit from or special accessor methods for accessing or changing the model. The model can be
|
||||
primitive, object hash, or a full object Type. In short the model is a plain JavaScript object.
|
||||
|
||||
@@ -248,9 +248,9 @@ primitive, object hash, or a full object Type. In short the model is a plain Jav
|
||||
|
||||
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-view.png">
|
||||
|
||||
The view is what the users sees. The view begins its life as a template, it is merged with the
|
||||
The view is what the user sees. The view begins its life as a template, is merged with the
|
||||
model and finally rendered into the browser DOM. Angular takes a very different approach to
|
||||
rendering the view, compared to most other templating systems.
|
||||
rendering the view compared to most other templating systems.
|
||||
|
||||
* **Others** - Most templating systems begin as an HTML string with special templating markup.
|
||||
Often the template markup breaks the HTML syntax which means that the template can not be
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
@name Developer Guide: Templates: Understanding Angular Filters
|
||||
@description
|
||||
|
||||
Angular filters format data for display to the user. In addition to formatting data, filters can
|
||||
also modify the DOM. This allows filters to handle tasks such as conditionally applying CSS styles
|
||||
to filtered output.
|
||||
Angular filters format data for display to the user.
|
||||
|
||||
For example, you might have a data object that needs to be formatted according to the locale before
|
||||
displaying it to the user. You can pass expressions through a chain of filters like this:
|
||||
|
||||
@@ -95,7 +95,7 @@ Compilation of HTML happens in three phases:
|
||||
var $compile = ...; // injected into your code
|
||||
var scope = ...;
|
||||
|
||||
var html = '<div ng-bind='exp'></div>';
|
||||
var html = '<div ng-bind="exp"></div>';
|
||||
|
||||
// Step 1: parse HTML into DOM element
|
||||
var template = angular.element(html);
|
||||
|
||||
@@ -158,9 +158,9 @@ angular.module('myModule', []).
|
||||
|
||||
angular.module('myModule', []).
|
||||
config(function($provide, $compileProvider, $filterProvider) {
|
||||
$provide.value('a', 123)
|
||||
$provide.factory('a', function() { return 123; })
|
||||
$compileProvider.directive('directiveName', ...).
|
||||
$provide.value('a', 123);
|
||||
$provide.factory('a', function() { return 123; });
|
||||
$compileProvider.directive('directiveName', ...);
|
||||
$filterProvider.register('filterName', ...);
|
||||
});
|
||||
</pre>
|
||||
|
||||
@@ -222,7 +222,8 @@ To run the E2E test suite:
|
||||
|
||||
To create and submit a change:
|
||||
|
||||
1. Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code changes to be
|
||||
1. <a name="CLA"></a>
|
||||
Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code changes to be
|
||||
accepted, the CLA must be signed. It's a quick process, we promise!
|
||||
|
||||
For individuals we have a [simple click-through form](http://code.google.com/legal/individual-cla-v1.0.html). For
|
||||
|
||||
@@ -109,7 +109,7 @@ __`app/index.html`:__
|
||||
|
||||
<html ng-app>
|
||||
|
||||
The `ng-app` attribute is represents an Angular directive (named `ngApp`; Angular uses
|
||||
The `ng-app` attribute represents an Angular directive (named `ngApp`; Angular uses
|
||||
`name-with-dashes` for attribute names and `camelCase` for the corresponding directive name)
|
||||
used to flag an element which Angular should consider to be the root element of our application.
|
||||
This gives application developers the freedom to tell Angular if the entire html page or only a
|
||||
@@ -127,7 +127,7 @@ being the element on which the `ngApp` directive was defined.
|
||||
|
||||
* Double-curly binding with an expression:
|
||||
|
||||
Nothing here {{'yet' + '!'}}`
|
||||
Nothing here {{'yet' + '!'}}
|
||||
|
||||
This line demonstrates the core feature of Angular's templating capabilities – a binding, denoted
|
||||
by double-curlies `{{ }}` as well as a simple expression `'yet' + '!'` used in this binding.
|
||||
|
||||
+23
-5
@@ -55,12 +55,15 @@ describe('ngdoc', function() {
|
||||
'@name a\n' +
|
||||
'@param {*} a short\n' +
|
||||
'@param {Type} b med\n' +
|
||||
'@param {Class=} [c=2] long\nline');
|
||||
'@param {Class=} [c=2] long\nline\n' +
|
||||
'@param {function(number, string=)} d fn with optional arguments');
|
||||
doc.parse();
|
||||
expect(doc.param).toEqual([
|
||||
{name:'a', description:'<p>short</p>', type:'*', optional:false, 'default':undefined},
|
||||
{name:'b', description:'<p>med</p>', type:'Type', optional:false, 'default':undefined},
|
||||
{name:'c', description:'<p>long\nline</p>', type:'Class', optional:true, 'default':'2'}
|
||||
{name:'c', description:'<p>long\nline</p>', type:'Class', optional:true, 'default':'2'},
|
||||
{name:'d', description:'<p>fn with optional arguments</p>',
|
||||
type: 'function(number, string=)', optional: false, 'default':undefined}
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -318,9 +321,9 @@ describe('ngdoc', function() {
|
||||
});
|
||||
|
||||
it('should not parse @property without a type', function() {
|
||||
var doc = new Doc("@property fake");
|
||||
var doc = new Doc("@property fake", 'test.js', '44');
|
||||
expect(function() { doc.parse(); }).
|
||||
toThrow(new Error("Not a valid 'property' format: fake"));
|
||||
toThrow(new Error("Not a valid 'property' format: fake (found in: test.js:44)"));
|
||||
});
|
||||
|
||||
it('should parse @property with type', function() {
|
||||
@@ -350,15 +353,30 @@ describe('ngdoc', function() {
|
||||
describe('@returns', function() {
|
||||
it('should not parse @returns without type', function() {
|
||||
var doc = new Doc("@returns lala");
|
||||
expect(doc.parse).toThrow();
|
||||
expect(function() { doc.parse(); }).
|
||||
toThrow();
|
||||
});
|
||||
|
||||
|
||||
it('should not parse @returns with invalid type', function() {
|
||||
var doc = new Doc("@returns {xx}x} lala", 'test.js', 34);
|
||||
expect(function() { doc.parse(); }).
|
||||
toThrow(new Error("Not a valid 'returns' format: {xx}x} lala (found in: test.js:34)"));
|
||||
});
|
||||
|
||||
|
||||
it('should parse @returns with type and description', function() {
|
||||
var doc = new Doc("@name a\n@returns {string} descrip tion");
|
||||
doc.parse();
|
||||
expect(doc.returns).toEqual({type: 'string', description: '<p>descrip tion</p>'});
|
||||
});
|
||||
|
||||
it('should parse @returns with complex type and description', function() {
|
||||
var doc = new Doc("@name a\n@returns {function(string, number=)} description");
|
||||
doc.parse();
|
||||
expect(doc.returns).toEqual({type: 'function(string, number=)', description: '<p>description</p>'});
|
||||
});
|
||||
|
||||
it('should transform description of @returns with markdown', function() {
|
||||
var doc = new Doc("@name a\n@returns {string} descrip *tion*");
|
||||
doc.parse();
|
||||
|
||||
+56
-41
@@ -214,23 +214,25 @@ Doc.prototype = {
|
||||
if (atName) {
|
||||
var text = trim(atText.join('\n')), match;
|
||||
if (atName == 'param') {
|
||||
match = text.match(/^\{([^}=]+)(=)?\}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
|
||||
// 1 12 2 34 4 5 5 6 6 3 7 7
|
||||
match = text.match(/^\{([^}]+)\}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
|
||||
// 1 1 23 3 4 4 5 5 2 6 6
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'param' format: " + text);
|
||||
throw new Error("Not a valid 'param' format: " + text + ' (found in: ' + self.file + ':' + self.line + ')');
|
||||
}
|
||||
|
||||
var optional = (match[1].slice(-1) === '=');
|
||||
var param = {
|
||||
name: match[5] || match[4],
|
||||
description:self.markdown(text.replace(match[0], match[7])),
|
||||
type: match[1],
|
||||
optional: !!match[2],
|
||||
'default':match[6]
|
||||
name: match[4] || match[3],
|
||||
description:self.markdown(text.replace(match[0], match[6])),
|
||||
type: optional ? match[1].substring(0, match[1].length-1) : match[1],
|
||||
optional: optional,
|
||||
'default':match[5]
|
||||
};
|
||||
self.param.push(param);
|
||||
} else if (atName == 'returns' || atName == 'return') {
|
||||
match = text.match(/^\{([^}=]+)\}\s+(.*)/);
|
||||
match = text.match(/^\{([^}]+)\}\s+(.*)/);
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'returns' format: " + text + ' in ' + self.file + ':' + self.line);
|
||||
throw new Error("Not a valid 'returns' format: " + text + ' (found in: ' + self.file + ':' + self.line + ')');
|
||||
}
|
||||
self.returns = {
|
||||
type: match[1],
|
||||
@@ -245,7 +247,7 @@ Doc.prototype = {
|
||||
} else if(atName == 'property') {
|
||||
match = text.match(/^\{(\S+)\}\s+(\S+)(\s+(.*))?/);
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'property' format: " + text);
|
||||
throw new Error("Not a valid 'property' format: " + text + ' (found in: ' + self.file + ':' + self.line + ')');
|
||||
}
|
||||
var property = new Doc({
|
||||
type: match[1],
|
||||
@@ -383,40 +385,53 @@ Doc.prototype = {
|
||||
var self = this;
|
||||
dom.h('Usage', function() {
|
||||
var restrict = self.restrict || 'AC';
|
||||
|
||||
if (restrict.match(/E/)) {
|
||||
dom.text('as element (see ');
|
||||
dom.text('This directive can be used as custom element, but we aware of ');
|
||||
dom.tag('a', {href:'guide/ie'}, 'IE restrictions');
|
||||
dom.text(')');
|
||||
dom.code(function() {
|
||||
dom.text('<');
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams('\n ', '="', '"');
|
||||
dom.text('>\n</');
|
||||
dom.text(dashCase(self.shortName));
|
||||
dom.text('>');
|
||||
});
|
||||
dom.text('.');
|
||||
}
|
||||
if (restrict.match(/A/)) {
|
||||
var element = self.element || 'ANY';
|
||||
dom.text('as attribute');
|
||||
dom.code(function() {
|
||||
dom.text('<' + element + ' ');
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams('\n ', '="', '"', true);
|
||||
dom.text('>\n ...\n');
|
||||
dom.text('</' + element + '>');
|
||||
});
|
||||
}
|
||||
if (restrict.match(/C/)) {
|
||||
dom.text('as class');
|
||||
var element = self.element || 'ANY';
|
||||
dom.code(function() {
|
||||
dom.text('<' + element + ' class="');
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams(' ', ': ', ';', true);
|
||||
dom.text('">\n ...\n');
|
||||
dom.text('</' + element + '>');
|
||||
|
||||
if (self.usage) {
|
||||
dom.tag('pre', function() {
|
||||
dom.tag('code', function() {
|
||||
dom.text(self.usage);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
if (restrict.match(/E/)) {
|
||||
dom.text('as element:');
|
||||
dom.code(function() {
|
||||
dom.text('<');
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams('\n ', '="', '"');
|
||||
dom.text('>\n</');
|
||||
dom.text(dashCase(self.shortName));
|
||||
dom.text('>');
|
||||
});
|
||||
}
|
||||
if (restrict.match(/A/)) {
|
||||
var element = self.element || 'ANY';
|
||||
dom.text('as attribute');
|
||||
dom.code(function() {
|
||||
dom.text('<' + element + ' ');
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams('\n ', '="', '"', true);
|
||||
dom.text('>\n ...\n');
|
||||
dom.text('</' + element + '>');
|
||||
});
|
||||
}
|
||||
if (restrict.match(/C/)) {
|
||||
dom.text('as class');
|
||||
var element = self.element || 'ANY';
|
||||
dom.code(function() {
|
||||
dom.text('<' + element + ' class="');
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams(' ', ': ', ';', true);
|
||||
dom.text('">\n ...\n');
|
||||
dom.text('</' + element + '>');
|
||||
});
|
||||
}
|
||||
}
|
||||
self.html_usage_directiveInfo(dom);
|
||||
self.html_usage_parameters(dom);
|
||||
|
||||
+4
-4
@@ -2,9 +2,9 @@
|
||||
"name": "AngularJS",
|
||||
"version": "0.0.0",
|
||||
"dependencies" : {
|
||||
"testacular" : "canary",
|
||||
"jasmine-node" : "*",
|
||||
"q-fs" : "*",
|
||||
"qq" : "*"
|
||||
"testacular" : "0.5.9",
|
||||
"jasmine-node" : "1.2.3",
|
||||
"q-fs" : "0.1.36",
|
||||
"qq" : "0.3.5"
|
||||
}
|
||||
}
|
||||
|
||||
+61
-11
@@ -49,8 +49,7 @@ if ('i' !== 'I'.toLowerCase()) {
|
||||
function fromCharCode(code) {return String.fromCharCode(code);}
|
||||
|
||||
|
||||
var Error = window.Error,
|
||||
/** holds major version number for IE or NaN for real browsers */
|
||||
var /** holds major version number for IE or NaN for real browsers */
|
||||
msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]),
|
||||
jqLite, // delay binding since jQuery could be loaded after us.
|
||||
jQuery, // delay binding
|
||||
@@ -58,12 +57,32 @@ var Error = window.Error,
|
||||
push = [].push,
|
||||
toString = Object.prototype.toString,
|
||||
|
||||
|
||||
_angular = window.angular,
|
||||
/** @name angular */
|
||||
angular = window.angular || (window.angular = {}),
|
||||
angularModule,
|
||||
nodeName_,
|
||||
uid = ['0', '0', '0'];
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.noConflict
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Restores the previous global value of angular and returns the current instance. Other libraries may already use the
|
||||
* angular namespace. Or a previous version of angular is already loaded on the page. In these cases you may want to
|
||||
* restore the previous namespace and keep a reference to angular.
|
||||
*
|
||||
* @return {Object} The current angular namespace
|
||||
*/
|
||||
function noConflict() {
|
||||
var a = window.angular;
|
||||
window.angular = _angular;
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.forEach
|
||||
@@ -91,6 +110,30 @@ var Error = window.Error,
|
||||
* @param {Object=} context Object to become context (`this`) for the iterator function.
|
||||
* @returns {Object|Array} Reference to `obj`.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {*} obj
|
||||
* @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...)
|
||||
*/
|
||||
function isArrayLike(obj) {
|
||||
if (!obj || (typeof obj.length !== 'number')) return false;
|
||||
|
||||
// We have on object which has length property. Should we treat it as array?
|
||||
if (typeof obj.hasOwnProperty != 'function' &&
|
||||
typeof obj.constructor != 'function') {
|
||||
// This is here for IE8: it is a bogus object treat it as array;
|
||||
return true;
|
||||
} else {
|
||||
return obj instanceof JQLite || // JQLite
|
||||
(jQuery && obj instanceof jQuery) || // jQuery
|
||||
toString.call(obj) !== '[object Object]' || // some browser native object
|
||||
typeof obj.callee === 'function'; // arguments (on IE8 looks like regular obj)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function forEach(obj, iterator, context) {
|
||||
var key;
|
||||
if (obj) {
|
||||
@@ -102,7 +145,7 @@ function forEach(obj, iterator, context) {
|
||||
}
|
||||
} else if (obj.forEach && obj.forEach !== forEach) {
|
||||
obj.forEach(iterator, context);
|
||||
} else if (isObject(obj) && isNumber(obj.length)) {
|
||||
} else if (isArrayLike(obj)) {
|
||||
for (key = 0; key < obj.length; key++)
|
||||
iterator.call(context, obj[key], key);
|
||||
} else {
|
||||
@@ -147,7 +190,7 @@ function reverseParams(iteratorFn) {
|
||||
/**
|
||||
* A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric
|
||||
* characters such as '012ABC'. The reason why we are not using simply a number counter is that
|
||||
* the number string gets longer over time, and it can also overflow, where as the the nextId
|
||||
* the number string gets longer over time, and it can also overflow, where as the nextId
|
||||
* will grow much slower, it is a string, and it will never overflow.
|
||||
*
|
||||
* @returns an unique alpha-numeric string
|
||||
@@ -543,9 +586,7 @@ function copy(source, destination){
|
||||
} else {
|
||||
if (source === destination) throw Error("Can't copy equivalent objects or arrays");
|
||||
if (isArray(source)) {
|
||||
while(destination.length) {
|
||||
destination.pop();
|
||||
}
|
||||
destination.length = 0;
|
||||
for ( var i = 0; i < source.length; i++) {
|
||||
destination.push(copy(source[i]));
|
||||
}
|
||||
@@ -756,9 +797,18 @@ function startingTag(element) {
|
||||
// are not allowed to have children. So we just ignore it.
|
||||
element.html('');
|
||||
} catch(e) {}
|
||||
return jqLite('<div>').append(element).html().
|
||||
match(/^(<[^>]+>)/)[1].
|
||||
replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
|
||||
// As Per DOM Standards
|
||||
var TEXT_NODE = 3;
|
||||
var elemHtml = jqLite('<div>').append(element).html();
|
||||
try {
|
||||
return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
|
||||
elemHtml.
|
||||
match(/^(<[^>]+>)/)[1].
|
||||
replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
|
||||
} catch(e) {
|
||||
return lowercase(elemHtml);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -842,7 +892,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
|
||||
* Use this directive to auto-bootstrap on application. Only
|
||||
* one directive can be used per HTML document. The directive
|
||||
* designates the root of the application and is typically placed
|
||||
* ot the root of the page.
|
||||
* at the root of the page.
|
||||
*
|
||||
* In the example below if the `ngApp` directive would not be placed
|
||||
* on the `html` element then the document would not be compiled
|
||||
|
||||
@@ -48,7 +48,8 @@ function publishExternalAPI(angular){
|
||||
'isDate': isDate,
|
||||
'lowercase': lowercase,
|
||||
'uppercase': uppercase,
|
||||
'callbacks': {counter: 0}
|
||||
'callbacks': {counter: 0},
|
||||
'noConflict': noConflict
|
||||
});
|
||||
|
||||
angularModule = setupModuleLoader(window);
|
||||
|
||||
@@ -192,7 +192,7 @@ function annotate(fn) {
|
||||
* This method does not work with code minfication / obfuscation. For this reason the following annotation strategies
|
||||
* are supported.
|
||||
*
|
||||
* # The `$injector` property
|
||||
* # The `$inject` property
|
||||
*
|
||||
* If a function has an `$inject` property and its value is an array of strings, then the strings represent names of
|
||||
* services to be injected into the function.
|
||||
|
||||
Vendored
+4
-4
@@ -4,10 +4,10 @@ var directive = {};
|
||||
var service = { value: {} };
|
||||
|
||||
var DEPENDENCIES = {
|
||||
'angular.js': 'http://code.angularjs.org/' + angular.version.full + 'angular.min.js',
|
||||
'angular-resource.js': 'http://code.angularjs.org/' + angular.version.full + 'angular-resource.min.js',
|
||||
'angular-sanitize.js': 'http://code.angularjs.org/' + angular.version.full + 'angular-sanitize.min.js',
|
||||
'angular-cookies.js': 'http://code.angularjs.org/' + angular.version.full + 'angular-cookies.min.js'
|
||||
'angular.js': 'http://code.angularjs.org/' + angular.version.full + '/angular.min.js',
|
||||
'angular-resource.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-resource.min.js',
|
||||
'angular-sanitize.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-sanitize.min.js',
|
||||
'angular-cookies.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-cookies.min.js'
|
||||
};
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -237,7 +237,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
*/
|
||||
self.baseHref = function() {
|
||||
var href = baseElement.attr('href');
|
||||
return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href;
|
||||
return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : '';
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
+69
-9
@@ -155,7 +155,8 @@ function $CompileProvider($provide) {
|
||||
Suffix = 'Directive',
|
||||
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
|
||||
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
|
||||
MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ';
|
||||
MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ',
|
||||
urlSanitizationWhitelist = /^\s*(https?|ftp|mailto):/;
|
||||
|
||||
|
||||
/**
|
||||
@@ -209,11 +210,41 @@ function $CompileProvider($provide) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name ng.$compileProvider#urlSanitizationWhitelist
|
||||
* @methodOf ng.$compileProvider
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
|
||||
* urls during a[href] sanitization.
|
||||
*
|
||||
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
|
||||
*
|
||||
* Any url about to be assigned to a[href] via data-binding is first normalized and turned into an
|
||||
* absolute url. Afterwards the url is matched against the `urlSanitizationWhitelist` regular
|
||||
* expression. If a match is found the original url is written into the dom. Otherwise the
|
||||
* absolute url is prefixed with `'unsafe:'` string and only then it is written into the DOM.
|
||||
*
|
||||
* @param {RegExp=} regexp New regexp to whitelist urls with.
|
||||
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
|
||||
* chaining otherwise.
|
||||
*/
|
||||
this.urlSanitizationWhitelist = function(regexp) {
|
||||
if (isDefined(regexp)) {
|
||||
urlSanitizationWhitelist = regexp;
|
||||
return this;
|
||||
}
|
||||
return urlSanitizationWhitelist;
|
||||
};
|
||||
|
||||
|
||||
this.$get = [
|
||||
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
|
||||
'$controller', '$rootScope',
|
||||
'$controller', '$rootScope', '$document',
|
||||
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
|
||||
$controller, $rootScope) {
|
||||
$controller, $rootScope, $document) {
|
||||
|
||||
var Attributes = function(element, attr) {
|
||||
this.$$element = element;
|
||||
@@ -235,7 +266,8 @@ function $CompileProvider($provide) {
|
||||
*/
|
||||
$set: function(key, value, writeAttr, attrName) {
|
||||
var booleanKey = getBooleanAttrName(this.$$element[0], key),
|
||||
$$observers = this.$$observers;
|
||||
$$observers = this.$$observers,
|
||||
normalizedVal;
|
||||
|
||||
if (booleanKey) {
|
||||
this.$$element.prop(key, value);
|
||||
@@ -254,6 +286,19 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sanitize a[href] values
|
||||
if (nodeName_(this.$$element[0]) === 'A' && key === 'href') {
|
||||
urlSanitizationNode.setAttribute('href', value);
|
||||
|
||||
// href property always returns normalized absolute url, so we can match against that
|
||||
normalizedVal = urlSanitizationNode.href;
|
||||
if (!normalizedVal.match(urlSanitizationWhitelist)) {
|
||||
this[key] = value = 'unsafe:' + normalizedVal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (writeAttr !== false) {
|
||||
if (value === null || value === undefined) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
@@ -297,7 +342,8 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
};
|
||||
|
||||
var startSymbol = $interpolate.startSymbol(),
|
||||
var urlSanitizationNode = $document[0].createElement('a'),
|
||||
startSymbol = $interpolate.startSymbol(),
|
||||
endSymbol = $interpolate.endSymbol(),
|
||||
denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
|
||||
? identity
|
||||
@@ -330,7 +376,14 @@ function $CompileProvider($provide) {
|
||||
var $linkNode = cloneConnectFn
|
||||
? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
|
||||
: $compileNodes;
|
||||
$linkNode.data('$scope', scope);
|
||||
|
||||
// Attach scope only to non-text nodes.
|
||||
for(var i = 0, ii = $linkNode.length; i<ii; i++) {
|
||||
var node = $linkNode[i];
|
||||
if (node.nodeType == 1 /* element */ || node.nodeType == 9 /* document */) {
|
||||
$linkNode.eq(i).data('$scope', scope);
|
||||
}
|
||||
}
|
||||
safeAddClass($linkNode, 'ng-scope');
|
||||
if (cloneConnectFn) cloneConnectFn($linkNode, scope);
|
||||
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
|
||||
@@ -420,6 +473,7 @@ function $CompileProvider($provide) {
|
||||
(function(transcludeFn) {
|
||||
return function(cloneFn) {
|
||||
var transcludeScope = scope.$new();
|
||||
transcludeScope.$$transcluded = true;
|
||||
|
||||
return transcludeFn(transcludeScope, cloneFn).
|
||||
bind('$destroy', bind(transcludeScope, transcludeScope.$destroy));
|
||||
@@ -725,6 +779,8 @@ function $CompileProvider($provide) {
|
||||
lastValue,
|
||||
parentGet, parentSet;
|
||||
|
||||
scope.$$isolateBindings[scopeName] = mode + attrName;
|
||||
|
||||
switch (mode) {
|
||||
|
||||
case '@': {
|
||||
@@ -732,6 +788,10 @@ function $CompileProvider($provide) {
|
||||
scope[scopeName] = value;
|
||||
});
|
||||
attrs.$$observers[attrName].$$scope = parentScope;
|
||||
if( attrs[attrName] ) {
|
||||
// If the attribute has been provided then we trigger an interpolation to ensure the value is there for use in the link fn
|
||||
scope[scopeName] = $interpolate(attrs[attrName])(parentScope);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -935,7 +995,7 @@ function $CompileProvider($provide) {
|
||||
}
|
||||
|
||||
directives.unshift(derivedSyncDirective);
|
||||
afterTemplateNodeLinkFn = applyDirectivesToNode(directives, $compileNode, tAttrs, childTranscludeFn);
|
||||
afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn);
|
||||
afterTemplateChildLinkFn = compileNodes($compileNode.contents(), childTranscludeFn);
|
||||
|
||||
|
||||
@@ -1015,10 +1075,10 @@ function $CompileProvider($provide) {
|
||||
function addAttrInterpolateDirective(node, directives, value, name) {
|
||||
var interpolateFn = $interpolate(value, true);
|
||||
|
||||
|
||||
// no interpolation found -> ignore
|
||||
if (!interpolateFn) return;
|
||||
|
||||
|
||||
directives.push({
|
||||
priority: 100,
|
||||
compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
|
||||
@@ -1030,7 +1090,7 @@ function $CompileProvider($provide) {
|
||||
interpolateFn = $interpolate(attr[name], true);
|
||||
}
|
||||
|
||||
attr[name] = undefined;
|
||||
attr[name] = interpolateFn(scope);
|
||||
($$observers[name] || ($$observers[name] = [])).$$inter = true;
|
||||
(attr.$$observers && attr.$$observers[name].$$scope || scope).
|
||||
$watch(interpolateFn, function interpolateFnWatchAction(value) {
|
||||
|
||||
+15
-5
@@ -11,15 +11,25 @@
|
||||
*
|
||||
* The reasoning for this change is to allow easy creation of action links with `ngClick` directive
|
||||
* without changing the location or causing page reloads, e.g.:
|
||||
* <a href="" ng-click="model.$save()">Save</a>
|
||||
* `<a href="" ng-click="model.$save()">Save</a>`
|
||||
*/
|
||||
var htmlAnchorDirective = valueFn({
|
||||
restrict: 'E',
|
||||
compile: function(element, attr) {
|
||||
// turn <a href ng-click="..">link</a> into a link in IE
|
||||
// but only if it doesn't have name attribute, in which case it's an anchor
|
||||
if (!attr.href) {
|
||||
attr.$set('href', '');
|
||||
|
||||
if (msie <= 8) {
|
||||
|
||||
// turn <a href ng-click="..">link</a> into a stylable link in IE
|
||||
// but only if it doesn't have name attribute, in which case it's an anchor
|
||||
if (!attr.href && !attr.name) {
|
||||
attr.$set('href', '');
|
||||
}
|
||||
|
||||
// add a comment node to anchors to workaround IE bug that causes element content to be reset
|
||||
// to new attribute content if attribute is updated with value containing @ and element also
|
||||
// contains value with @
|
||||
// see issue #1949
|
||||
element.append(document.createComment('IE fix'));
|
||||
}
|
||||
|
||||
return function(scope, element) {
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
it('should execute ng-click but not reload when no href but name specified', function() {
|
||||
element('#link-5').click();
|
||||
expect(input('value').val()).toEqual('5');
|
||||
expect(element('#link-5').attr('href')).toBe('');
|
||||
expect(element('#link-5').attr('href')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should only change url when only ng-href', function() {
|
||||
@@ -340,8 +340,9 @@ forEach(['src', 'href'], function(attrName) {
|
||||
|
||||
// on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
|
||||
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
|
||||
// to set the property as well to achieve the desired effect
|
||||
if (msie) element.prop(attrName, value);
|
||||
// to set the property as well to achieve the desired effect.
|
||||
// we use attr[attrName] value since $set can sanitize the url.
|
||||
if (msie) element.prop(attrName, attr[attrName]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
function classDirective(name, selector) {
|
||||
name = 'ngClass' + name;
|
||||
return ngDirective(function(scope, element, attr) {
|
||||
var oldVal = undefined;
|
||||
|
||||
scope.$watch(attr[name], ngClassWatchAction, true);
|
||||
|
||||
@@ -26,13 +27,14 @@ function classDirective(name, selector) {
|
||||
}
|
||||
|
||||
|
||||
function ngClassWatchAction(newVal, oldVal) {
|
||||
function ngClassWatchAction(newVal) {
|
||||
if (selector === true || scope.$index % 2 === selector) {
|
||||
if (oldVal && (newVal !== oldVal)) {
|
||||
removeClass(oldVal);
|
||||
}
|
||||
addClass(newVal);
|
||||
}
|
||||
oldVal = newVal;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +67,7 @@ function classDirective(name, selector) {
|
||||
*
|
||||
* The directive won't add duplicate classes if a particular class was already set.
|
||||
*
|
||||
* When the expression changes, the previously added classes are removed and only then the classes
|
||||
* When the expression changes, the previously added classes are removed and only then the
|
||||
* new classes are added.
|
||||
*
|
||||
* @element ANY
|
||||
|
||||
@@ -20,8 +20,11 @@
|
||||
* On child elments add:
|
||||
*
|
||||
* * `ngSwitchWhen`: the case statement to match against. If match then this
|
||||
* case will be displayed.
|
||||
* * `ngSwitchDefault`: the default case when no other casses match.
|
||||
* case will be displayed. If the same match appears multiple times, all the
|
||||
* elements will be displayed.
|
||||
* * `ngSwitchDefault`: the default case when no other case match. If there
|
||||
* are multiple default cases, all of them will be displayed when no other
|
||||
* case match.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
@@ -63,27 +66,34 @@ var NG_SWITCH = 'ng-switch';
|
||||
var ngSwitchDirective = valueFn({
|
||||
restrict: 'EA',
|
||||
require: 'ngSwitch',
|
||||
controller: function ngSwitchController() {
|
||||
// asks for $scope to fool the BC controller module
|
||||
controller: ['$scope', function ngSwitchController() {
|
||||
this.cases = {};
|
||||
},
|
||||
}],
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
var watchExpr = attr.ngSwitch || attr.on,
|
||||
selectedTransclude,
|
||||
selectedElement,
|
||||
selectedScope;
|
||||
selectedTranscludes,
|
||||
selectedElements,
|
||||
selectedScopes = [];
|
||||
|
||||
scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
|
||||
if (selectedElement) {
|
||||
selectedScope.$destroy();
|
||||
selectedElement.remove();
|
||||
selectedElement = selectedScope = null;
|
||||
for (var i= 0, ii=selectedScopes.length; i<ii; i++) {
|
||||
selectedScopes[i].$destroy();
|
||||
selectedElements[i].remove();
|
||||
}
|
||||
if ((selectedTransclude = ctrl.cases['!' + value] || ctrl.cases['?'])) {
|
||||
|
||||
selectedElements = [];
|
||||
selectedScopes = [];
|
||||
|
||||
if ((selectedTranscludes = ctrl.cases['!' + value] || ctrl.cases['?'])) {
|
||||
scope.$eval(attr.change);
|
||||
selectedScope = scope.$new();
|
||||
selectedTransclude(selectedScope, function(caseElement) {
|
||||
selectedElement = caseElement;
|
||||
element.append(caseElement);
|
||||
forEach(selectedTranscludes, function(selectedTransclude) {
|
||||
var selectedScope = scope.$new();
|
||||
selectedScopes.push(selectedScope);
|
||||
selectedTransclude(selectedScope, function(caseElement) {
|
||||
selectedElements.push(caseElement);
|
||||
element.append(caseElement);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -96,7 +106,8 @@ var ngSwitchWhenDirective = ngDirective({
|
||||
require: '^ngSwitch',
|
||||
compile: function(element, attrs, transclude) {
|
||||
return function(scope, element, attr, ctrl) {
|
||||
ctrl.cases['!' + attrs.ngSwitchWhen] = transclude;
|
||||
ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
|
||||
ctrl.cases['!' + attrs.ngSwitchWhen].push(transclude);
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -107,7 +118,8 @@ var ngSwitchDefaultDirective = ngDirective({
|
||||
require: '^ngSwitch',
|
||||
compile: function(element, attrs, transclude) {
|
||||
return function(scope, element, attr, ctrl) {
|
||||
ctrl.cases['?'] = transclude;
|
||||
ctrl.cases['?'] = (ctrl.cases['?'] || []);
|
||||
ctrl.cases['?'].push(transclude);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -147,7 +147,7 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
|
||||
if (current.controller) {
|
||||
locals.$scope = lastScope;
|
||||
controller = $controller(current.controller, locals);
|
||||
element.contents().data('$ngControllerController', controller);
|
||||
element.children().data('$ngControllerController', controller);
|
||||
}
|
||||
|
||||
link(lastScope);
|
||||
|
||||
+63
-17
@@ -32,6 +32,22 @@
|
||||
* called for each element of `array`. The final result is an array of those elements that
|
||||
* the predicate returned true for.
|
||||
*
|
||||
* @param {function(expected, actual)|true|undefined} comparator Comparator which is used in
|
||||
* determining if the expected value (from the filter expression) and actual value (from
|
||||
* the object in the array) should be considered a match.
|
||||
*
|
||||
* Can be one of:
|
||||
*
|
||||
* - `function(expected, actual)`:
|
||||
* The function will be given the object value and the predicate value to compare and
|
||||
* should return true if the item should be included in filtered result.
|
||||
*
|
||||
* - `true`: A shorthand for `function(expected, actual) { return angular.equals(expected, actual)}`.
|
||||
* this is essentially strict comparison of expected and actual.
|
||||
*
|
||||
* - `false|undefined`: A short hand for a function which will look for a substring match in case
|
||||
* insensitive way.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
@@ -39,7 +55,8 @@
|
||||
{name:'Mary', phone:'800-BIG-MARY'},
|
||||
{name:'Mike', phone:'555-4321'},
|
||||
{name:'Adam', phone:'555-5678'},
|
||||
{name:'Julie', phone:'555-8765'}]"></div>
|
||||
{name:'Julie', phone:'555-8765'},
|
||||
{name:'Juliette', phone:'555-5678'}]"></div>
|
||||
|
||||
Search: <input ng-model="searchText">
|
||||
<table id="searchTextResults">
|
||||
@@ -53,9 +70,10 @@
|
||||
Any: <input ng-model="search.$"> <br>
|
||||
Name only <input ng-model="search.name"><br>
|
||||
Phone only <input ng-model="search.phone"å><br>
|
||||
Equality <input type="checkbox" ng-model="strict"><br>
|
||||
<table id="searchObjResults">
|
||||
<tr><th>Name</th><th>Phone</th><tr>
|
||||
<tr ng-repeat="friend in friends | filter:search">
|
||||
<tr ng-repeat="friend in friends | filter:search:strict">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<tr>
|
||||
@@ -75,14 +93,20 @@
|
||||
it('should search in specific fields when filtering with a predicate object', function() {
|
||||
input('search.$').enter('i');
|
||||
expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Mary', 'Mike', 'Julie']);
|
||||
toEqual(['Mary', 'Mike', 'Julie', 'Juliette']);
|
||||
});
|
||||
it('should use a equal comparison when comparator is true', function() {
|
||||
input('search.name').enter('Julie');
|
||||
input('strict').check();
|
||||
expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Julie']);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
function filterFilter() {
|
||||
return function(array, expression) {
|
||||
if (!(array instanceof Array)) return array;
|
||||
return function(array, expression, comperator) {
|
||||
if (!isArray(array)) return array;
|
||||
var predicates = [];
|
||||
predicates.check = function(value) {
|
||||
for (var j = 0; j < predicates.length; j++) {
|
||||
@@ -92,20 +116,43 @@ function filterFilter() {
|
||||
}
|
||||
return true;
|
||||
};
|
||||
switch(typeof comperator) {
|
||||
case "function":
|
||||
break;
|
||||
case "boolean":
|
||||
if(comperator == true) {
|
||||
comperator = function(obj, text) {
|
||||
return angular.equals(obj, text);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
comperator = function(obj, text) {
|
||||
text = (''+text).toLowerCase();
|
||||
return (''+obj).toLowerCase().indexOf(text) > -1
|
||||
};
|
||||
}
|
||||
var search = function(obj, text){
|
||||
if (text.charAt(0) === '!') {
|
||||
if (typeof text == 'string' && text.charAt(0) === '!') {
|
||||
return !search(obj, text.substr(1));
|
||||
}
|
||||
switch (typeof obj) {
|
||||
case "boolean":
|
||||
case "number":
|
||||
case "string":
|
||||
return ('' + obj).toLowerCase().indexOf(text) > -1;
|
||||
return comperator(obj, text);
|
||||
case "object":
|
||||
for ( var objKey in obj) {
|
||||
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
|
||||
return true;
|
||||
}
|
||||
switch (typeof text) {
|
||||
case "object":
|
||||
return comperator(obj, text);
|
||||
break;
|
||||
default:
|
||||
for ( var objKey in obj) {
|
||||
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
case "array":
|
||||
@@ -128,19 +175,18 @@ function filterFilter() {
|
||||
for (var key in expression) {
|
||||
if (key == '$') {
|
||||
(function() {
|
||||
var text = (''+expression[key]).toLowerCase();
|
||||
if (!text) return;
|
||||
if (!expression[key]) return;
|
||||
var path = key
|
||||
predicates.push(function(value) {
|
||||
return search(value, text);
|
||||
return search(value, expression[path]);
|
||||
});
|
||||
})();
|
||||
} else {
|
||||
(function() {
|
||||
if (!expression[key]) return;
|
||||
var path = key;
|
||||
var text = (''+expression[key]).toLowerCase();
|
||||
if (!text) return;
|
||||
predicates.push(function(value) {
|
||||
return search(getter(value, path), text);
|
||||
return search(getter(value,path), expression[path]);
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
fraction += '0';
|
||||
}
|
||||
|
||||
if (fractionSize) formatedText += decimalSep + fraction.substr(0, fractionSize);
|
||||
if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
|
||||
}
|
||||
|
||||
parts.push(isNegative ? pattern.negPre : pattern.posPre);
|
||||
@@ -211,8 +211,12 @@ function dateStrGetter(name, shortForm) {
|
||||
}
|
||||
|
||||
function timeZoneGetter(date) {
|
||||
var offset = date.getTimezoneOffset();
|
||||
return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2);
|
||||
var zone = -1 * date.getTimezoneOffset();
|
||||
var paddedZone = (zone >= 0) ? "+" : "";
|
||||
|
||||
paddedZone += padNumber(zone / 60, 2) + padNumber(Math.abs(zone % 60), 2);
|
||||
|
||||
return paddedZone;
|
||||
}
|
||||
|
||||
function ampmGetter(date, formats) {
|
||||
@@ -237,6 +241,9 @@ var DATE_FORMATS = {
|
||||
m: dateGetter('Minutes', 1),
|
||||
ss: dateGetter('Seconds', 2),
|
||||
s: dateGetter('Seconds', 1),
|
||||
// while ISO 8601 requires fractions to be prefixed with `.` or `,`
|
||||
// we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
|
||||
sss: dateGetter('Milliseconds', 3),
|
||||
EEEE: dateStrGetter('Day'),
|
||||
EEE: dateStrGetter('Day', true),
|
||||
a: ampmGetter,
|
||||
@@ -275,6 +282,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
|
||||
* * `'m'`: Minute in hour (0-59)
|
||||
* * `'ss'`: Second in minute, padded (00-59)
|
||||
* * `'s'`: Second in minute (0-59)
|
||||
* * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
|
||||
* * `'a'`: am/pm marker
|
||||
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200)
|
||||
*
|
||||
@@ -298,7 +306,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
|
||||
*
|
||||
* @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
|
||||
* number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's
|
||||
* shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ).
|
||||
* shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
|
||||
* specified in the string input, the time is considered to be in the local timezone.
|
||||
* @param {string=} format Formatting rules (see Description). If not specified,
|
||||
* `mediumDate` is used.
|
||||
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
|
||||
@@ -318,7 +327,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
|
||||
expect(binding("1288323623006 | date:'medium'")).
|
||||
toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
|
||||
expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
|
||||
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
|
||||
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
|
||||
expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
|
||||
toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
|
||||
});
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
orderByFilter.$inject = ['$parse'];
|
||||
function orderByFilter($parse){
|
||||
return function(array, sortPredicate, reverseOrder) {
|
||||
if (!(array instanceof Array)) return array;
|
||||
if (!isArray(array)) return array;
|
||||
if (!sortPredicate) return array;
|
||||
sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
|
||||
sortPredicate = map(sortPredicate, function(predicate){
|
||||
|
||||
+23
-9
@@ -149,7 +149,10 @@ function $HttpProvider() {
|
||||
},
|
||||
post: {'Content-Type': 'application/json;charset=utf-8'},
|
||||
put: {'Content-Type': 'application/json;charset=utf-8'}
|
||||
}
|
||||
},
|
||||
|
||||
xsrfCookieName: 'XSRF-TOKEN',
|
||||
xsrfHeaderName: 'X-XSRF-TOKEN'
|
||||
};
|
||||
|
||||
var providerResponseInterceptors = this.responseInterceptors = [];
|
||||
@@ -383,9 +386,10 @@ function $HttpProvider() {
|
||||
* {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which
|
||||
* an unauthorized site can gain your user's private data. Angular provides following mechanism
|
||||
* to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
|
||||
* called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that
|
||||
* runs on your domain could read the cookie, your server can be assured that the XHR came from
|
||||
* JavaScript running on your domain. The header will not be set for cross-domain requests.
|
||||
* (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only
|
||||
* JavaScript that runs on your domain could read the cookie, your server can be assured that
|
||||
* the XHR came from JavaScript running on your domain. The header will not be set for
|
||||
* cross-domain requests.
|
||||
*
|
||||
* To take advantage of this, your server needs to set a token in a JavaScript readable session
|
||||
* cookie called `XSRF-TOKEN` on first HTTP GET request. On subsequent non-GET requests the
|
||||
@@ -395,6 +399,9 @@ function $HttpProvider() {
|
||||
* up its own tokens). We recommend that the token is a digest of your site's authentication
|
||||
* cookie with {@link http://en.wikipedia.org/wiki/Rainbow_table salt for added security}.
|
||||
*
|
||||
* The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
|
||||
* properties of either $httpProvider.defaults, or the per-request config object.
|
||||
*
|
||||
*
|
||||
* @param {object} config Object describing the request to be made and how it should be
|
||||
* processed. The object has following properties:
|
||||
@@ -405,6 +412,8 @@ function $HttpProvider() {
|
||||
* `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified.
|
||||
* - **data** – `{string|Object}` – Data to be sent as the request message data.
|
||||
* - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server.
|
||||
* - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
|
||||
* - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
|
||||
* - **transformRequest** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||||
* transform function or an array of such functions. The transform function takes the http
|
||||
* request body and headers and returns its transformed (typically serialized) version.
|
||||
@@ -513,12 +522,17 @@ function $HttpProvider() {
|
||||
function $http(config) {
|
||||
config.method = uppercase(config.method);
|
||||
|
||||
var xsrfHeader = {},
|
||||
xsrfCookieName = config.xsrfCookieName || defaults.xsrfCookieName,
|
||||
xsrfHeaderName = config.xsrfHeaderName || defaults.xsrfHeaderName,
|
||||
xsrfToken = isSameDomain(config.url, $browser.url()) ?
|
||||
$browser.cookies()[xsrfCookieName] : undefined;
|
||||
xsrfHeader[xsrfHeaderName] = xsrfToken;
|
||||
|
||||
var reqTransformFn = config.transformRequest || defaults.transformRequest,
|
||||
respTransformFn = config.transformResponse || defaults.transformResponse,
|
||||
defHeaders = defaults.headers,
|
||||
xsrfToken = isSameDomain(config.url, $browser.url()) ?
|
||||
$browser.cookies()['XSRF-TOKEN'] : undefined,
|
||||
reqHeaders = extend({'X-XSRF-TOKEN': xsrfToken},
|
||||
reqHeaders = extend(xsrfHeader,
|
||||
defHeaders.common, defHeaders[lowercase(config.method)], config.headers),
|
||||
reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn),
|
||||
promise;
|
||||
@@ -804,8 +818,8 @@ function $HttpProvider() {
|
||||
if (isObject(v)) {
|
||||
v = toJson(v);
|
||||
}
|
||||
parts.push(encodeURIComponent(key) + '=' +
|
||||
encodeURIComponent(v));
|
||||
parts.push(encodeUriQuery(key) + '=' +
|
||||
encodeUriQuery(v));
|
||||
});
|
||||
});
|
||||
return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
|
||||
|
||||
+23
-1
@@ -65,8 +65,30 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
|
||||
// always async
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
var responseHeaders = xhr.getAllResponseHeaders();
|
||||
|
||||
// TODO(vojta): remove once Firefox 21 gets released.
|
||||
// begin: workaround to overcome Firefox CORS http response headers bug
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=608735
|
||||
// Firefox already patched in nightly. Should land in Firefox 21.
|
||||
|
||||
// CORS "simple response headers" http://www.w3.org/TR/cors/
|
||||
var value,
|
||||
simpleHeaders = ["Cache-Control", "Content-Language", "Content-Type",
|
||||
"Expires", "Last-Modified", "Pragma"];
|
||||
if (!responseHeaders) {
|
||||
responseHeaders = "";
|
||||
forEach(simpleHeaders, function (header) {
|
||||
var value = xhr.getResponseHeader(header);
|
||||
if (value) {
|
||||
responseHeaders += header + ": " + value + "\n";
|
||||
}
|
||||
});
|
||||
}
|
||||
// end of the workaround.
|
||||
|
||||
completeRequest(callback, status || xhr.status, xhr.response || xhr.responseText,
|
||||
xhr.getAllResponseHeaders());
|
||||
responseHeaders);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+44
-14
@@ -303,6 +303,8 @@ function parser(text, json, $filter, csp){
|
||||
if (tokens.length !== 0) {
|
||||
throwError("is an unexpected token", tokens[0]);
|
||||
}
|
||||
value.literal = !!value.literal;
|
||||
value.constant = !!value.constant;
|
||||
return value;
|
||||
|
||||
///////////////////////////////////
|
||||
@@ -350,15 +352,19 @@ function parser(text, json, $filter, csp){
|
||||
}
|
||||
|
||||
function unaryFn(fn, right) {
|
||||
return function(self, locals) {
|
||||
return extend(function(self, locals) {
|
||||
return fn(self, locals, right);
|
||||
};
|
||||
}, {
|
||||
constant:right.constant
|
||||
});
|
||||
}
|
||||
|
||||
function binaryFn(left, fn, right) {
|
||||
return function(self, locals) {
|
||||
return extend(function(self, locals) {
|
||||
return fn(self, locals, left, right);
|
||||
};
|
||||
}, {
|
||||
constant:left.constant && right.constant
|
||||
});
|
||||
}
|
||||
|
||||
function statements() {
|
||||
@@ -526,6 +532,9 @@ function parser(text, json, $filter, csp){
|
||||
if (!primary) {
|
||||
throwError("not a primary expression", token);
|
||||
}
|
||||
if (token.json) {
|
||||
primary.constant = primary.literal = true;
|
||||
}
|
||||
}
|
||||
|
||||
var next, context;
|
||||
@@ -614,23 +623,32 @@ function parser(text, json, $filter, csp){
|
||||
// This is used with json array declaration
|
||||
function arrayDeclaration () {
|
||||
var elementFns = [];
|
||||
var allConstant = true;
|
||||
if (peekToken().text != ']') {
|
||||
do {
|
||||
elementFns.push(expression());
|
||||
var elementFn = expression();
|
||||
elementFns.push(elementFn);
|
||||
if (!elementFn.constant) {
|
||||
allConstant = false;
|
||||
}
|
||||
} while (expect(','));
|
||||
}
|
||||
consume(']');
|
||||
return function(self, locals){
|
||||
return extend(function(self, locals){
|
||||
var array = [];
|
||||
for ( var i = 0; i < elementFns.length; i++) {
|
||||
array.push(elementFns[i](self, locals));
|
||||
}
|
||||
return array;
|
||||
};
|
||||
}, {
|
||||
literal:true,
|
||||
constant:allConstant
|
||||
});
|
||||
}
|
||||
|
||||
function object () {
|
||||
var keyValues = [];
|
||||
var allConstant = true;
|
||||
if (peekToken().text != '}') {
|
||||
do {
|
||||
var token = expect(),
|
||||
@@ -638,10 +656,13 @@ function parser(text, json, $filter, csp){
|
||||
consume(":");
|
||||
var value = expression();
|
||||
keyValues.push({key:key, value:value});
|
||||
if (!value.constant) {
|
||||
allConstant = false;
|
||||
}
|
||||
} while (expect(','));
|
||||
}
|
||||
consume('}');
|
||||
return function(self, locals){
|
||||
return extend(function(self, locals){
|
||||
var object = {};
|
||||
for ( var i = 0; i < keyValues.length; i++) {
|
||||
var keyValue = keyValues[i];
|
||||
@@ -649,7 +670,10 @@ function parser(text, json, $filter, csp){
|
||||
object[keyValue.key] = value;
|
||||
}
|
||||
return object;
|
||||
};
|
||||
}, {
|
||||
literal:true,
|
||||
constant:allConstant
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -848,12 +872,18 @@ function getterFn(path, csp) {
|
||||
* @param {string} expression String expression to compile.
|
||||
* @returns {function(context, locals)} a function which represents the compiled expression:
|
||||
*
|
||||
* * `context`: an object against which any expressions embedded in the strings are evaluated
|
||||
* against (Topically a scope object).
|
||||
* * `locals`: local variables context object, useful for overriding values in `context`.
|
||||
* * `context` – `{object}` – an object against which any expressions embedded in the strings
|
||||
* are evaluated against (tipically a scope object).
|
||||
* * `locals` – `{object=}` – local variables context object, useful for overriding values in
|
||||
* `context`.
|
||||
*
|
||||
* The return function also has an `assign` property, if the expression is assignable, which
|
||||
* allows one to set values to expressions.
|
||||
* The returned function also has the following properties:
|
||||
* * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
|
||||
* literal.
|
||||
* * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
|
||||
* constant literals.
|
||||
* * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
|
||||
* set to a function to change its value on the given context.
|
||||
*
|
||||
*/
|
||||
function $ParseProvider() {
|
||||
|
||||
+9
-9
@@ -12,7 +12,7 @@
|
||||
* interface for interacting with an object that represents the result of an action that is
|
||||
* performed asynchronously, and may or may not be finished at any given point in time.
|
||||
*
|
||||
* From the perspective of dealing with error handling, deferred and promise apis are to
|
||||
* From the perspective of dealing with error handling, deferred and promise APIs are to
|
||||
* asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
|
||||
*
|
||||
* <pre>
|
||||
@@ -47,7 +47,7 @@
|
||||
*
|
||||
* At first it might not be obvious why this extra complexity is worth the trouble. The payoff
|
||||
* comes in the way of
|
||||
* [guarantees that promise and deferred apis make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
|
||||
* [guarantees that promise and deferred APIs make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
|
||||
*
|
||||
* Additionally the promise api allows for composition that is very hard to do with the
|
||||
* traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
|
||||
@@ -59,7 +59,7 @@
|
||||
*
|
||||
* A new instance of deferred is constructed by calling `$q.defer()`.
|
||||
*
|
||||
* The purpose of the deferred object is to expose the associated Promise instance as well as apis
|
||||
* The purpose of the deferred object is to expose the associated Promise instance as well as APIs
|
||||
* that can be used for signaling the successful or unsuccessful completion of the task.
|
||||
*
|
||||
* **Methods**
|
||||
@@ -102,7 +102,7 @@
|
||||
* return result + 1;
|
||||
* });
|
||||
*
|
||||
* // promiseB will be resolved immediately after promiseA is resolved and it's value will be
|
||||
* // promiseB will be resolved immediately after promiseA is resolved and its value will be
|
||||
* // the result of promiseA incremented by 1
|
||||
* </pre>
|
||||
*
|
||||
@@ -127,7 +127,7 @@
|
||||
* # Testing
|
||||
*
|
||||
* <pre>
|
||||
* it('should simulate promise', inject(function($q, $rootSCope) {
|
||||
* it('should simulate promise', inject(function($q, $rootScope) {
|
||||
* var deferred = $q.defer();
|
||||
* var promise = deferred.promise;
|
||||
* var resolvedValue;
|
||||
@@ -136,7 +136,7 @@
|
||||
* expect(resolvedValue).toBeUndefined();
|
||||
*
|
||||
* // Simulate resolving of promise
|
||||
* defered.resolve(123);
|
||||
* deferred.resolve(123);
|
||||
* // Note that the 'then' function does not get called synchronously.
|
||||
* // This is because we want the promise API to always be async, whether or not
|
||||
* // it got called synchronously or asynchronously.
|
||||
@@ -312,12 +312,12 @@ function qFactory(nextTick, exceptionHandler) {
|
||||
* @methodOf ng.$q
|
||||
* @description
|
||||
* Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
|
||||
* This is useful when you are dealing with on object that might or might not be a promise, or if
|
||||
* This is useful when you are dealing with an object that might or might not be a promise, or if
|
||||
* the promise comes from a source that can't be trusted.
|
||||
*
|
||||
* @param {*} value Value or a promise
|
||||
* @returns {Promise} Returns a single promise that will be resolved with an array of values,
|
||||
* each value coresponding to the promise at the same index in the `promises` array. If any of
|
||||
* each value corresponding to the promise at the same index in the `promises` array. If any of
|
||||
* the promises is resolved with a rejection, this resulting promise will be resolved with the
|
||||
* same rejection.
|
||||
*/
|
||||
@@ -379,7 +379,7 @@ function qFactory(nextTick, exceptionHandler) {
|
||||
*
|
||||
* @param {Array.<Promise>} promises An array of promises.
|
||||
* @returns {Promise} Returns a single promise that will be resolved with an array of values,
|
||||
* each value coresponding to the promise at the same index in the `promises` array. If any of
|
||||
* each value corresponding to the promise at the same index in the `promises` array. If any of
|
||||
* the promises is resolved with a rejection, this resulting promise will be resolved with the
|
||||
* same rejection.
|
||||
*/
|
||||
|
||||
+14
-5
@@ -137,6 +137,7 @@ function $RootScopeProvider(){
|
||||
this.$$destroyed = false;
|
||||
this.$$asyncQueue = [];
|
||||
this.$$listeners = {};
|
||||
this.$$isolateBindings = {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,6 +300,14 @@ function $RootScopeProvider(){
|
||||
watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
|
||||
}
|
||||
|
||||
if (typeof watchExp == 'string' && get.constant) {
|
||||
var originalFn = watcher.fn;
|
||||
watcher.fn = function(newVal, oldVal, scope) {
|
||||
originalFn.call(this, newVal, oldVal, scope);
|
||||
arrayRemove(array, watcher);
|
||||
};
|
||||
}
|
||||
|
||||
if (!array) {
|
||||
array = scope.$$watchers = [];
|
||||
}
|
||||
@@ -618,10 +627,6 @@ function $RootScopeProvider(){
|
||||
* Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
|
||||
* event life cycle.
|
||||
*
|
||||
* @param {string} name Event name to listen on.
|
||||
* @param {function(event)} listener Function to call when the event is emitted.
|
||||
* @returns {function()} Returns a deregistration function for this listener.
|
||||
*
|
||||
* The event listener function format is: `function(event, args...)`. The `event` object
|
||||
* passed into the listener has the following attributes:
|
||||
*
|
||||
@@ -632,6 +637,10 @@ function $RootScopeProvider(){
|
||||
* propagation (available only for events that were `$emit`-ed).
|
||||
* - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true.
|
||||
* - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
|
||||
*
|
||||
* @param {string} name Event name to listen on.
|
||||
* @param {function(event, args...)} listener Function to call when the event is emitted.
|
||||
* @returns {function()} Returns a deregistration function for this listener.
|
||||
*/
|
||||
$on: function(name, listener) {
|
||||
var namedListeners = this.$$listeners[name];
|
||||
@@ -809,7 +818,7 @@ function $RootScopeProvider(){
|
||||
|
||||
/**
|
||||
* function used as an initial value for watchers.
|
||||
* because it's uniqueue we can easily tell it apart from other values
|
||||
* because it's unique we can easily tell it apart from other values
|
||||
*/
|
||||
function initWatchVal() {}
|
||||
}];
|
||||
|
||||
+23
-7
@@ -23,9 +23,18 @@ function $RouteProvider(){
|
||||
* `$location.path` will be updated to add or drop the trailing slash to exactly match the
|
||||
* route definition.
|
||||
*
|
||||
* `path` can contain named groups starting with a colon (`:name`). All characters up to the
|
||||
* next slash are matched and stored in `$routeParams` under the given `name` when the route
|
||||
* matches.
|
||||
* * `path` can contain named groups starting with a colon (`:name`). All characters up
|
||||
* to the next slash are matched and stored in `$routeParams` under the given `name`
|
||||
* when the route matches.
|
||||
* * `path` can contain named groups starting with a star (`*name`). All characters are
|
||||
* eagerly stored in `$routeParams` under the given `name` when the route matches.
|
||||
*
|
||||
* For example, routes like `/color/:color/largecode/*largecode/edit` will match
|
||||
* `/color/brown/largecode/code/with/slashs/edit` and extract:
|
||||
*
|
||||
* * `color: brown`
|
||||
* * `largecode: code/with/slashs`.
|
||||
*
|
||||
*
|
||||
* @param {Object} route Mapping information to be assigned to `$route.current` on route
|
||||
* match.
|
||||
@@ -341,12 +350,12 @@ function $RouteProvider(){
|
||||
// regex only once and then reuse it
|
||||
|
||||
// Escape regexp special characters.
|
||||
when = '^' + when.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$';
|
||||
when = '^' + when.replace(/[-\/\\^$:*+?.()|[\]{}]/g, "\\$&") + '$';
|
||||
var regex = '',
|
||||
params = [],
|
||||
dst = {};
|
||||
|
||||
var re = /:(\w+)/g,
|
||||
var re = /\\([:*])(\w+)/g,
|
||||
paramMatch,
|
||||
lastMatchedIndex = 0;
|
||||
|
||||
@@ -354,8 +363,15 @@ function $RouteProvider(){
|
||||
// Find each :param in `when` and replace it with a capturing group.
|
||||
// Append all other sections of when unchanged.
|
||||
regex += when.slice(lastMatchedIndex, paramMatch.index);
|
||||
regex += '([^\\/]*)';
|
||||
params.push(paramMatch[1]);
|
||||
switch(paramMatch[1]) {
|
||||
case ':':
|
||||
regex += '([^\\/]*)';
|
||||
break;
|
||||
case '*':
|
||||
regex += '(.*)';
|
||||
break;
|
||||
}
|
||||
params.push(paramMatch[2]);
|
||||
lastMatchedIndex = re.lastIndex;
|
||||
}
|
||||
// Append trailing path part.
|
||||
|
||||
@@ -19,6 +19,18 @@ angular.module('ngCookies', ['ng']).
|
||||
* this object, new cookies are created/deleted at the end of current $eval.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function ExampleController($cookies) {
|
||||
// Retrieving a cookie
|
||||
var favoriteCookie = $cookies.myFavorite;
|
||||
// Setting a cookie
|
||||
$cookies.myFavorite = 'oatmeal';
|
||||
}
|
||||
</script>
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
*/
|
||||
factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
|
||||
var cookies = {},
|
||||
|
||||
Vendored
+6
-1
@@ -456,6 +456,7 @@ angular.mock.$LogProvider = function() {
|
||||
* newYearInBratislava.getDate() => 1;
|
||||
* newYearInBratislava.getHours() => 0;
|
||||
* newYearInBratislava.getMinutes() => 0;
|
||||
* newYearInBratislava.getSeconds() => 0;
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
@@ -512,6 +513,10 @@ angular.mock.$LogProvider = function() {
|
||||
return self.date.getSeconds();
|
||||
};
|
||||
|
||||
self.getMilliseconds = function() {
|
||||
return self.date.getMilliseconds();
|
||||
};
|
||||
|
||||
self.getTimezoneOffset = function() {
|
||||
return offset * 60;
|
||||
};
|
||||
@@ -562,7 +567,7 @@ angular.mock.$LogProvider = function() {
|
||||
}
|
||||
|
||||
//hide all methods not implemented in this mock that the Date prototype exposes
|
||||
var unimplementedMethods = ['getMilliseconds', 'getUTCDay',
|
||||
var unimplementedMethods = ['getUTCDay',
|
||||
'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
|
||||
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
|
||||
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
|
||||
|
||||
+65
-30
@@ -19,7 +19,7 @@
|
||||
* the need to interact with the low level {@link ng.$http $http} service.
|
||||
*
|
||||
* @param {string} url A parameterized URL template with parameters prefixed by `:` as in
|
||||
* `/user/:username`. If you are using a URL with a port number (e.g.
|
||||
* `/user/:username`. If you are using a URL with a port number (e.g.
|
||||
* `http://example.com:8080/api`), you'll need to escape the colon character before the port
|
||||
* number, like this: `$resource('http://example.com\\:8080/api')`.
|
||||
*
|
||||
@@ -83,9 +83,9 @@
|
||||
*
|
||||
* Calling these methods invoke an {@link ng.$http} with the specified http method,
|
||||
* destination and parameters. When the data is returned from the server then the object is an
|
||||
* instance of the resource class `save`, `remove` and `delete` actions are available on it as
|
||||
* methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read,
|
||||
* update, delete) on server-side data like this:
|
||||
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
|
||||
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
|
||||
* read, update, delete) on server-side data like this:
|
||||
* <pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
var user = User.get({userId:123}, function() {
|
||||
@@ -110,6 +110,24 @@
|
||||
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
|
||||
*
|
||||
*
|
||||
* The Resource instances and collection have these additional properties:
|
||||
*
|
||||
* - `$then`: the `then` method of a {@link ng.$q promise} derived from the underlying
|
||||
* {@link ng.$http $http} call.
|
||||
*
|
||||
* The success callback for the `$then` method will be resolved if the underlying `$http` requests
|
||||
* succeeds.
|
||||
*
|
||||
* The success callback is called with a single object which is the {@link ng.$http http response}
|
||||
* object extended with a new property `resource`. This `resource` property is a reference to the
|
||||
* result of the resource action — resource object or array of resources.
|
||||
*
|
||||
* The error callback is called with the {@link ng.$http http response} object when an http
|
||||
* error occurs.
|
||||
*
|
||||
* - `$resolved`: true if the promise has been resolved (either with success or rejection);
|
||||
* Knowing if the Resource has been resolved is useful in data-binding.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* # Credit card resource
|
||||
@@ -165,9 +183,9 @@
|
||||
});
|
||||
</pre>
|
||||
*
|
||||
* It's worth noting that the success callback for `get`, `query` and other method gets passed
|
||||
* in the response that came from the server as well as $http header getter function, so one
|
||||
* could rewrite the above example and get access to http headers as:
|
||||
* It's worth noting that the success callback for `get`, `query` and other method gets passed
|
||||
* in the response that came from the server as well as $http header getter function, so one
|
||||
* could rewrite the above example and get access to http headers as:
|
||||
*
|
||||
<pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
@@ -290,7 +308,7 @@ angular.module('ngResource', ['ng']).
|
||||
this.defaults = defaults || {};
|
||||
var urlParams = this.urlParams = {};
|
||||
forEach(template.split(/\W/), function(param){
|
||||
if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) {
|
||||
if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) {
|
||||
urlParams[param] = true;
|
||||
}
|
||||
});
|
||||
@@ -298,7 +316,7 @@ angular.module('ngResource', ['ng']).
|
||||
}
|
||||
|
||||
Route.prototype = {
|
||||
url: function(params) {
|
||||
setUrlParams: function(config, params) {
|
||||
var self = this,
|
||||
url = this.template,
|
||||
val,
|
||||
@@ -321,16 +339,17 @@ angular.module('ngResource', ['ng']).
|
||||
});
|
||||
}
|
||||
});
|
||||
url = url.replace(/\/?#$/, '');
|
||||
var query = [];
|
||||
|
||||
// set the url
|
||||
config.url = url.replace(/\/?#$/, '').replace(/\/*$/, '');
|
||||
|
||||
// set params - delegate param encoding to $http
|
||||
forEach(params, function(value, key){
|
||||
if (!self.urlParams[key]) {
|
||||
query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value));
|
||||
config.params = config.params || {};
|
||||
config.params[key] = value;
|
||||
}
|
||||
});
|
||||
query.sort();
|
||||
url = url.replace(/\/*$/, '');
|
||||
return url + (query.length ? '?' + query.join('&') : '');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -362,6 +381,8 @@ angular.module('ngResource', ['ng']).
|
||||
var data;
|
||||
var success = noop;
|
||||
var error = null;
|
||||
var promise;
|
||||
|
||||
switch(arguments.length) {
|
||||
case 4:
|
||||
error = a4;
|
||||
@@ -397,7 +418,8 @@ angular.module('ngResource', ['ng']).
|
||||
}
|
||||
|
||||
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
|
||||
var httpConfig = {};
|
||||
var httpConfig = {},
|
||||
promise;
|
||||
|
||||
forEach(action, function(value, key) {
|
||||
if (key != 'params' && key != 'isArray' ) {
|
||||
@@ -405,23 +427,36 @@ angular.module('ngResource', ['ng']).
|
||||
}
|
||||
});
|
||||
httpConfig.data = data;
|
||||
httpConfig.url = route.url(extend({}, extractParams(data, action.params || {}), params))
|
||||
route.setUrlParams(httpConfig, extend({}, extractParams(data, action.params || {}), params));
|
||||
|
||||
$http(httpConfig).then(function(response) {
|
||||
var data = response.data;
|
||||
function markResolved() { value.$resolved = true; }
|
||||
|
||||
if (data) {
|
||||
if (action.isArray) {
|
||||
value.length = 0;
|
||||
forEach(data, function(item) {
|
||||
value.push(new Resource(item));
|
||||
});
|
||||
} else {
|
||||
copy(data, value);
|
||||
}
|
||||
promise = $http(httpConfig);
|
||||
value.$resolved = false;
|
||||
|
||||
promise.then(markResolved, markResolved);
|
||||
value.$then = promise.then(function(response) {
|
||||
var data = response.data;
|
||||
var then = value.$then, resolved = value.$resolved;
|
||||
|
||||
if (data) {
|
||||
if (action.isArray) {
|
||||
value.length = 0;
|
||||
forEach(data, function(item) {
|
||||
value.push(new Resource(item));
|
||||
});
|
||||
} else {
|
||||
copy(data, value);
|
||||
value.$then = then;
|
||||
value.$resolved = resolved;
|
||||
}
|
||||
(success||noop)(value, response.headers);
|
||||
}, error);
|
||||
}
|
||||
|
||||
(success||noop)(value, response.headers);
|
||||
|
||||
response.resource = value;
|
||||
return response;
|
||||
}, error).then;
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ angular.scenario.output('xml', function(context, runner, model) {
|
||||
if (step.error) {
|
||||
var error = $('<error></error>');
|
||||
stepContext.append(error);
|
||||
error.text(formatException(stepContext.error));
|
||||
error.text(formatException(step.error));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+123
-3
@@ -31,7 +31,7 @@ describe('angular', function() {
|
||||
expect(copy(date) === date).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should copy array", function() {
|
||||
it("should deeply copy an array into an existing array", function() {
|
||||
var src = [1, {name:"value"}];
|
||||
var dst = [{key:"v"}];
|
||||
expect(copy(src, dst)).toBe(dst);
|
||||
@@ -40,6 +40,15 @@ describe('angular', function() {
|
||||
expect(dst[1]).not.toBe(src[1]);
|
||||
});
|
||||
|
||||
it("should deeply copy an array into a new array", function() {
|
||||
var src = [1, {name:"value"}];
|
||||
var dst = copy(src);
|
||||
expect(src).toEqual([1, {name:"value"}]);
|
||||
expect(dst).toEqual(src);
|
||||
expect(dst).not.toBe(src);
|
||||
expect(dst[1]).not.toBe(src[1]);
|
||||
});
|
||||
|
||||
it('should copy empty array', function() {
|
||||
var src = [];
|
||||
var dst = [{key: "v"}];
|
||||
@@ -47,7 +56,7 @@ describe('angular', function() {
|
||||
expect(dst).toEqual([]);
|
||||
});
|
||||
|
||||
it("should copy object", function() {
|
||||
it("should deeply copy an object into an existing object", function() {
|
||||
var src = {a:{name:"value"}};
|
||||
var dst = {b:{key:"v"}};
|
||||
expect(copy(src, dst)).toBe(dst);
|
||||
@@ -56,6 +65,16 @@ describe('angular', function() {
|
||||
expect(dst.a).not.toBe(src.a);
|
||||
});
|
||||
|
||||
it("should deeply copy an object into an existing object", function() {
|
||||
var src = {a:{name:"value"}};
|
||||
var dst = copy(src, dst);
|
||||
expect(src).toEqual({a:{name:"value"}});
|
||||
expect(dst).toEqual(src);
|
||||
expect(dst).not.toBe(src);
|
||||
expect(dst.a).toEqual(src.a);
|
||||
expect(dst.a).not.toBe(src.a);
|
||||
});
|
||||
|
||||
it("should copy primitives", function() {
|
||||
expect(copy(null)).toEqual(null);
|
||||
expect(copy('')).toBe('');
|
||||
@@ -257,7 +276,7 @@ describe('angular', function() {
|
||||
function MyObj() {
|
||||
this.bar = 'barVal';
|
||||
this.baz = 'bazVal';
|
||||
};
|
||||
}
|
||||
MyObj.prototype.foo = 'fooVal';
|
||||
|
||||
var obj = new MyObj(),
|
||||
@@ -267,6 +286,77 @@ describe('angular', function() {
|
||||
|
||||
expect(log).toEqual(['bar:barVal', 'baz:bazVal']);
|
||||
});
|
||||
|
||||
|
||||
it('should handle JQLite and jQuery objects like arrays', function() {
|
||||
var jqObject = jqLite("<p><span>s1</span><span>s2</span></p>").find("span"),
|
||||
log = [];
|
||||
|
||||
forEach(jqObject, function(value, key) { log.push(key + ':' + value.innerHTML)});
|
||||
expect(log).toEqual(['0:s1', '1:s2']);
|
||||
});
|
||||
|
||||
|
||||
it('should handle NodeList objects like arrays', function() {
|
||||
var nodeList = jqLite("<p><span>a</span><span>b</span><span>c</span></p>")[0].childNodes,
|
||||
log = [];
|
||||
|
||||
|
||||
forEach(nodeList, function(value, key) { log.push(key + ':' + value.innerHTML)});
|
||||
expect(log).toEqual(['0:a', '1:b', '2:c']);
|
||||
});
|
||||
|
||||
|
||||
it('should handle HTMLCollection objects like arrays', function() {
|
||||
document.body.innerHTML = "<p>" +
|
||||
"<a name='x'>a</a>" +
|
||||
"<a name='y'>b</a>" +
|
||||
"<a name='x'>c</a>" +
|
||||
"</p>";
|
||||
|
||||
var htmlCollection = document.getElementsByName('x'),
|
||||
log = [];
|
||||
|
||||
forEach(htmlCollection, function(value, key) { log.push(key + ':' + value.innerHTML)});
|
||||
expect(log).toEqual(['0:a', '1:c']);
|
||||
});
|
||||
|
||||
|
||||
it('should handle arguments objects like arrays', function() {
|
||||
var args,
|
||||
log = [];
|
||||
|
||||
(function(){ args = arguments}('a', 'b', 'c'));
|
||||
|
||||
forEach(args, function(value, key) { log.push(key + ':' + value)});
|
||||
expect(log).toEqual(['0:a', '1:b', '2:c']);
|
||||
});
|
||||
|
||||
|
||||
it('should handle objects with length property as objects', function() {
|
||||
var obj = {
|
||||
'foo' : 'bar',
|
||||
'length': 2
|
||||
},
|
||||
log = [];
|
||||
|
||||
forEach(obj, function(value, key) { log.push(key + ':' + value)});
|
||||
expect(log).toEqual(['foo:bar', 'length:2']);
|
||||
});
|
||||
|
||||
|
||||
it('should handle objects of custom types with length property as objects', function() {
|
||||
function CustomType() {
|
||||
this.length = 2;
|
||||
this.foo = 'bar'
|
||||
}
|
||||
|
||||
var obj = new CustomType(),
|
||||
log = [];
|
||||
|
||||
forEach(obj, function(value, key) { log.push(key + ':' + value)});
|
||||
expect(log).toEqual(['length:2', 'foo:bar']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -583,6 +673,13 @@ describe('angular', function() {
|
||||
toBe('<ng-abc x="2A">');
|
||||
});
|
||||
});
|
||||
|
||||
describe('startingTag', function() {
|
||||
it('should allow passing in Nodes instead of Elements', function() {
|
||||
var txtNode = document.createTextNode('some text');
|
||||
expect(startingTag(txtNode)).toBe('some text');
|
||||
});
|
||||
});
|
||||
|
||||
describe('snake_case', function(){
|
||||
it('should convert to snake_case', function() {
|
||||
@@ -640,4 +737,27 @@ describe('angular', function() {
|
||||
expect(toJson({key: $rootScope})).toEqual('{"key":"$SCOPE"}');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('noConflict', function() {
|
||||
var globalAngular;
|
||||
beforeEach(function() {
|
||||
globalAngular = angular;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
angular = globalAngular;
|
||||
});
|
||||
|
||||
it('should return angular', function() {
|
||||
var a = angular.noConflict();
|
||||
expect(a).toBe(globalAngular);
|
||||
});
|
||||
|
||||
it('should restore original angular', function() {
|
||||
var a = angular.noConflict();
|
||||
expect(angular).toBeUndefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+6
-6
@@ -109,7 +109,7 @@ beforeEach(function() {
|
||||
if (this.actual.callCount != 1) {
|
||||
if (this.actual.callCount == 0) {
|
||||
return [
|
||||
'Expected spy ' + this.actual.identity + ' to have been called with ' +
|
||||
'Expected spy ' + this.actual.identity + ' to have been called once with ' +
|
||||
jasmine.pp(expectedArgs) + ' but it was never called.',
|
||||
'Expected spy ' + this.actual.identity + ' not to have been called with ' +
|
||||
jasmine.pp(expectedArgs) + ' but it was.'
|
||||
@@ -117,16 +117,16 @@ beforeEach(function() {
|
||||
}
|
||||
|
||||
return [
|
||||
'Expected spy ' + this.actual.identity + ' to have been called with ' +
|
||||
jasmine.pp(expectedArgs) + ' but it was never called.',
|
||||
'Expected spy ' + this.actual.identity + ' not to have been called with ' +
|
||||
'Expected spy ' + this.actual.identity + ' to have been called once with ' +
|
||||
jasmine.pp(expectedArgs) + ' but it was called ' + this.actual.callCount + ' times.',
|
||||
'Expected spy ' + this.actual.identity + ' not to have been called once with ' +
|
||||
jasmine.pp(expectedArgs) + ' but it was.'
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'Expected spy ' + this.actual.identity + ' to have been called with ' +
|
||||
'Expected spy ' + this.actual.identity + ' to have been called once with ' +
|
||||
jasmine.pp(expectedArgs) + ' but was called with ' + jasmine.pp(this.actual.argsForCall),
|
||||
'Expected spy ' + this.actual.identity + ' not to have been called with ' +
|
||||
'Expected spy ' + this.actual.identity + ' not to have been called once with ' +
|
||||
jasmine.pp(expectedArgs) + ' but was called with ' + jasmine.pp(this.actual.argsForCall)
|
||||
];
|
||||
}
|
||||
|
||||
+14
-2
@@ -279,6 +279,18 @@ describe('browser', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('put via cookies(cookieName, string), if no <base href> ', function () {
|
||||
beforeEach(function () {
|
||||
fakeDocument.basePath = undefined;
|
||||
});
|
||||
|
||||
it('should default path in cookie to "" (empty string)', function () {
|
||||
browser.cookies('cookie', 'bender');
|
||||
// This only fails in Safari and IE when cookiePath returns undefined
|
||||
// Where it now succeeds since baseHref return '' instead of undefined
|
||||
expect(document.cookie).toEqual('cookie=bender');
|
||||
});
|
||||
});
|
||||
|
||||
describe('get via cookies()[cookieName]', function() {
|
||||
|
||||
@@ -555,9 +567,9 @@ describe('browser', function() {
|
||||
expect(browser.baseHref()).toEqual('/base/path/');
|
||||
});
|
||||
|
||||
it('should return undefined if no <base href>', function() {
|
||||
it('should return \'\' (empty string) if no <base href>', function() {
|
||||
fakeDocument.basePath = undefined;
|
||||
expect(browser.baseHref()).toBeUndefined();
|
||||
expect(browser.baseHref()).toEqual('');
|
||||
});
|
||||
|
||||
it('should remove domain from <base href>', function() {
|
||||
|
||||
+248
-15
@@ -116,12 +116,17 @@ describe('$compile', function() {
|
||||
|
||||
describe('compile phase', function() {
|
||||
|
||||
it('should attach scope to the document node when it is compiled explicitly', inject(function($document){
|
||||
$compile($document)($rootScope);
|
||||
expect($document.scope()).toBe($rootScope);
|
||||
}));
|
||||
|
||||
it('should wrap root text nodes in spans', inject(function($compile, $rootScope) {
|
||||
element = jqLite('<div>A<a>B</a>C</div>');
|
||||
var text = element.contents();
|
||||
expect(text[0].nodeName).toEqual('#text');
|
||||
text = $compile(text)($rootScope);
|
||||
expect(lowercase(text[0].nodeName)).toEqual('span');
|
||||
expect(text[0].nodeName).toEqual('SPAN');
|
||||
expect(element.find('span').text()).toEqual('A<a>B</a>C');
|
||||
}));
|
||||
|
||||
@@ -137,6 +142,41 @@ describe('$compile', function() {
|
||||
expect(spans.text().indexOf('C')).toEqual(0);
|
||||
});
|
||||
|
||||
it('should not leak memory when there are top level empty text nodes', function() {
|
||||
var calcCacheSize = function() {
|
||||
var size = 0;
|
||||
forEach(jqLite.cache, function(item, key) { size++; });
|
||||
return size;
|
||||
};
|
||||
|
||||
// We compile the contents of element (i.e. not element itself)
|
||||
// Then delete these contents and check the cache has been reset to zero
|
||||
|
||||
// First with only elements at the top level
|
||||
element = jqLite('<div><div></div></div>');
|
||||
$compile(element.contents())($rootScope);
|
||||
element.html('');
|
||||
expect(calcCacheSize()).toEqual(0);
|
||||
|
||||
// Next with non-empty text nodes at the top level
|
||||
// (in this case the compiler will wrap them in a <span>)
|
||||
element = jqLite('<div>xxx</div>');
|
||||
$compile(element.contents())($rootScope);
|
||||
element.html('');
|
||||
expect(calcCacheSize()).toEqual(0);
|
||||
|
||||
// Next with comment nodes at the top level
|
||||
element = jqLite('<div><!-- comment --></div>');
|
||||
$compile(element.contents())($rootScope);
|
||||
element.html('');
|
||||
expect(calcCacheSize()).toEqual(0);
|
||||
|
||||
// Finally with empty text nodes at the top level
|
||||
element = jqLite('<div> \n<div></div> </div>');
|
||||
$compile(element.contents())($rootScope);
|
||||
element.html('');
|
||||
expect(calcCacheSize()).toEqual(0);
|
||||
});
|
||||
|
||||
describe('multiple directives per element', function() {
|
||||
it('should allow multiple directives per element', inject(function($compile, $rootScope, log){
|
||||
@@ -1381,9 +1421,10 @@ describe('$compile', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should set interpolated attrs to undefined', inject(function($rootScope, $compile) {
|
||||
it('should set interpolated attrs to initial interpolation value', inject(function($rootScope, $compile) {
|
||||
$rootScope.whatever = 'test value';
|
||||
$compile('<div some-attr="{{whatever}}" observer></div>')($rootScope);
|
||||
expect(directiveAttrs.someAttr).toBeUndefined();
|
||||
expect(directiveAttrs.someAttr).toBe($rootScope.whatever);
|
||||
}));
|
||||
|
||||
|
||||
@@ -1538,6 +1579,25 @@ describe('$compile', function() {
|
||||
expect(element.text()).toEqual('WORKS');
|
||||
});
|
||||
});
|
||||
|
||||
it('should support $observe inside link function on directive object', function() {
|
||||
module(function() {
|
||||
directive('testLink', valueFn({
|
||||
templateUrl: 'test-link.html',
|
||||
link: function(scope, element, attrs) {
|
||||
attrs.$observe( 'testLink', function ( val ) {
|
||||
scope.testAttr = val;
|
||||
});
|
||||
}
|
||||
}));
|
||||
});
|
||||
inject(function($compile, $rootScope, $templateCache) {
|
||||
$templateCache.put('test-link.html', '{{testAttr}}' );
|
||||
element = $compile('<div test-link="{{1+2}}"></div>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('3');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1793,27 +1853,21 @@ describe('$compile', function() {
|
||||
describe('attribute', function() {
|
||||
it('should copy simple attribute', inject(function() {
|
||||
compile('<div><span my-component attr="some text">');
|
||||
expect(componentScope.attr).toEqual(undefined);
|
||||
expect(componentScope.attrAlias).toEqual(undefined);
|
||||
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(componentScope.attr).toEqual('some text');
|
||||
expect(componentScope.attrAlias).toEqual('some text');
|
||||
expect(componentScope.attrAlias).toEqual(componentScope.attr);
|
||||
}));
|
||||
|
||||
it('should set up the interpolation before it reaches the link function', inject(function() {
|
||||
$rootScope.name = 'misko';
|
||||
compile('<div><span my-component attr="hello {{name}}">');
|
||||
expect(componentScope.attr).toEqual('hello misko');
|
||||
expect(componentScope.attrAlias).toEqual('hello misko');
|
||||
}));
|
||||
|
||||
it('should update when interpolated attribute updates', inject(function() {
|
||||
compile('<div><span my-component attr="hello {{name}}">');
|
||||
expect(componentScope.attr).toEqual(undefined);
|
||||
expect(componentScope.attrAlias).toEqual(undefined);
|
||||
|
||||
$rootScope.name = 'misko';
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(componentScope.attr).toEqual('hello misko');
|
||||
expect(componentScope.attrAlias).toEqual('hello misko');
|
||||
|
||||
$rootScope.name = 'igor';
|
||||
$rootScope.$apply();
|
||||
@@ -1938,6 +1992,21 @@ describe('$compile', function() {
|
||||
compile('<div><span bad-declaration>');
|
||||
}).toThrow('Invalid isolate scope definition for directive badDeclaration: xxx');
|
||||
}));
|
||||
|
||||
it('should expose a $$isolateBindings property onto the scope', inject(function() {
|
||||
compile('<div><span my-component>');
|
||||
|
||||
expect(typeof componentScope.$$isolateBindings).toBe('object');
|
||||
|
||||
expect(componentScope.$$isolateBindings.attr).toBe('@attr');
|
||||
expect(componentScope.$$isolateBindings.attrAlias).toBe('@attr');
|
||||
expect(componentScope.$$isolateBindings.ref).toBe('=ref');
|
||||
expect(componentScope.$$isolateBindings.refAlias).toBe('=ref');
|
||||
expect(componentScope.$$isolateBindings.reference).toBe('=reference');
|
||||
expect(componentScope.$$isolateBindings.expr).toBe('&expr');
|
||||
expect(componentScope.$$isolateBindings.exprAlias).toBe('&expr');
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -2264,5 +2333,169 @@ describe('$compile', function() {
|
||||
|
||||
expect(element.text()).toBe('-->|x|');
|
||||
}));
|
||||
|
||||
|
||||
it('should add a $$transcluded property onto the transcluded scope', function() {
|
||||
module(function() {
|
||||
directive('trans', function() {
|
||||
return {
|
||||
transclude: true,
|
||||
replace: true,
|
||||
scope: true,
|
||||
template: '<div><span>I:{{$$transcluded}}</span><div ng-transclude></div></div>'
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function(log, $rootScope, $compile) {
|
||||
element = $compile('<div><div trans>T:{{$$transcluded}}</div></div>')
|
||||
($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(jqLite(element.find('span')[0]).text()).toEqual('I:');
|
||||
expect(jqLite(element.find('span')[1]).text()).toEqual('T:true');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('href sanitization', function() {
|
||||
|
||||
it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.testUrl = "javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element.attr('href')).toBe('unsafe:javascript:doEvilStuff()');
|
||||
}));
|
||||
|
||||
|
||||
it('should sanitize data: urls', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.testUrl = "data:evilPayload";
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element.attr('href')).toBe('unsafe:data:evilPayload');
|
||||
}));
|
||||
|
||||
|
||||
it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
|
||||
// case-sensitive
|
||||
$rootScope.testUrl = "JaVaScRiPt:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
|
||||
|
||||
// tab in protocol
|
||||
$rootScope.testUrl = "java\u0009script:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
expect(element[0].href).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
|
||||
|
||||
// space before
|
||||
$rootScope.testUrl = " javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
|
||||
|
||||
// ws chars before
|
||||
$rootScope.testUrl = " \u000e javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
expect(element[0].href).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
|
||||
|
||||
// post-fixed with proper url
|
||||
$rootScope.testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
|
||||
$rootScope.$apply();
|
||||
expect(element[0].href).toBeOneOf(
|
||||
'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
|
||||
'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
|
||||
);
|
||||
}));
|
||||
|
||||
|
||||
it('should sanitize ngHref bindings as well', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a ng-href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.testUrl = "javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
|
||||
}));
|
||||
|
||||
|
||||
it('should not sanitize valid urls', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
|
||||
$rootScope.testUrl = "foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('foo/bar');
|
||||
|
||||
$rootScope.testUrl = "/foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('/foo/bar');
|
||||
|
||||
$rootScope.testUrl = "../foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('../foo/bar');
|
||||
|
||||
$rootScope.testUrl = "#foo";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('#foo');
|
||||
|
||||
$rootScope.testUrl = "http://foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('http://foo/bar');
|
||||
|
||||
$rootScope.testUrl = " http://foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe(' http://foo/bar');
|
||||
|
||||
$rootScope.testUrl = "https://foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('https://foo/bar');
|
||||
|
||||
$rootScope.testUrl = "ftp://foo/bar";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('ftp://foo/bar');
|
||||
|
||||
$rootScope.testUrl = "mailto:foo@bar.com";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('mailto:foo@bar.com');
|
||||
}));
|
||||
|
||||
|
||||
it('should not sanitize href on elements other than anchor', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div href="{{testUrl}}"></div>')($rootScope);
|
||||
$rootScope.testUrl = "javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element.attr('href')).toBe('javascript:doEvilStuff()');
|
||||
}));
|
||||
|
||||
|
||||
it('should not sanitize attributes other than href', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a title="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.testUrl = "javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element.attr('title')).toBe('javascript:doEvilStuff()');
|
||||
}));
|
||||
|
||||
|
||||
it('should allow reconfiguration of the href whitelist', function() {
|
||||
module(function($compileProvider) {
|
||||
expect($compileProvider.urlSanitizationWhitelist() instanceof RegExp).toBe(true);
|
||||
var returnVal = $compileProvider.urlSanitizationWhitelist(/javascript:/);
|
||||
expect(returnVal).toBe($compileProvider);
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
|
||||
$rootScope.testUrl = "javascript:doEvilStuff()";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('javascript:doEvilStuff()');
|
||||
|
||||
$rootScope.testUrl = "http://recon/figured";
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('unsafe:http://recon/figured');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
describe('a', function() {
|
||||
var element;
|
||||
var element, $compile, $rootScope;
|
||||
|
||||
|
||||
beforeEach(inject(function(_$compile_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
|
||||
afterEach(function(){
|
||||
@@ -9,8 +15,7 @@ describe('a', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should prevent default action to be executed when href is empty',
|
||||
inject(function($rootScope, $compile) {
|
||||
it('should prevent default action to be executed when href is empty', function() {
|
||||
var orgLocation = document.location.href,
|
||||
preventDefaultCalled = false,
|
||||
event;
|
||||
@@ -42,5 +47,15 @@ describe('a', function() {
|
||||
}
|
||||
|
||||
expect(document.location.href).toEqual(orgLocation);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
it('should prevent IE for changing text content when setting attribute', function() {
|
||||
// see issue #1949
|
||||
element = jqLite('<a href="">hello@you</a>');
|
||||
$compile(element);
|
||||
element.attr('href', 'bye@me');
|
||||
|
||||
expect(element.text()).toBe('hello@you');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -236,6 +236,17 @@ describe('ngClass', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should not mess up class value due to observing an interpolated class attribute', inject(function($rootScope, $compile) {
|
||||
$rootScope.foo = true;
|
||||
$rootScope.$watch("anything", function() {
|
||||
$rootScope.foo = false;
|
||||
});
|
||||
element = $compile('<div ng-class="{foo:foo}"></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('foo')).toBe(false);
|
||||
}));
|
||||
|
||||
|
||||
it('should update ngClassOdd/Even when model is changed by filtering', inject(function($rootScope, $compile) {
|
||||
element = $compile('<ul>' +
|
||||
'<li ng-repeat="i in items" ' +
|
||||
|
||||
@@ -36,6 +36,36 @@ describe('ngSwitch', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should show all switch-whens that match the current value', inject(function($rootScope, $compile) {
|
||||
element = $compile(
|
||||
'<ul ng-switch="select">' +
|
||||
'<li ng-switch-when="1">first:{{name}}</li>' +
|
||||
'<li ng-switch-when="1">, first too:{{name}}</li>' +
|
||||
'<li ng-switch-when="2">second:{{name}}</li>' +
|
||||
'<li ng-switch-when="true">true:{{name}}</li>' +
|
||||
'</ul>')($rootScope);
|
||||
expect(element.html()).toEqual('<!-- ngSwitchWhen: 1 -->' +
|
||||
'<!-- ngSwitchWhen: 1 -->' +
|
||||
'<!-- ngSwitchWhen: 2 -->' +
|
||||
'<!-- ngSwitchWhen: true -->');
|
||||
$rootScope.select = 1;
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('first:, first too:');
|
||||
$rootScope.name="shyam";
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('first:shyam, first too:shyam');
|
||||
$rootScope.select = 2;
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('second:shyam');
|
||||
$rootScope.name = 'misko';
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('second:misko');
|
||||
$rootScope.select = true;
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('true:misko');
|
||||
}));
|
||||
|
||||
|
||||
it('should switch on switch-when-default', inject(function($rootScope, $compile) {
|
||||
element = $compile(
|
||||
'<ng:switch on="select">' +
|
||||
@@ -50,6 +80,21 @@ describe('ngSwitch', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should show all switch-when-default', inject(function($rootScope, $compile) {
|
||||
element = $compile(
|
||||
'<ul ng-switch="select">' +
|
||||
'<li ng-switch-when="1">one</li>' +
|
||||
'<li ng-switch-default>other</li>' +
|
||||
'<li ng-switch-default>, other too</li>' +
|
||||
'</ul>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('other, other too');
|
||||
$rootScope.select = 1;
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toEqual('one');
|
||||
}));
|
||||
|
||||
|
||||
it('should call change on switch', inject(function($rootScope, $compile) {
|
||||
element = $compile(
|
||||
'<ng:switch on="url" change="name=\'works\'">' +
|
||||
|
||||
@@ -429,7 +429,7 @@ describe('ngView', function() {
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.load).toHaveBeenCalledOnce();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should set $scope and $controllerController on the view', function() {
|
||||
@@ -459,4 +459,28 @@ describe('ngView', function() {
|
||||
expect(div.controller()).toBe($route.current.scope.ctrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not set $scope or $controllerController on top level text elements in the view', function() {
|
||||
function MyCtrl($scope) {}
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/foo', {templateUrl: 'tpl.html', controller: MyCtrl});
|
||||
});
|
||||
|
||||
inject(function($templateCache, $location, $rootScope, $route) {
|
||||
$templateCache.put('tpl.html', '<div></div> ');
|
||||
$location.url('/foo');
|
||||
$rootScope.$digest();
|
||||
|
||||
forEach(element.contents(), function(node) {
|
||||
if ( node.nodeType == 3 ) {
|
||||
expect(jqLite(node).scope()).not.toBe($route.current.scope);
|
||||
expect(jqLite(node).controller()).not.toBeDefined();
|
||||
} else {
|
||||
expect(jqLite(node).scope()).toBe($route.current.scope);
|
||||
expect(jqLite(node).controller()).toBeDefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,4 +66,57 @@ describe('Filter: filter', function() {
|
||||
expect(filter(items, '!isk').length).toBe(1);
|
||||
expect(filter(items, '!isk')[0]).toEqual(items[1]);
|
||||
});
|
||||
|
||||
describe('should support comparator', function() {
|
||||
|
||||
it('as equality when true', function() {
|
||||
var items = ['misko', 'adam', 'adamson'];
|
||||
var expr = 'adam';
|
||||
expect(filter(items, expr, true)).toEqual([items[1]]);
|
||||
expect(filter(items, expr, false)).toEqual([items[1], items[2]]);
|
||||
|
||||
var items = [
|
||||
{key: 'value1', nonkey: 1},
|
||||
{key: 'value2', nonkey: 2},
|
||||
{key: 'value12', nonkey: 3},
|
||||
{key: 'value1', nonkey:4},
|
||||
{key: 'Value1', nonkey:5}
|
||||
];
|
||||
var expr = {key: 'value1'};
|
||||
expect(filter(items, expr, true)).toEqual([items[0], items[3]]);
|
||||
|
||||
var items = [
|
||||
{key: 1, nonkey: 1},
|
||||
{key: 2, nonkey: 2},
|
||||
{key: 12, nonkey: 3},
|
||||
{key: 1, nonkey:4}
|
||||
];
|
||||
var expr = { key: 1 };
|
||||
expect(filter(items, expr, true)).toEqual([items[0], items[3]]);
|
||||
|
||||
var expr = 12;
|
||||
expect(filter(items, expr, true)).toEqual([items[2]]);
|
||||
});
|
||||
|
||||
it('and use the function given to compare values', function() {
|
||||
var items = [
|
||||
{key: 1, nonkey: 1},
|
||||
{key: 2, nonkey: 2},
|
||||
{key: 12, nonkey: 3},
|
||||
{key: 1, nonkey:14}
|
||||
];
|
||||
var expr = {key: 10};
|
||||
var comparator = function (obj,value) {
|
||||
return obj > value;
|
||||
}
|
||||
expect(filter(items, expr, comparator)).toEqual([items[2]]);
|
||||
|
||||
expr = 10;
|
||||
expect(filter(items, expr, comparator)).toEqual([items[2], items[3]]);
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -71,6 +71,17 @@ describe('filters', function() {
|
||||
var num = formatNumber(123.1116, pattern, ',', '.');
|
||||
expect(num).toBe('123.112');
|
||||
});
|
||||
|
||||
it('should format the same with string as well as numeric fractionSize', function(){
|
||||
var num = formatNumber(123.1, pattern, ',', '.', "0");
|
||||
expect(num).toBe('123');
|
||||
var num = formatNumber(123.1, pattern, ',', '.', 0);
|
||||
expect(num).toBe('123');
|
||||
var num = formatNumber(123.1, pattern, ',', '.', "3");
|
||||
expect(num).toBe('123.100');
|
||||
var num = formatNumber(123.1, pattern, ',', '.', 3);
|
||||
expect(num).toBe('123.100');
|
||||
});
|
||||
});
|
||||
|
||||
describe('currency', function() {
|
||||
@@ -162,9 +173,9 @@ describe('filters', function() {
|
||||
|
||||
describe('date', function() {
|
||||
|
||||
var morning = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.000Z'); //7am
|
||||
var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.000Z'); //12pm
|
||||
var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.000Z'); //12am
|
||||
var morning = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.001Z'); //7am
|
||||
var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.012Z'); //12pm
|
||||
var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.123Z'); //12am
|
||||
var earlyDate = new angular.mock.TzDate(+5, '0001-09-03T05:05:08.000Z');
|
||||
|
||||
var date;
|
||||
@@ -192,14 +203,23 @@ describe('filters', function() {
|
||||
expect(date(morning, "yy-MM-dd HH:mm:ss")).
|
||||
toEqual('10-09-03 07:05:08');
|
||||
|
||||
expect(date(morning, "yy-MM-dd HH:mm:ss.sss")).
|
||||
toEqual('10-09-03 07:05:08.001');
|
||||
|
||||
expect(date(midnight, "yyyy-M-d h=H:m:saZ")).
|
||||
toEqual('2010-9-3 12=0:5:8AM0500');
|
||||
toEqual('2010-9-3 12=0:5:8AM-0500');
|
||||
|
||||
expect(date(midnight, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=00:05:08AM0500');
|
||||
toEqual('2010-09-03 12=00:05:08AM-0500');
|
||||
|
||||
expect(date(midnight, "yyyy-MM-dd hh=HH:mm:ss.sssaZ")).
|
||||
toEqual('2010-09-03 12=00:05:08.123AM-0500');
|
||||
|
||||
expect(date(noon, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=12:05:08PM0500');
|
||||
toEqual('2010-09-03 12=12:05:08PM-0500');
|
||||
|
||||
expect(date(noon, "yyyy-MM-dd hh=HH:mm:ss.sssaZ")).
|
||||
toEqual('2010-09-03 12=12:05:08.012PM-0500');
|
||||
|
||||
expect(date(noon, "EEE, MMM d, yyyy")).
|
||||
toEqual('Fri, Sep 3, 2010');
|
||||
@@ -211,14 +231,30 @@ describe('filters', function() {
|
||||
toEqual('September 03, 1');
|
||||
});
|
||||
|
||||
it('should format timezones correctly (as per ISO_8601)', function() {
|
||||
//Note: TzDate's first argument is offset, _not_ timezone.
|
||||
var utc = new angular.mock.TzDate( 0, '2010-09-03T12:05:08.000Z');
|
||||
var eastOfUTC = new angular.mock.TzDate(-5, '2010-09-03T12:05:08.000Z');
|
||||
var westOfUTC = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.000Z');
|
||||
|
||||
expect(date(utc, "yyyy-MM-ddTHH:mm:ssZ")).
|
||||
toEqual('2010-09-03T12:05:08+0000')
|
||||
|
||||
expect(date(eastOfUTC, "yyyy-MM-ddTHH:mm:ssZ")).
|
||||
toEqual('2010-09-03T17:05:08+0500')
|
||||
|
||||
expect(date(westOfUTC, "yyyy-MM-ddTHH:mm:ssZ")).
|
||||
toEqual('2010-09-03T07:05:08-0500')
|
||||
});
|
||||
|
||||
it('should treat single quoted strings as string literals', function() {
|
||||
expect(date(midnight, "yyyy'de' 'a'x'dd' 'adZ' h=H:m:saZ")).
|
||||
toEqual('2010de axdd adZ 12=0:5:8AM0500');
|
||||
toEqual('2010de axdd adZ 12=0:5:8AM-0500');
|
||||
});
|
||||
|
||||
it('should treat a sequence of two single quotes as a literal single quote', function() {
|
||||
expect(date(midnight, "yyyy'de' 'a''dd' 'adZ' h=H:m:saZ")).
|
||||
toEqual("2010de a'dd adZ 12=0:5:8AM0500");
|
||||
toEqual("2010de a'dd adZ 12=0:5:8AM-0500");
|
||||
});
|
||||
|
||||
it('should accept default formats', function() {
|
||||
|
||||
@@ -116,6 +116,9 @@ describe('$httpBackend', function() {
|
||||
};
|
||||
|
||||
this.getAllResponseHeaders = valueFn('');
|
||||
// for temporary Firefox CORS workaround
|
||||
// see https://github.com/angular/angular.js/issues/1468
|
||||
this.getResponseHeader = valueFn('');
|
||||
}
|
||||
|
||||
callback.andCallFake(function(status, response) {
|
||||
|
||||
+19
-3
@@ -144,7 +144,7 @@ describe('$http', function() {
|
||||
|
||||
|
||||
it('should jsonify objects in params map', inject(function($httpBackend, $http) {
|
||||
$httpBackend.expect('GET', '/url?a=1&b=%7B%22c%22%3A3%7D').respond('');
|
||||
$httpBackend.expect('GET', '/url?a=1&b=%7B%22c%22:3%7D').respond('');
|
||||
$http({url: '/url', params: {a:1, b:{c:3}}, method: 'GET'});
|
||||
}));
|
||||
|
||||
@@ -153,6 +153,17 @@ describe('$http', function() {
|
||||
$httpBackend.expect('GET', '/url?a=1&a=2&a=3').respond('');
|
||||
$http({url: '/url', params: {a: [1,2,3]}, method: 'GET'});
|
||||
}));
|
||||
|
||||
|
||||
it('should not encode @ in url params', function() {
|
||||
//encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt
|
||||
//with regards to the character set (pchar) allowed in path segments
|
||||
//so we need this test to make sure that we don't over-encode the params and break stuff
|
||||
//like buzz api which uses @self
|
||||
|
||||
$httpBackend.expect('GET', '/Path?!do%26h=g%3Da+h&:bar=$baz@1').respond('');
|
||||
$http({url: '/Path', params: {':bar': '$baz@1', '!do&h': 'g=a h'}, method: 'GET'});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -453,22 +464,27 @@ describe('$http', function() {
|
||||
|
||||
|
||||
it('should set the XSRF cookie into a XSRF header', inject(function($browser) {
|
||||
function checkXSRF(secret) {
|
||||
function checkXSRF(secret, header) {
|
||||
return function(headers) {
|
||||
return headers['X-XSRF-TOKEN'] == secret;
|
||||
return headers[header || 'X-XSRF-TOKEN'] == secret;
|
||||
};
|
||||
}
|
||||
|
||||
$browser.cookies('XSRF-TOKEN', 'secret');
|
||||
$browser.cookies('aCookie', 'secret2');
|
||||
$httpBackend.expect('GET', '/url', undefined, checkXSRF('secret')).respond('');
|
||||
$httpBackend.expect('POST', '/url', undefined, checkXSRF('secret')).respond('');
|
||||
$httpBackend.expect('PUT', '/url', undefined, checkXSRF('secret')).respond('');
|
||||
$httpBackend.expect('DELETE', '/url', undefined, checkXSRF('secret')).respond('');
|
||||
$httpBackend.expect('GET', '/url', undefined, checkXSRF('secret', 'aHeader')).respond('');
|
||||
$httpBackend.expect('GET', '/url', undefined, checkXSRF('secret2')).respond('');
|
||||
|
||||
$http({url: '/url', method: 'GET'});
|
||||
$http({url: '/url', method: 'POST', headers: {'S-ome': 'Header'}});
|
||||
$http({url: '/url', method: 'PUT', headers: {'Another': 'Header'}});
|
||||
$http({url: '/url', method: 'DELETE', headers: {}});
|
||||
$http({url: '/url', method: 'GET', xsrfHeaderName: 'aHeader'})
|
||||
$http({url: '/url', method: 'GET', xsrfCookieName: 'aCookie'})
|
||||
|
||||
$httpBackend.flush();
|
||||
}));
|
||||
|
||||
@@ -671,6 +671,74 @@ describe('parser', function() {
|
||||
expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('literal', function() {
|
||||
it('should mark scalar value expressions as literal', inject(function($parse) {
|
||||
expect($parse('0').literal).toBe(true);
|
||||
expect($parse('"hello"').literal).toBe(true);
|
||||
expect($parse('true').literal).toBe(true);
|
||||
expect($parse('false').literal).toBe(true);
|
||||
expect($parse('null').literal).toBe(true);
|
||||
expect($parse('undefined').literal).toBe(true);
|
||||
}));
|
||||
|
||||
it('should mark array expressions as literal', inject(function($parse) {
|
||||
expect($parse('[]').literal).toBe(true);
|
||||
expect($parse('[1, 2, 3]').literal).toBe(true);
|
||||
expect($parse('[1, identifier]').literal).toBe(true);
|
||||
}));
|
||||
|
||||
it('should mark object expressions as literal', inject(function($parse) {
|
||||
expect($parse('{}').literal).toBe(true);
|
||||
expect($parse('{x: 1}').literal).toBe(true);
|
||||
expect($parse('{foo: bar}').literal).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not mark function calls or operator expressions as literal', inject(function($parse) {
|
||||
expect($parse('1 + 1').literal).toBe(false);
|
||||
expect($parse('call()').literal).toBe(false);
|
||||
expect($parse('[].length').literal).toBe(false);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('constant', function() {
|
||||
it('should mark scalar value expressions as constant', inject(function($parse) {
|
||||
expect($parse('12.3').constant).toBe(true);
|
||||
expect($parse('"string"').constant).toBe(true);
|
||||
expect($parse('true').constant).toBe(true);
|
||||
expect($parse('false').constant).toBe(true);
|
||||
expect($parse('null').constant).toBe(true);
|
||||
expect($parse('undefined').constant).toBe(true);
|
||||
}));
|
||||
|
||||
it('should mark arrays as constant if they only contain constant elements', inject(function($parse) {
|
||||
expect($parse('[]').constant).toBe(true);
|
||||
expect($parse('[1, 2, 3]').constant).toBe(true);
|
||||
expect($parse('["string", null]').constant).toBe(true);
|
||||
expect($parse('[[]]').constant).toBe(true);
|
||||
expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) {
|
||||
expect($parse('[foo]').constant).toBe(false);
|
||||
expect($parse('[x + 1]').constant).toBe(false);
|
||||
expect($parse('[bar[0]]').constant).toBe(false);
|
||||
}));
|
||||
|
||||
it('should mark complex expressions involving constant values as constant', inject(function($parse) {
|
||||
expect($parse('!true').constant).toBe(true);
|
||||
expect($parse('1 - 1').constant).toBe(true);
|
||||
expect($parse('"foo" + "bar"').constant).toBe(true);
|
||||
expect($parse('5 != null').constant).toBe(true);
|
||||
expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not mark any expression involving variables or function calls as constant', inject(function($parse) {
|
||||
expect($parse('true.toString()').constant).toBe(false);
|
||||
expect($parse('foo(1, 2, 3)').constant).toBe(false);
|
||||
expect($parse('"name" + id').constant).toBe(false);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -99,6 +99,14 @@ describe('Scope', function() {
|
||||
expect(spy).wasCalled();
|
||||
}));
|
||||
|
||||
it('should not keep constant expressions on watch queue', inject(function($rootScope) {
|
||||
$rootScope.$watch('1 + 1', function() {});
|
||||
expect($rootScope.$$watchers.length).toEqual(1);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.$$watchers.length).toEqual(0);
|
||||
}));
|
||||
|
||||
|
||||
it('should delegate exceptions', function() {
|
||||
module(function($exceptionHandlerProvider) {
|
||||
@@ -119,10 +127,14 @@ describe('Scope', function() {
|
||||
var log = '';
|
||||
$rootScope.$watch('a', function() { log += 'a'; });
|
||||
$rootScope.$watch('b', function() { log += 'b'; });
|
||||
// constant expressions have slightly different handling,
|
||||
// let's ensure they are kept in the same list as others
|
||||
$rootScope.$watch('1', function() { log += '1'; });
|
||||
$rootScope.$watch('c', function() { log += 'c'; });
|
||||
$rootScope.$watch('2', function() { log += '2'; });
|
||||
$rootScope.a = $rootScope.b = $rootScope.c = 1;
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('abc');
|
||||
expect(log).toEqual('ab1c2');
|
||||
}));
|
||||
|
||||
|
||||
@@ -216,7 +228,7 @@ describe('Scope', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should prevent infinite recursion and print print watcher function name or body',
|
||||
it('should prevent infinite recursion and print watcher function name or body',
|
||||
inject(function($rootScope) {
|
||||
$rootScope.$watch(function watcherA() {return $rootScope.a;}, function() {$rootScope.b++;});
|
||||
$rootScope.$watch(function() {return $rootScope.b;}, function() {$rootScope.a++;});
|
||||
@@ -328,7 +340,7 @@ describe('Scope', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should always call the watchr with newVal and oldVal equal on the first run',
|
||||
it('should always call the watcher with newVal and oldVal equal on the first run',
|
||||
inject(function($rootScope) {
|
||||
var log = [];
|
||||
function logger(scope, newVal, oldVal) {
|
||||
|
||||
@@ -59,6 +59,63 @@ describe('$route', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should route and fire change event when catch-all params are used', function() {
|
||||
var log = '',
|
||||
lastRoute,
|
||||
nextRoute;
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/Book1/:book/Chapter/:chapter/*highlight/edit',
|
||||
{controller: noop, templateUrl: 'Chapter.html'});
|
||||
$routeProvider.when('/Book2/:book/*highlight/Chapter/:chapter',
|
||||
{controller: noop, templateUrl: 'Chapter.html'});
|
||||
$routeProvider.when('/Blank', {});
|
||||
});
|
||||
inject(function($route, $location, $rootScope) {
|
||||
$rootScope.$on('$routeChangeStart', function(event, next, current) {
|
||||
log += 'before();';
|
||||
expect(current).toBe($route.current);
|
||||
lastRoute = current;
|
||||
nextRoute = next;
|
||||
});
|
||||
$rootScope.$on('$routeChangeSuccess', function(event, current, last) {
|
||||
log += 'after();';
|
||||
expect(current).toBe($route.current);
|
||||
expect(lastRoute).toBe(last);
|
||||
expect(nextRoute).toBe(current);
|
||||
});
|
||||
|
||||
$location.path('/Book1/Moby/Chapter/Intro/one/edit').search('p=123');
|
||||
$rootScope.$digest();
|
||||
$httpBackend.flush();
|
||||
expect(log).toEqual('before();after();');
|
||||
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one', p:'123'});
|
||||
|
||||
log = '';
|
||||
$location.path('/Blank').search('ignore');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('before();after();');
|
||||
expect($route.current.params).toEqual({ignore:true});
|
||||
|
||||
log = '';
|
||||
$location.path('/Book1/Moby/Chapter/Intro/one/two/edit').search('p=123');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('before();after();');
|
||||
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one/two', p:'123'});
|
||||
|
||||
log = '';
|
||||
$location.path('/Book2/Moby/one/two/Chapter/Intro').search('p=123');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('before();after();');
|
||||
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one/two', p:'123'});
|
||||
|
||||
log = '';
|
||||
$location.path('/NONE');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('before();after();');
|
||||
expect($route.current).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not change route when location is canceled', function() {
|
||||
module(function($routeProvider) {
|
||||
|
||||
Vendored
+8
@@ -109,6 +109,13 @@ describe('ngMock', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should fake getMilliseconds method', function() {
|
||||
expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.003Z').getMilliseconds()).toBe(3);
|
||||
expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.023Z').getMilliseconds()).toBe(23);
|
||||
expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.123Z').getMilliseconds()).toBe(123);
|
||||
});
|
||||
|
||||
|
||||
it('should create a date representing new year in Bratislava', function() {
|
||||
var newYearInBratislava = new angular.mock.TzDate(-1, '2009-12-31T23:00:00.000Z');
|
||||
expect(newYearInBratislava.getTimezoneOffset()).toBe(-60);
|
||||
@@ -117,6 +124,7 @@ describe('ngMock', function() {
|
||||
expect(newYearInBratislava.getDate()).toBe(1);
|
||||
expect(newYearInBratislava.getHours()).toBe(0);
|
||||
expect(newYearInBratislava.getMinutes()).toBe(0);
|
||||
expect(newYearInBratislava.getSeconds()).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -119,14 +119,33 @@ describe("resource", function() {
|
||||
|
||||
|
||||
it('should not encode @ in url params', function() {
|
||||
//encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt
|
||||
//with regards to the character set (pchar) allowed in path segments
|
||||
//so we need this test to make sure that we don't over-encode the params and break stuff like
|
||||
//buzz api which uses @self
|
||||
//encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt
|
||||
//with regards to the character set (pchar) allowed in path segments
|
||||
//so we need this test to make sure that we don't over-encode the params and break stuff like
|
||||
//buzz api which uses @self
|
||||
|
||||
var R = $resource('/Path/:a');
|
||||
$httpBackend.expect('GET', '/Path/doh@fo%20o?!do%26h=g%3Da+h&:bar=$baz@1').respond('{}');
|
||||
R.get({a: 'doh@fo o', ':bar': '$baz@1', '!do&h': 'g=a h'});
|
||||
});
|
||||
|
||||
|
||||
it('should encode array params', function() {
|
||||
var R = $resource('/Path/:a');
|
||||
$httpBackend.expect('GET', '/Path/doh@fo%20o?!do%26h=g%3Da+h&:bar=$baz@1').respond('{}');
|
||||
R.get({a: 'doh@fo o', ':bar': '$baz@1', '!do&h': 'g=a h'});
|
||||
$httpBackend.expect('GET', '/Path/doh&foo?bar=baz1&bar=baz2').respond('{}');
|
||||
R.get({a: 'doh&foo', bar: ['baz1', 'baz2']});
|
||||
});
|
||||
|
||||
it('should allow relative paths in resource url', function () {
|
||||
var R = $resource(':relativePath');
|
||||
$httpBackend.expect('GET', 'data.json').respond('{}');
|
||||
R.get({ relativePath: 'data.json' });
|
||||
});
|
||||
|
||||
it('should handle + in url params', function () {
|
||||
var R = $resource('/api/myapp/:myresource?from=:from&to=:to&histlen=:histlen');
|
||||
$httpBackend.expect('GET', '/api/myapp/pear+apple?from=2012-04-01&to=2012-04-29&histlen=3').respond('{}');
|
||||
R.get({ myresource: 'pear+apple', from : '2012-04-01', to : '2012-04-29', histlen : 3 });
|
||||
});
|
||||
|
||||
|
||||
@@ -264,7 +283,7 @@ describe("resource", function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
|
||||
|
||||
var ccs = CreditCard.query({key: 'value'}, callback);
|
||||
expect(ccs).toEqual([]);
|
||||
expect(ccs).toEqualData([]);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
$httpBackend.flush();
|
||||
@@ -420,6 +439,210 @@ describe("resource", function() {
|
||||
});
|
||||
|
||||
|
||||
describe('promise api', function() {
|
||||
|
||||
var $rootScope;
|
||||
|
||||
|
||||
beforeEach(inject(function(_$rootScope_) {
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
|
||||
describe('single resource', function() {
|
||||
|
||||
it('should add promise $then method to the result object', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
|
||||
var cc = CreditCard.get({id: 123});
|
||||
|
||||
cc.$then(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
var response = callback.mostRecentCall.args[0];
|
||||
|
||||
expect(response).toEqualData({
|
||||
data: {id: 123, number: '9876'},
|
||||
status: 200,
|
||||
config: {method: 'GET', data: undefined, url: '/CreditCard/123'},
|
||||
resource: {id: 123, number: '9876', $resolved: true}
|
||||
});
|
||||
expect(typeof response.resource.$save).toBe('function');
|
||||
});
|
||||
|
||||
|
||||
it('should keep $then around after promise resolution', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
|
||||
var cc = CreditCard.get({id: 123});
|
||||
|
||||
cc.$then(callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
var response = callback.mostRecentCall.args[0];
|
||||
|
||||
callback.reset();
|
||||
|
||||
cc.$then(callback);
|
||||
$rootScope.$apply(); //flush async queue
|
||||
|
||||
expect(callback).toHaveBeenCalledOnceWith(response);
|
||||
});
|
||||
|
||||
|
||||
it('should allow promise chaining via $then method', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
|
||||
var cc = CreditCard.get({id: 123});
|
||||
|
||||
cc.$then(function(response) { return 'new value'; }).then(callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(callback).toHaveBeenCalledOnceWith('new value');
|
||||
});
|
||||
|
||||
|
||||
it('should allow error callback registration via $then method', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard/123').respond(404, 'resource not found');
|
||||
var cc = CreditCard.get({id: 123});
|
||||
|
||||
cc.$then(null, callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
var response = callback.mostRecentCall.args[0];
|
||||
|
||||
expect(response).toEqualData({
|
||||
data : 'resource not found',
|
||||
status : 404,
|
||||
config : { method : 'GET', data : undefined, url : '/CreditCard/123' }
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should add $resolved boolean field to the result object', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
|
||||
var cc = CreditCard.get({id: 123});
|
||||
|
||||
expect(cc.$resolved).toBe(false);
|
||||
|
||||
cc.$then(callback);
|
||||
expect(cc.$resolved).toBe(false);
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(cc.$resolved).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should set $resolved field to true when an error occurs', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard/123').respond(404, 'resource not found');
|
||||
var cc = CreditCard.get({id: 123});
|
||||
|
||||
cc.$then(null, callback);
|
||||
$httpBackend.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
expect(cc.$resolved).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('resource collection', function() {
|
||||
|
||||
it('should add promise $then method to the result object', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
|
||||
var ccs = CreditCard.query({key: 'value'});
|
||||
|
||||
ccs.$then(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
var response = callback.mostRecentCall.args[0];
|
||||
|
||||
expect(response).toEqualData({
|
||||
data: [{id: 1}, {id :2}],
|
||||
status: 200,
|
||||
config: {method: 'GET', data: undefined, url: '/CreditCard', params: {key: 'value'}},
|
||||
resource: [ { id : 1 }, { id : 2 } ]
|
||||
});
|
||||
expect(typeof response.resource[0].$save).toBe('function');
|
||||
expect(typeof response.resource[1].$save).toBe('function');
|
||||
});
|
||||
|
||||
|
||||
it('should keep $then around after promise resolution', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
|
||||
var ccs = CreditCard.query({key: 'value'});
|
||||
|
||||
ccs.$then(callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
var response = callback.mostRecentCall.args[0];
|
||||
|
||||
callback.reset();
|
||||
|
||||
ccs.$then(callback);
|
||||
$rootScope.$apply(); //flush async queue
|
||||
|
||||
expect(callback).toHaveBeenCalledOnceWith(response);
|
||||
});
|
||||
|
||||
|
||||
it('should allow promise chaining via $then method', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
|
||||
var ccs = CreditCard.query({key: 'value'});
|
||||
|
||||
ccs.$then(function(response) { return 'new value'; }).then(callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(callback).toHaveBeenCalledOnceWith('new value');
|
||||
});
|
||||
|
||||
|
||||
it('should allow error callback registration via $then method', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond(404, 'resource not found');
|
||||
var ccs = CreditCard.query({key: 'value'});
|
||||
|
||||
ccs.$then(null, callback);
|
||||
$httpBackend.flush();
|
||||
|
||||
var response = callback.mostRecentCall.args[0];
|
||||
|
||||
expect(response).toEqualData({
|
||||
data : 'resource not found',
|
||||
status : 404,
|
||||
config : { method : 'GET', data : undefined, url : '/CreditCard', params: {key: 'value'}}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should add $resolved boolean field to the result object', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
|
||||
var ccs = CreditCard.query({key: 'value'}, callback);
|
||||
|
||||
expect(ccs.$resolved).toBe(false);
|
||||
|
||||
ccs.$then(callback);
|
||||
expect(ccs.$resolved).toBe(false);
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(ccs.$resolved).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should set $resolved field to true when an error occurs', function() {
|
||||
$httpBackend.expect('GET', '/CreditCard?key=value').respond(404, 'resource not found');
|
||||
var ccs = CreditCard.query({key: 'value'});
|
||||
|
||||
ccs.$then(null, callback);
|
||||
$httpBackend.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
expect(ccs.$resolved).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('failure mode', function() {
|
||||
var ERROR_CODE = 500,
|
||||
ERROR_RESPONSE = 'Server Error',
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
describe('ngBindHtml', function() {
|
||||
beforeEach(module('ngSanitize'));
|
||||
|
||||
it('should set html', inject(function($rootScope, $compile) {
|
||||
element = $compile('<div ng-bind-html="html"></div>')($rootScope);
|
||||
var element = $compile('<div ng-bind-html="html"></div>')($rootScope);
|
||||
$rootScope.html = '<div unknown>hello</div>';
|
||||
$rootScope.$digest();
|
||||
expect(angular.lowercase(element.html())).toEqual('<div>hello</div>');
|
||||
@@ -10,7 +13,7 @@ describe('ngBindHtml', function() {
|
||||
|
||||
|
||||
it('should reset html when value is null or undefined', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div ng-bind-html="html"></div>')($rootScope);
|
||||
var element = $compile('<div ng-bind-html="html"></div>')($rootScope);
|
||||
|
||||
angular.forEach([null, undefined, ''], function(val) {
|
||||
$rootScope.html = 'some val';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
describe('angular.scenario.output.json', function() {
|
||||
describe('angular.scenario.output.xml', function() {
|
||||
var output, context;
|
||||
var runner, model, $window;
|
||||
var spec, step;
|
||||
@@ -33,4 +33,17 @@ describe('angular.scenario.output.json', function() {
|
||||
expect(context.find('it').attr('status')).toEqual('success');
|
||||
expect(context.find('it step').attr('status')).toEqual('success');
|
||||
});
|
||||
|
||||
it('should output errors to the XML', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepFailure', spec, step, 'error reason');
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
runner.emit('RunnerEnd');
|
||||
|
||||
expect(context.find('it').attr('status')).toEqual('failure');
|
||||
expect(context.find('it step').attr('status')).toEqual('failure');
|
||||
expect(context.find('it step').text()).toEqual('error reason');
|
||||
});
|
||||
});
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
# AngularJS build config file
|
||||
---
|
||||
version: 1.1.2
|
||||
codename: tofu-animation
|
||||
stable: 1.0.3
|
||||
version: 1.1.3
|
||||
codename: radioactive-gargle
|
||||
stable: 1.0.4
|
||||
|
||||
Reference in New Issue
Block a user