Compare commits

...

38 Commits

Author SHA1 Message Date
Igor Minar 2f128c9619 fix(e2e): add index-nocache.html to run e2e tests without cache
using appcache while running e2e tests was causing the following
problems:
- Safari would occasionally reload the app (as a result of the appcache
  refresh) during the angular.validator.asychronous test, which would
  result in test failure and false positivy.
- Firefox6 would run the tests very slowly, disabling the cache resolved
  the latency issues
- Sometimes tests would run with stale code pulled from cache, which
  would result in flaky tests.
2011-10-03 12:24:29 -07:00
Igor Minar f7a5f1788a fix($route): fix regex escaping in route matcher 2011-09-27 01:45:54 +02:00
Marcello Nuccio b3ed7a8a7a fix($resource): action defaults should override resource defaults
defaults definned per action should take precedence over defaults
defined for the whole resource.

This is potentialy a BREAKING CHANGE in case someone relied on the buggy
behavior.
2011-09-27 00:52:12 +02:00
Igor Minar 62d34e1437 fix(angular-mocks): fix .defer.cancel when i=0 2011-09-16 14:19:05 +02:00
Igor Minar 855971c385 fix(angular-mocks): fix forEach -> angular.forEach in $browser.defer.cancel 2011-09-16 01:52:30 +02:00
Igor Minar 2d489ff936 fix(docs): use window.execScript instead of window.eval on IE
IE's window.eval doesn't execute in the global context, so we have to
use window.execScript instead which works like window.eval on normal
browsers. However execScript throws an exception when an empty string is
passed in, so I created a workaround with a workaround.
2011-09-06 14:58:39 -07:00
Vojta Jina fc5cda2f72 fix($browser.xhr): not convert 0 status to 200 2011-09-06 23:33:29 +02:00
Igor Minar 05ad1ce90c test(coockbook/mvc): disable tic tac toe e2e test
it looks like under certain circumstances the location service doesn't
propagate chages to window.loction. I'm disabling this test now, it is
passing on the master branch (0.10.0)
2011-09-02 22:34:07 -07:00
Igor Minar 5703984d4d test(jsonp): fixing jsonp e2e tests
- buzz api keeps on throttling our requests which makes our build fail
  so I'm disabling the buzz demo e2e test
- the $xhr service jsonp test was modified to use jsonp on angularjs.org
  instead of buzz api for the same reason as mentioned above
2011-09-02 17:10:25 -07:00
Igor Minar ea8952177e fix(test): improve $cookie service test to work with Safari 5.1
the max size for safari cookies has changed sligtly so I had to adjust
the test to make cookie creation fail on this browser
2011-09-02 10:05:06 -07:00
Di Peng 90ca6f983e fix(docs): remove more unecessary use of hide() and show() method
- tutorial section of docs fails to render properly as
doc:tutorial-instructions widget uses deprecated show and hide methods
of jQlite.
2011-08-21 09:41:50 -07:00
Igor Minar 57030bb6b4 chore(version.yaml): preparing version.yaml for 0.9.20 2011-08-21 01:14:16 -07:00
Igor Minar 710a27030e cutting the 0.9.19 canine-psychokinesis release 2011-08-21 01:12:34 -07:00
Igor Minar 19aa16c8d5 fix(docs): work around the lame ng:show directive 2011-08-21 01:12:34 -07:00
Igor Minar 4a1972c71b fix(docs): change docs.css to avoid css clashes in buzz example 2011-08-21 01:12:34 -07:00
Igor Minar 6aa04b1db4 fix(ng:options): remove memory leak caused by scope.
$new can't be used for creation of temporary scopes because it registers
an onEval listener that doesn't go away and keeps the scope around, we
must use inherit(scope) instead to avoid this issue.

The issue does not apply to the master branch with the new scope which
has a scope descructor to clean up this mess.
2011-08-21 01:12:34 -07:00
Di Peng ac6e1306ec fix(sample): Fix for jsFiddle integration 2011-08-19 13:29:26 -07:00
Igor Minar e004378d10 feat($route): add reloadOnSearch route param to avoid reloads
In order to avoid unnecesary route reloads when just hashSearch part
of the url changes, it is now possible to disable this behavior by
setting reloadOnSearch param of the route declaration to false.

Closes #354
2011-08-19 12:05:52 -07:00
Karl Seamon 4ec1d8ee86 feat($xhr,$resource): expose response headers in callbacks
all $xhr*, $resource and related mocks now have access to headers from
their callbacks
2011-08-19 01:20:45 -07:00
Karl Seamon c37bfde9eb fix($resource): properly call error callback when resource is called with two arguments 2011-08-19 01:17:20 -07:00
Vojta Jina f6bcbb53f0 feat(test): toHaveBeenCalledOnce jasmine matcher 2011-08-19 01:17:09 -07:00
dandoyon 53a4580d95 doc(sample): Add javascript sandbox integration (jsFiddle)
Change doc_widget.js to:

- render "edit in jsfiddle" button next to all examples
- make opt out certain examples by adding jsfiddle="false" attribute to
  doc:source element
2011-08-19 01:16:56 -07:00
Igor Minar 4c8eaa1eb0 refactor(jqLite): remove jqLite show/hide support
it turns out that even with our tricks, jqLite#show is not usable in
practice and definitely not on par with jQuery. so rather than
introducing half-baked apis which introduce issues, I'm removing them.

I also removed show/hide uses from docs, since they are not needed.

Breaks jqLite.hide/jqLite.show which are no longer available.
2011-08-19 00:59:44 -07:00
Igor Minar 4ba35eb97e chore(jasmine): disable 'Jasmine waiting for..' msg 2011-08-19 00:15:21 -07:00
Di Peng 6fb4bf4c54 fix(directives): make ng:class-even/odd work with ng:class
Closes #508
2011-08-19 00:14:05 -07:00
Misko Hevery cc604b6e26 fix(bootstrap): missing var failed strict mode boot 2011-08-18 23:47:02 -07:00
Vojta Jina 99ee6f275a doc($browser): remove duplication of $browser to docs
This was causing to show up the "$browser" twice in the menu.
2011-08-18 23:46:51 -07:00
Vojta Jina 21c4919a5b doc($browser): hide $browser.notifyWhenNoOustandingRequest method
Closes #506
2011-08-18 23:46:21 -07:00
Di Peng 714759100c refactor(widgets): remove input[button, submit, reset, image] and button windgets
These widgets are useless and only trigger extra $updateViews.

The only reason we had them was to support ng:change on these widgets,
but since there are no bindings present in these cases it doesn't make
sense to support ng:change here. It's likely just a leftover from
getangular.com

Breaking change: ng:change for input[button], input[submit], input[reset], input[image]
and button widgets is not supported any more
2011-08-18 23:44:15 -07:00
Di Peng ee8e981c47 doc(xhr): add e2e test for JSONP error handling
- add e2e tests
- refactor the example by removing clear button and simplifying the code
2011-08-18 23:34:15 -07:00
Di Peng 05e2c3196c feat($browser): JSONP error handling
since we don't know if the error was due to a client error (4xx) or
server error (5xx), we leave the status code as undefined.
2011-08-18 23:33:32 -07:00
Igor Minar 718ebf1fcf doc(tutorial): updates needed for 0.9.18 rebase 2011-08-18 23:33:19 -07:00
DiPeng 2f5d17f3b6 fix(docs): fix qfs.read() encoding issue
- must use binary reading when using read function in q-fs module
otherwise some unicode character may be garbled.

Closes #497
2011-08-18 23:33:10 -07:00
Vojta Jina fd792de9e8 fix($xhr.error): fix docs and add missed breaking change
$xhr.error's first argument (request) has no callback property anymore,
it's called success now...

This breaking change was introduced by b5594a773a
2011-08-18 23:33:00 -07:00
dandoyon a01cf6d39e doc(typos): fix couple of typos in the docs
Minor documentation fixes. Should not be any code changes.
One test changed due to dependency on text in documentation.
2011-08-18 23:24:08 -07:00
Igor Minar f93e9bfa81 prepare the 0.9.19 canine-psychokinesis iteration 2011-08-18 23:24:08 -07:00
Igor Minar 590cd14ae0 fix(Rakefile): index-jq.html needs to be rewritten like index.html 2011-08-18 23:23:41 -07:00
Igor Minar 74db92cd9f doc(release notes): small fixes for the 0.9.18 release 2011-08-18 23:23:27 -07:00
49 changed files with 913 additions and 348 deletions
+40 -6
View File
@@ -1,9 +1,42 @@
<a name="0.9.19"><a/>
# 0.9.19 canine-psychokinesis (2011-08-20) #
## Features
- added error handling support for JSONP requests (see error callback param of the [$xhr] service)
([commit](https://github.com/angular/angular.js/commit/05e2c3196c857402a9aa93837b565e0a2736af23))
- exposed http response headers in the [$xhr] and [$resource] callbacks
([commit](https://github.com/angular/angular.js/commit/4ec1d8ee86e3138fb91543ca0dca28463895c090)
contributed by Karl Seamon)
- added `reloadOnSearch` [$route] param support to prevent unnecessary controller reloads and
resulting flicker
([commit](https://github.com/angular/angular.js/commit/e004378d100ce767a1107180102790a9a360644e))
## Fixes
- make ng:class-even/odd compatible with ng:class
(Issue [#508](https://github.com/angular/angular.js/issues/508))
- fixed error handling for resources that didn't work in certain situations
([commit](https://github.com/angular/angular.js/commit/c37bfde9eb31556ee1eb146795b0c1f1504a4a26)
contributed by Karl Seamon)
## Docs
- [jsFiddle](http://jsfiddle.net/) integration for all docs.angularjs.org examples (contributed by
Dan Doyon).
## Breaking Changes
- removed [jqLite] show/hide support. See the
[commit](https://github.com/angular/angular.js/commit/4c8eaa1eb05ba98d30ff83f4420d6fcd69045d99)
message for details. Developers should use jquery or jqLite's `css('display', 'none')` and
`css('display', 'block'/'inline'/..)` instead
<a name="0.9.18"><a/>
# AngularJS 0.9.18 jiggling-armfat (2011-07-29) #
# 0.9.18 jiggling-armfat (2011-07-29) #
### Features
- made angular(.min).js
[ECMAScript 5 Strict Mode](https://developer.mozilla.org/en/JavaScript/Strict_mode) compliant
- [ECMAScript 5 Strict Mode](https://developer.mozilla.org/en/JavaScript/Strict_mode) compliance
- [jqLite]
- added `show()`, `hide()` and `eq()` methods to jqlite
([commit](https://github.com/angular/angular.js/commit/7a3fdda9650a06792d9278a8cef06d544d49300f))
@@ -59,12 +92,13 @@
- doubled our e2e test suite by running all angular e2e tests with jqLite in addition to jQuery
### Breaking changes:
### Breaking changes
- [commit](https://github.com/angular/angular.js/commit/3af1e7ca2ee8c2acd69e5bcbb3ffc1bf51239285)
removed support for the `MMMMM` (long month name), use `MMMM` instead. This was done to align
Angular with
[Unicode Technical Standard #35](http://unicode.org/reports/tr35/#Date_Format_Patterns) used by
Closure, as well as, future DOM apis currently being proposed to w3c.
- `$xhr.error`'s `request` argument has no `callback` property anymore, use `success` instead
@@ -267,8 +301,7 @@
- many, but by far not all, docs were updated, improved and cleaned up
### Features
- [`$route`](http://docs.angularjs.org/#!/api/angular.service.$route) service now supports these
features:
- [$route] service now supports these features:
- route not found handling via `#otherwise()`
- redirection support via `#when('/foo', {redirectTo: '/bar'})` (including param interpolation)
- setting the parent scope for scopes created by the service via `#parent()`
@@ -627,6 +660,7 @@ with the `$route` service
[$xhr]: http://docs.angularjs.org/#!/api/angular.service.$xhr
[$xhr.cache]: http://docs.angularjs.org/#!/api/angular.service.$xhr.cache
[$resource]: http://docs.angularjs.org/#!/api/angular.service.$resource
[$route]: http://docs.angularjs.org/#!/api/angular.service.$route
[$orderBy]: http://docs.angularjs.org/#!/api/angular.Array.orderBy
[date]: http://docs.angularjs.org/#!/api/angular.filter.date
[number]: http://docs.angularjs.org/#!/api/angular.filter.number
+25
View File
@@ -261,6 +261,31 @@ task :package => [:clean, :compile, :docs] do
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq.html", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-nocache.html", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-nocache.html", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/docs-scenario.html", File::RDWR) do |f|
text = f.read
f.truncate 0
+1 -1
View File
@@ -53,7 +53,7 @@ to retrieve Buzz activity and comments.
</div>
</doc:source>
<doc:scenario>
it('fetch buzz and expand', function() {
xit('fetch buzz and expand', function() {
element(':button:contains(fetch)').click();
expect(repeater('div.buzz').count()).toBeGreaterThan(0);
element('.buzz a:contains(Expand replies):first').click();
+1 -1
View File
@@ -93,7 +93,7 @@ no connection between the controller and the view.
</div>
</doc:source>
<doc:scenario>
it('should play a game', function(){
xit('should play a game', function(){
piece(1, 1);
expect(binding('nextMove')).toEqual('O');
piece(3, 1);
+6 -6
View File
@@ -67,17 +67,17 @@ __`app/js/controllers.js`.__
<pre>
...
function PhoneListCtrl(Phone_) {
function PhoneListCtrl(Phone) {
this.orderProp = 'age';
this.phones = Phone_.query();
this.phones = Phone.query();
}
//PhoneListCtrl.$inject = ['Phone'];
function PhoneDetailCtrl(Phone_) {
function PhoneDetailCtrl(Phone) {
var self = this;
self.phone = Phone_.get({phoneId: self.params.phoneId}, function(phone) {
self.phone = Phone.get({phoneId: self.params.phoneId}, function(phone) {
self.mainImageUrl = phone.images[0];
});
@@ -94,7 +94,7 @@ Notice how in `PhoneListCtrl` we replaced:
with:
this.phones = Phone_.query();
this.phones = Phone.query();
This is a simple statement that we want to query for all phones.
@@ -116,7 +116,7 @@ We have modified our unit tests to verify that our new service is issuing HTTP r
processing them as expected. The tests also check that our controllers are interacting with the
service correctly.
The {@link api/angular.service.$resource $resource} client augments the response object with
The {@link api/angular.service.$resource $resource} service augments the response object with
methods for updating and deleting the resource. If we were to use the standard `toEqual` matcher,
our tests would fail because the test values would not match the responses exactly. To solve the
problem, we use a newly-defined `toEqualData` {@link
+8
View File
@@ -89,6 +89,14 @@ describe('ngdoc', function(){
'<pre class="doc-source">\n&lt;&gt;\n</pre></doc:example><p>after</p>');
});
it('should preserve the jsfiddle attribute', function(){
var doc = new Doc('@description before <doc:example>' +
'<doc:source jsfiddle="foo">lala</doc:source></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<pre class="doc-source" jsfiddle="foo">lala</pre></doc:example><p>after</p>');
});
it('should escape <doc:scenario> element', function(){
var doc = new Doc('@description before <doc:example>' +
'<doc:scenario>\n<>\n</doc:scenario></doc:example> after');
+11 -4
View File
@@ -43,10 +43,17 @@ function writeTheRest(writesFuture) {
writesFuture.push(writer.copyDir('img'));
writesFuture.push(writer.copyDir('examples'));
writesFuture.push(writer.copyTpl('index.html'));
writesFuture.push(writer.copy('docs/src/templates/index.html',
'build/docs/index-jq.html',
'<!-- jquery place holder -->',
'<script src=\"jquery.min.js\"><\/script>'));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq.html',
'<!-- jquery place holder -->', '<script src=\"jquery.min.js\"><\/script>'));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-nocache.html',
'manifest="appcache.manifest"', ''));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq-nocache.html',
'manifest="appcache.manifest"', '',
'<!-- jquery place holder -->', '<script src=\"jquery.min.js\"><\/script>'));
writesFuture.push(writer.copyTpl('offline.html'));
writesFuture.push(writer.copyTpl('docs-scenario.html'));
writesFuture.push(writer.copyTpl('jquery.min.js'));
+9 -7
View File
@@ -111,9 +111,11 @@ Doc.prototype = {
'</pre></div>';
});
} else if (isDocWidget('example')) {
text = text.replace(/(<doc:source>)([\s\S]*)(<\/doc:source>)/mi,
function(_, before, content, after){
return '<pre class="doc-source">' + htmlEscape(content) + '</pre>';
text = text.replace(/<doc:source(\s+jsfiddle="[^"]+")?>([\s\S]*)<\/doc:source>/mi,
function(_, jsfiddle, content){
return '<pre class="doc-source"' + (jsfiddle || '') +'>' +
htmlEscape(content) +
'</pre>';
});
text = text.replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi,
function(_, before, content, after){
@@ -547,15 +549,15 @@ Doc.prototype = {
function scenarios(docs){
var specs = [];
specs.push('describe("angular without jquery", function() {');
appendSpecs('index.html');
specs.push('describe("angular+jqlite", function() {');
appendSpecs('index-nocache.html');
specs.push('});');
specs.push('');
specs.push('');
specs.push('describe("angular with jquery", function() {');
appendSpecs('index-jq.html');
specs.push('describe("angular+jquery", function() {');
appendSpecs('index-jq-nocache.html');
specs.push('});');
return specs.join('\n');
+2 -2
View File
@@ -24,7 +24,7 @@ function collect() {
var work;
if(/\.js$/.test(file)) {
console.log("reading " + file + ".......");
work = Q.when(qfs.read(file), function(content) {
work = Q.when(qfs.read(file, 'b'), function(content) {
processJsFile(content, file).forEach (function(doc) {
allDocs.push(doc);
});
@@ -45,7 +45,7 @@ function collect() {
var work2;
if (file.match(/\.ngdoc$/)) {
console.log("reading " + file + ".......");
work2 = Q.when(qfs.read(file), function(content){
work2 = Q.when(qfs.read(file, 'b'), function(content){
var section = '@section ' + file.split('/')[2] + '\n';
allDocs.push(new ngdoc.Doc(section + content.toString(),file, 1).parse());
});
+31 -6
View File
@@ -1,9 +1,3 @@
@namespace doc url("http://docs.angularjs.org/");
doc\:example {
display: none;
}
ul.doc-example {
list-style-type: none;
position: relative;
@@ -25,6 +19,37 @@ ul.doc-example > li.doc-example-heading {
margin-bottom: -10px;
}
span.nojsfiddle {
float: right;
font-size: 14px;
margin-right:10px;
margin-top: 10px;
}
form.jsfiddle {
position: absolute;
right: 0;
z-index: 1;
height: 14px;
}
form.jsfiddle button {
cursor: pointer;
padding: 4px 10px;
margin: 10px;
background-color: #FFF;
font-weight: bold;
color: #7989D6;
border-color: #7989D6;
-moz-border-radius: 8px;
-webkit-border-radius:8px;
border-radius: 8px;
}
form.jsfiddle textarea, form.jsfiddle input {
display: none;
}
li.doc-example-live {
padding: 10px;
font-size: 1.2em;
+48 -6
View File
@@ -23,12 +23,12 @@
angular.widget('doc:example', function(element){
this.descend(true); //compile the example code
element.hide();
//jQuery find() methods in this widget contain primitive selectors on purpose so that we can use
//jqlite instead. jqlite's find() method currently supports onlt getElementsByTagName!
var example = element.find('pre').eq(0), //doc-source
exampleSrc = example.text(),
jsfiddle = example.attr('jsfiddle') || true,
scenario = element.find('pre').eq(1); //doc-scenario
var code = indent(exampleSrc);
@@ -36,7 +36,8 @@
'<ul class="doc-example">' +
'<li class="doc-example-heading"><h3>Source</h3></li>' +
'<li class="doc-example-source" ng:non-bindable>' +
'<pre class="brush: js; html-script: true; highlight: [' +
jsFiddleButton(jsfiddle) + // may or may not have value
'<pre class="brush: js; html-script: true; highlight: [' +
code.hilite + ']; toolbar: false;"></pre></li>' +
'<li class="doc-example-heading"><h3>Live Preview</h3></li>' +
'<li class="doc-example-live">' + exampleSrc +'</li>';
@@ -53,14 +54,57 @@
element.html('');
element.append(tabs);
element.show();
var script = (exampleSrc.match(/<script[^\>]*>([\s\S]*)<\/script>/) || [])[1] || '';
try {
window.eval(script);
if (window.execScript) { // IE
window.execScript(script || '"stupid IE!"'); // IE complains when evaling empty string
} else {
window.eval(script);
}
} catch (e) {
alert(e);
}
function jsFiddleButton(jsfiddle) {
if (jsfiddle !== 'false') {
if(jsfiddle == true) {
//dynamically generate a fiddle
var fiddleUrl = 'http://jsfiddle.net/api/post/library/pure/',
fiddleSrc = exampleSrc,
stripIndent = fiddleSrc.match(/^(\s*)/)[1].length;
//escape closing textarea
fiddleSrc = fiddleSrc.replace(/<\/textarea>/gi,'&lt;/textarea&gt;')
//strip extra indentation
fiddleSrc = fiddleSrc.replace(new RegExp('^\\s{' + stripIndent + '}', 'gm'), '');
return '<form class="jsfiddle" method="post" action="' + fiddleUrl + '" target="_blank">' +
'<textarea name="css">' +
'body { font-family: Arial,Helvetica,sans-serif; }\n' +
'body, td, th { font-size: 14px; margin: 0; }\n' +
'table { border-collapse: separate; border-spacing: 2px; display: table; margin-bottom: 0; margin-top: 0; -moz-box-sizing: border-box; text-indent: 0; }\n' +
'a:link, a:visited, a:hover { color: #5D6DB6; text-decoration: none; }\n' +
'</textarea>' +
'<input type="text" name="title" value="AngularJS Live Example">' +
'<textarea name="html">' +
'<script src="' + angularJsUrl + '" ng:autobind></script>\n\n' +
'<!-- AngularJS Example Code: -->\n\n' +
fiddleSrc +
'</textarea>' +
'<button>edit at jsFiddle</button>' +
'</form>';
} else {
//use existing fiddle
fiddleUrl = "http://jsfiddle.net" + jsfiddle;
return '<form class="jsfiddle" method="get" action="' + fiddleUrl + '" target="_blank">' +
'<button>edit at jsFiddle</button>' +
'</form>';
}
}
return '';
}
});
function indent(text) {
@@ -142,7 +186,6 @@
'</div>';
angular.widget('doc:tutorial-instructions', function(element) {
element.hide();
this.descend(true);
var tabs = angular.element(HTML_TPL.replace('{show}', element.attr('show') || 'false')),
@@ -168,7 +211,6 @@
element.html('');
element.append(tabs);
element.show();
});
+2 -6
View File
@@ -289,7 +289,8 @@ li {
}
#content img {
#content img:not(.ng-directive) {
/* the negation rule above is to avoid applying this rule to images in buzz and other examples */
display: block;
margin: 2em auto;
padding: 1em;
@@ -327,7 +328,6 @@ li {
/* subpages */
#fader {
display: none;
position: fixed;
top: 0px;
left: 0px;
@@ -340,10 +340,6 @@ li {
z-index: 3;
}
#subpage {
display: none;
}
#subpage > div {
position: fixed;
top: 50%;
+2 -2
View File
@@ -83,8 +83,8 @@
</div>
</div>
<div id="fader" ng:show="subpage"></div>
<div id="subpage" ng:show="subpage">
<div id="fader" ng:show="subpage" style="display: none"></div>
<div id="subpage" ng:show="subpage" style="display: none">
<div>
<h2>Would you like full offline support for this AngularJS Docs App?</h2>
<a ng:click="subpage=false">&#10005;</a>
+17 -4
View File
@@ -44,12 +44,25 @@ exports.copyTpl = function(filename) {
return exports.copy('docs/src/templates/' + filename, OUTPUT_DIR + filename);
};
exports.copy = function (from, to, replacementKey, replacement) {
exports.copy = function (from, to) {
var args = [].slice.call(arguments);
args.shift(); // drop 'from'
args.shift(); // drop 'to'
// Have to use rb (read binary), char 'r' is infered by library.
return qfs.read(from,'b').then(function(content) {
if(replacementKey && replacement) {
content = content.toString().replace(replacementKey, replacement);
var replacementKey,
replacement;
while (args.length) {
replacementKey = args.shift();
replacement = args.shift();
if(replacementKey != undefined && replacement != undefined) {
content = content.toString().replace(replacementKey, replacement);
}
}
qfs.write(to, content);
});
}
@@ -83,7 +96,7 @@ function merge(srcs, to) {
srcs.forEach(function (src) {
done = Q.when(done, function(content) {
if(content) contents.push(content);
return qfs.read(src);
return qfs.read(src, 'b');
});
});
+2 -1
View File
@@ -2200,7 +2200,8 @@ jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
// (i): disabled this log since its annoying
//this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
var latchFunctionResult;
try {
latchFunctionResult = this.latchFunction.apply(this.spec);
+2 -3
View File
@@ -64,7 +64,6 @@ var _undefined = undefined,
$boolean = 'boolean',
$console = 'console',
$date = 'date',
$display = 'display',
$element = 'element',
$function = 'function',
$length = 'length',
@@ -145,7 +144,7 @@ var _undefined = undefined,
* @param {Object|Array} obj Object to iterate over.
* @param {function()} iterator Iterator function.
* @param {Object} context Object to become context (`this`) for the iterator function.
* @returns {Objet|Array} Reference to `obj`.
* @returns {Object|Array} Reference to `obj`.
*/
function forEach(obj, iterator, context) {
var key;
@@ -875,7 +874,7 @@ function toKeyValue(obj) {
/**
* we need our custom mehtod because encodeURIComponent is too agressive and doesn't follow
* We need our custom mehtod because 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:
* segment = *pchar
+1 -9
View File
@@ -1,15 +1,7 @@
'use strict';
var browserSingleton;
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$browser
* @requires $log
*
* @description
* Represents the browser.
*/
angularService('$browser', function($log){
if (!browserSingleton) {
browserSingleton = new Browser(window, jqLite(window.document), jqLite(window.document.body),
+59 -14
View File
@@ -84,7 +84,9 @@ function Browser(window, document, body, XHR, $log) {
* @param {string} method Requested method (get|post|put|delete|head|json)
* @param {string} url Requested url
* @param {?string} post Post data to send (null if nothing to post)
* @param {function(number, string)} callback Function that will be called on response
* @param {function(number, string, function([string]))} callback Function that will be called on
* response. The third argument is a function that can be called to return a specified response
* header or an Object containing all headers (when called with no arguments).
* @param {object=} header additional HTTP headers to send with XHR.
* Standard headers are:
* <ul>
@@ -97,15 +99,24 @@ function Browser(window, document, body, XHR, $log) {
* Send ajax request
*/
self.xhr = function(method, url, post, callback, headers) {
var parsedHeaders;
outstandingRequestCount ++;
if (lowercase(method) == 'json') {
var callbackId = ("angular_" + Math.random() + '_' + (idCounter++)).replace(/\d\./, '');
var script = self.addJs(url.replace('JSON_CALLBACK', callbackId));
window[callbackId] = function(data){
window[callbackId] = function(data) {
window[callbackId].data = data;
};
var script = self.addJs(url.replace('JSON_CALLBACK', callbackId), null, function() {
if (window[callbackId].data) {
completeOutstandingRequest(callback, 200, window[callbackId].data);
} else {
completeOutstandingRequest(callback);
}
delete window[callbackId];
body[0].removeChild(script);
completeOutstandingRequest(callback, 200, data);
};
});
} else {
var xhr = new XHR();
xhr.open(method, url, true);
@@ -115,8 +126,35 @@ function Browser(window, document, body, XHR, $log) {
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
var status = xhr.status == 1223 ? 204 : xhr.status || 200;
completeOutstandingRequest(callback, status, xhr.responseText);
var status = xhr.status == 1223 ? 204 : xhr.status;
completeOutstandingRequest(callback, status, xhr.responseText, function(header) {
header = lowercase(header);
if (header) {
return parsedHeaders
? parsedHeaders[header] || null
: xhr.getResponseHeader(header);
} else {
// Return an object containing each response header
parsedHeaders = {};
forEach(xhr.getAllResponseHeaders().split('\n'), function(line) {
var i = line.indexOf(':'),
key = lowercase(trim(line.substr(0, i))),
value = trim(line.substr(i + 1));
if (parsedHeaders[key]) {
// Combine repeated headers
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
parsedHeaders[key] += ', ' + value;
} else {
parsedHeaders[key] = value;
}
});
return parsedHeaders;
}
});
}
};
xhr.send(post || '');
@@ -124,11 +162,9 @@ function Browser(window, document, body, XHR, $log) {
};
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#notifyWhenNoOutstandingRequests
* @methodOf angular.service.$browser
*
* @private
* Note: this method is used only by scenario runner
* TODO(vojta): prefix this method with $$ ?
* @param {function()} callback Function that will be called when no outstanding request
*/
self.notifyWhenNoOutstandingRequests = function(callback) {
@@ -239,7 +275,7 @@ function Browser(window, document, body, XHR, $log) {
* The listener gets called with either HashChangeEvent object or simple object that also contains
* `oldURL` and `newURL` properties.
*
* NOTE: this api is intended for use only by the $location service. Please use the
* Note: this api is intended for use only by the $location service. Please use the
* {@link angular.service.$location $location service} to monitor hash changes in angular apps.
*
* @param {function(event)} listener Listener function to be called when url hash changes.
@@ -452,7 +488,7 @@ function Browser(window, document, body, XHR, $log) {
* @description
* Adds a script tag to the head.
*/
self.addJs = function(url, domId) {
self.addJs = function(url, domId, done) {
// we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
// - fetches local scripts via XHR and evals them
// - adds and immediately removes script elements from the document
@@ -465,6 +501,15 @@ function Browser(window, document, body, XHR, $log) {
script.type = 'text/javascript';
script.src = url;
if (domId) script.id = domId;
if (msie) {
script.onreadystatechange = function() {
/loaded|complete/.test(script.readyState) && done && done();
};
} else {
if (done) script.onload = script.onerror = done;
}
body[0].appendChild(script);
return script;
+3 -3
View File
@@ -89,7 +89,7 @@ Template.prototype = {
* The compilation is a process of walking the DOM tree and trying to match DOM elements to
* {@link angular.markup markup}, {@link angular.attrMarkup attrMarkup},
* {@link angular.widget widgets}, and {@link angular.directive directives}. For each match it
* executes coresponding markup, attrMarkup, widget or directive template function and collects the
* executes corresponding markup, attrMarkup, widget or directive template function and collects the
* instance functions into a single template function which is then returned.
*
* The template function can then be used once to produce the view or as it is the case with
@@ -118,7 +118,7 @@ Template.prototype = {
* root scope is created.
* * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
* `template` and call the `cloneAttachFn` function allowing the caller to attach the
* cloned elements to the DOM document at the approriate place. The `cloneAttachFn` is
* cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
* called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
*
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
@@ -234,7 +234,7 @@ Compiler.prototype = {
* not a problem, but under some circumstances the values for data
* is not available until after the full view is computed. If such
* values are needed before they are computed the order of
* evaluation can be change using ng:eval-order
* evaluation can be changed using ng:eval-order
*
* @element ANY
* @param {integer|string=} [priority=0] priority integer, or FIRST, LAST constant
+10 -3
View File
@@ -76,9 +76,16 @@ ResourceFactory.prototype = {
case 4:
error = a4;
success = a3;
//fallthrough
case 3:
case 2:
if (isFunction(a2)) {
if (isFunction(a1)) {
success = a1;
error = a2;
break;
}
success = a2;
error = a3;
//fallthrough
@@ -102,9 +109,9 @@ ResourceFactory.prototype = {
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
self.xhr(
action.method,
route.url(extend({}, action.params || {}, extractParams(data), params)),
route.url(extend({}, extractParams(data), action.params || {}, params)),
data,
function(status, response) {
function(status, response, responseHeaders) {
if (response) {
if (action.isArray) {
value.length = 0;
@@ -115,7 +122,7 @@ ResourceFactory.prototype = {
copy(response, value);
}
}
(success||noop)(value);
(success||noop)(value, responseHeaders);
},
error || action.verifyCache,
action.verifyCache);
+3 -3
View File
@@ -9,7 +9,7 @@ function getter(instance, path, unboundFn) {
for ( var i = 0; i < len; i++) {
key = element[i];
if (!key.match(/^[\$\w][\$\w\d]*$/))
throw "Expression '" + path + "' is not a valid expression for accesing variables.";
throw "Expression '" + path + "' is not a valid expression for accessing variables.";
if (instance) {
lastInstance = instance;
instance = instance[key];
@@ -202,7 +202,7 @@ function createScope(parent, providers, instanceCache) {
* @description
* Assigns a value to a property of the current scope specified via `property_chain`. Unlike in
* JavaScript, if there are any `undefined` intermediary properties, empty objects are created
* and assigned in to them instead of throwing an exception.
* and assigned to them instead of throwing an exception.
*
<pre>
var scope = angular.scope();
@@ -368,7 +368,7 @@ function createScope(parent, providers, instanceCache) {
* parameters, `newValue` and `oldValue`.
* @param {(function()|DOMElement)=} [exceptionHanlder=angular.service.$exceptionHandler] Handler
* that gets called when `watchExp` or `listener` throws an exception. If a DOMElement is
* specified as handler, the element gets decorated by angular with the information about the
* specified as a handler, the element gets decorated by angular with the information about the
* exception.
* @param {boolean=} [initRun=true] Flag that prevents the first execution of the listener upon
* registration.
+1 -1
View File
@@ -76,7 +76,7 @@
// load the js scripts
for (i in Array.prototype.slice.call(arguments, 0)) {
file = arguments[i];
var file = arguments[i];
document.write('<script type="text/javascript" src="' + serverPath + file + '" ' +
'onload="angularClobberTest(\'' + file + '\')"></script>');
}
+28 -11
View File
@@ -152,7 +152,14 @@ function MockBrowser() {
throw new Error("Missing HTTP request header: " + key + ": " + value);
}
});
callback(expectation.code, expectation.response);
callback(expectation.code, expectation.response, function(header) {
if (header) {
header = header.toLowerCase();
return expectation.responseHeaders && expectation.responseHeaders[header] || null;
} else {
return expectation.responseHeaders || {};
}
});
});
};
self.xhr.expectations = expectations;
@@ -162,12 +169,22 @@ function MockBrowser() {
if (data && angular.isString(data)) url += "|" + data;
var expect = expectations[method] || (expectations[method] = {});
return {
respond: function(code, response) {
respond: function(code, response, responseHeaders) {
if (!angular.isNumber(code)) {
responseHeaders = response;
response = code;
code = 200;
}
expect[url] = {code:code, response:response, headers: headers || {}};
angular.forEach(responseHeaders, function(value, key) {
delete responseHeaders[key];
responseHeaders[key.toLowerCase()] = value;
});
expect[url] = {
code: code,
response: response,
headers: headers || {},
responseHeaders: responseHeaders || {}
};
}
};
};
@@ -268,7 +285,7 @@ function MockBrowser() {
self.defer = function(fn, delay) {
delay = delay || 0;
self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId});
self.deferredFns.sort(function(a,b){ return a.time - b.time;});
self.deferredFns.sort(function(a,b){return a.time - b.time;});
return self.deferredNextId++;
};
@@ -279,11 +296,11 @@ function MockBrowser() {
self.defer.cancel = function(deferId) {
var fnIndex;
forEach(self.deferredFns, function(fn, index) {
angular.forEach(self.deferredFns, function(fn, index) {
if (fn.id === deferId) fnIndex = index;
});
if (fnIndex) {
if (fnIndex !== undefined) {
self.deferredFns.splice(fnIndex, 1);
}
};
@@ -374,7 +391,7 @@ angular.service('$browser', function(){
* See {@link angular.mock} for more info on angular mocks.
*/
angular.service('$exceptionHandler', function() {
return function(e) { throw e;};
return function(e) {throw e;};
});
@@ -394,10 +411,10 @@ angular.service('$log', MockLogFactory);
function MockLogFactory() {
var $log = {
log: function(){ $log.log.logs.push(arguments); },
warn: function(){ $log.warn.logs.push(arguments); },
info: function(){ $log.info.logs.push(arguments); },
error: function(){ $log.error.logs.push(arguments); }
log: function(){$log.log.logs.push(arguments);},
warn: function(){$log.warn.logs.push(arguments);},
info: function(){$log.info.logs.push(arguments);},
error: function(){$log.error.logs.push(arguments);}
};
$log.log.logs = [];
+10 -5
View File
@@ -265,7 +265,7 @@ angularDirective("ng:bind", function(expression, element){
error = formatError(e);
});
this.$element = oldElement;
// If we are HTML than save the raw HTML data so that we don't
// If we are HTML then save the raw HTML data so that we don't
// recompute sanitization since it is expensive.
// TODO: turn this into a more generic way to compute this
if (isHtml = (value instanceof HTML))
@@ -571,9 +571,14 @@ function ngClass(selector) {
var existing = element[0].className + ' ';
return function(element){
this.$onEval(function(){
if (selector(this.$index)) {
var value = this.$eval(expression);
var scope = this;
if (selector(scope.$index)) {
var ngClassVal = scope.$eval(element.attr('ng:class') || '');
if (isArray(ngClassVal)) ngClassVal = ngClassVal.join(' ');
var value = scope.$eval(expression);
if (isArray(value)) value = value.join(' ');
if (ngClassVal && ngClassVal !== value) value = value + ' ' + ngClassVal;
element[0].className = trim(existing + value);
}
}, element);
@@ -733,7 +738,7 @@ angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));
angularDirective("ng:show", function(expression, element){
return function(element){
this.$onEval(function(){
toBoolean(this.$eval(expression)) ? element.show() : element.hide();
element.css('display', toBoolean(this.$eval(expression)) ? '' : 'none');
}, element);
};
});
@@ -774,7 +779,7 @@ angularDirective("ng:show", function(expression, element){
angularDirective("ng:hide", function(expression, element){
return function(element){
this.$onEval(function(){
toBoolean(this.$eval(expression)) ? element.hide() : element.show();
element.css('display', toBoolean(this.$eval(expression)) ? 'none' : '');
}, element);
};
});
+5 -5
View File
@@ -40,10 +40,10 @@
*
* @param {number} amount Input to filter.
* @param {string=} symbol Currency symbol or identifier to be displayed.
* @returns {string} Formated number.
* @returns {string} Formatted number.
*
* @css ng-format-negative
* When the value is negative, this css class is applied to the binding making it by default red.
* When the value is negative, this css class is applied to the binding making it (by default) red.
*
* @example
<doc:example>
@@ -82,7 +82,7 @@ angularFilter.currency = function(amount, currencySymbol){
* @description
* Formats a number as text.
*
* If the input is not a number empty string is returned.
* If the input is not a number an empty string is returned.
*
* @param {number|string} number Number to format.
* @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
@@ -492,7 +492,7 @@ angularFilter.uppercase = uppercase;
*
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
* then serialized back to properly escaped html string. This means that no unsafe input can make
* it into the returned string, however since our parser is more strict than a typical browser
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
* browser, won't make it through the sanitizer.
*
@@ -581,7 +581,7 @@ angularFilter.html = function(html, option){
*
* @description
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
* plane email address links.
* plain email address links.
*
* @param {string} text Input text.
* @returns {string} Html-linkified text.
+1 -1
View File
@@ -6,7 +6,7 @@
* @name angular.formatter
* @description
*
* Formatters are used for translating data formats between those used in for display and those used
* Formatters are used for translating data formats between those used for display and those used
* for storage.
*
* Following is the list of built-in angular formatters:
+2 -30
View File
@@ -23,7 +23,7 @@
* focus on the most commonly needed functionality and minimal footprint. For this reason only a
* limited number of jQuery methods, arguments and invocation styles are supported.
*
* NOTE: All element references in angular are always wrapped with jQuery (lite) and are never
* Note: All element references in angular are always wrapped with jQuery (lite) and are never
* raw DOM references.
*
* ## Angular's jQuery lite implements these functions:
@@ -47,8 +47,6 @@
* - [text()](http://api.jquery.com/text/)
* - [trigger()](http://api.jquery.com/trigger/)
* - [eq()](http://api.jquery.com/eq/)
* - [show()](http://api.jquery.com/show/)
* - [hide()](http://api.jquery.com/hide/)
*
* ## Additionally these methods extend the jQuery and are available in both jQuery and jQuery lite
* version:
@@ -152,7 +150,7 @@ function JQLiteData(element, key, value) {
function JQLiteHasClass(element, selector, _) {
// the argument '_' is important, since it makes the function have 3 arguments, which
// is neede for delegate function to realize the this is a getter.
// is needed for delegate function to realize the this is a getter.
var className = " " + selector + " ";
return ((" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1);
}
@@ -456,32 +454,6 @@ forEach({
return element.getElementsByTagName(selector);
},
hide: function(element) {
if (element.style) {
if(element.style.display !=="none" && !JQLiteData(element,"olddisplay")) {
JQLiteData( element, "olddisplay", element.style.display);
}
element.style.display = "none";
}
},
show: function(element) {
if(element.style) {
var display = element.style.display;
if ( display === "" || display === "none" ) {
// restore the original value overwritten by hide if present or default to nothing (which
// will let browser correctly choose between 'inline' or 'block')
element.style.display = JQLiteData(element, "olddisplay") || "";
// if the previous didn't make the element visible then there are some cascading rules that
// are still hiding it, so let's default to 'block', which might be incorrect in case of
// elmenents that should be 'inline' by default, but oh well :-)
if (!isVisible([element])) element.style.display = "block";
}
}
},
clone: JQLiteClone
}, function(fn, name){
/**
+14 -9
View File
@@ -12,8 +12,8 @@
* Markup extensions do not themselves produce linking functions. Think of markup as a way to
* produce shorthand for a {@link angular.widget widget} or a {@link angular.directive directive}.
*
* The most prominent example of an markup in angular is the built-in double curly markup
* `{{expression}}`, which is a shorthand for `<span ng:bind="expression"></span>`.
* The most prominent example of a markup in angular is the built-in double curly markup
* `{{expression}}`, which is shorthand for `<span ng:bind="expression"></span>`.
*
* Create custom markup like this:
*
@@ -34,7 +34,7 @@
* @description
*
* Attribute markup extends the angular compiler in a very similar way as {@link angular.markup}
* except that it allows you to modify the state of the attribute text rather then the content of a
* except that it allows you to modify the state of the attribute text rather than the content of a
* node.
*
* Create custom attribute markup like this:
@@ -138,7 +138,7 @@ angularTextMarkup('option', function(text, textNode, parentElement){
*
* @description
* Using <angular/> markup like {{hash}} in an href attribute makes
* the page open to a wrong URL, ff the user clicks that link before
* the page open to a wrong URL, if the user clicks that link before
* angular has a chance to replace the {{hash}} with actual URL, the
* link will be broken and will most likely return a 404 error.
* The `ng:href` solves this problem by placing the `href` in the
@@ -251,7 +251,8 @@ angularTextMarkup('option', function(text, textNode, parentElement){
* </div>
* </pre>
*
* the HTML specs do not require browsers preserve the special attributes such as disabled.(The presense of them means true and absense means false)
* The HTML specs do not require browsers to preserve the special attributes such as disabled.
* (The presence of them means true and absence means false)
* This prevents the angular compiler from correctly retrieving the binding expression.
* To solve this problem, we introduce ng:disabled.
*
@@ -281,7 +282,8 @@ angularTextMarkup('option', function(text, textNode, parentElement){
* @name angular.directive.ng:checked
*
* @description
* the HTML specs do not require browsers preserve the special attributes such as checked.(The presense of them means true and absense means false)
* The HTML specs do not require browsers to preserve the special attributes such as checked.
* (The presence of them means true and absence means false)
* This prevents the angular compiler from correctly retrieving the binding expression.
* To solve this problem, we introduce ng:checked.
* @example
@@ -310,7 +312,8 @@ angularTextMarkup('option', function(text, textNode, parentElement){
* @name angular.directive.ng:multiple
*
* @description
* the HTML specs do not require browsers preserve the special attributes such as multiple.(The presense of them means true and absense means false)
* The HTML specs do not require browsers to preserve the special attributes such as multiple.
* (The presence of them means true and absence means false)
* This prevents the angular compiler from correctly retrieving the binding expression.
* To solve this problem, we introduce ng:multiple.
*
@@ -345,7 +348,8 @@ angularTextMarkup('option', function(text, textNode, parentElement){
* @name angular.directive.ng:readonly
*
* @description
* the HTML specs do not require browsers preserve the special attributes such as readonly.(The presense of them means true and absense means false)
* The HTML specs do not require browsers to preserve the special attributes such as readonly.
* (The presence of them means true and absence means false)
* This prevents the angular compiler from correctly retrieving the binding expression.
* To solve this problem, we introduce ng:readonly.
* @example
@@ -374,7 +378,8 @@ angularTextMarkup('option', function(text, textNode, parentElement){
* @name angular.directive.ng:selected
*
* @description
* the HTML specs do not require browsers preserve the special attributes such as selected.(The presense of them means true and absense means false)
* The HTML specs do not require browsers to preserve the special attributes such as selected.
* (The presence of them means true and absence means false)
* This prevents the angular compiler from correctly retrieving the binding expression.
* To solve this problem, we introduce ng:selected.
* @example
+9 -3
View File
@@ -147,9 +147,15 @@
*
<pre>
var User = $resource('/user/:userId', {userId:'@id'});
User.get({userId:123}, function(u){
User.get({userId:123}, function(u, responseHeaders){
u.abc = true;
u.$save();
u.$save(function(u, responseHeaders) {
// Get an Object containing all response headers
var allHeaders = responseHeaders();
// Get a specific response header
u.newId = responseHeaders('Location');
});
});
</pre>
@@ -157,7 +163,7 @@
Let's look at what a buzz client created with the `$resource` service looks like:
<doc:example>
<doc:source>
<doc:source jsfiddle="false">
<script>
function BuzzController($resource) {
this.Activity = $resource(
+31 -6
View File
@@ -22,7 +22,7 @@
Try changing the URL in the input box to see changes.
<doc:example>
<doc:source>
<doc:source jsfiddle="false">
<script>
function MainCntl($route, $location) {
this.$route = $route;
@@ -68,6 +68,8 @@ angularServiceInject('$route', function(location, $updateView) {
matcher = switchRouteMatcher,
parentScope = this,
dirty = 0,
lastHashPath,
lastRouteParams,
$route = {
routes: routes,
@@ -136,6 +138,18 @@ angularServiceInject('$route', function(location, $updateView) {
* The custom `redirectTo` function is expected to return a string which will be used
* to update `$location.hash`.
*
* - `[reloadOnSearch=true]` - {boolean=} - reload route when $location.hashSearch
* changes. If this option is disabled, you should set up a $watch to be notified of
* param (hashSearch) changes as follows:
*
* function MyCtrl($route) {
* this.$watch(function() {
* return $route.current.params.myHashSearchParam;
* }, function(params) {
* //do stuff with params
* });
* }
*
* @returns {Object} route object
*
* @description
@@ -144,8 +158,8 @@ angularServiceInject('$route', function(location, $updateView) {
when:function (path, params) {
if (isUndefined(path)) return routes; //TODO(im): remove - not needed!
var route = routes[path];
if (!route) route = routes[path] = {};
if (params) extend(route, params);
if (!route) route = routes[path] = {reloadOnSearch: true};
if (params) extend(route, params); //TODO(im): what the heck? merge two route definitions?
dirty++;
return route;
},
@@ -183,14 +197,16 @@ angularServiceInject('$route', function(location, $updateView) {
function switchRouteMatcher(on, when, dstName) {
var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$',
// TODO(i): this code is convoluted and inefficient, we should construct the route matching
// regex only once and then reuse it
var regex = '^' + when.replace(/([\.\\\(\)\^\$])/g, "\\$1") + '$',
params = [],
dst = {};
forEach(when.split(/\W/), function(param){
if (param) {
var paramRegExp = new RegExp(":" + param + "([\\W])");
if (regex.match(paramRegExp)) {
regex = regex.replace(paramRegExp, "([^\/]*)$1");
regex = regex.replace(paramRegExp, "([^\\/]*)$1");
params.push(param);
}
}
@@ -209,6 +225,14 @@ angularServiceInject('$route', function(location, $updateView) {
function updateRoute(){
var childScope, routeParams, pathParams, segmentMatch, key, redir;
if ($route.current) {
if (!$route.current.reloadOnSearch && (lastHashPath == location.hashPath)) {
$route.current.params = extend({}, location.hashSearch, lastRouteParams);
return;
}
}
lastHashPath = location.hashPath;
$route.current = null;
forEach(routes, function(rParams, rPath) {
if (!pathParams) {
@@ -255,6 +279,7 @@ angularServiceInject('$route', function(location, $updateView) {
scope: childScope,
params: extend({}, location.hashSearch, pathParams)
});
lastRouteParams = pathParams;
}
//fire onChange callbacks
@@ -266,7 +291,7 @@ angularServiceInject('$route', function(location, $updateView) {
}
this.$watch(function(){return dirty + location.hash;}, updateRoute);
this.$watch(function(){ return dirty + location.hash; }, updateRoute);
return $route;
}, ['$location', '$updateView']);
+1 -1
View File
@@ -34,7 +34,7 @@
* or 'XHR' (instead of {@link angular.service.$xhr}) then you may be changing the model
* without angular knowledge and you may need to call '$updateView()' directly.
*
* NOTE: if you wish to update the view immediately (without delay), you can do so by calling
* Note: if you wish to update the view immediately (without delay), you can do so by calling
* {@link angular.scope.$eval} at any time from your code:
* <pre>scope.$root.$eval()</pre>
*
+6 -5
View File
@@ -48,13 +48,14 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
queue.requests = [];
queue.callbacks = [];
$xhr('POST', url, {requests: currentRequests},
function(code, response) {
function(code, response, responseHeaders) {
forEach(response, function(response, i) {
try {
if (response.status == 200) {
(currentRequests[i].success || noop)(response.status, response.response);
(currentRequests[i].success || noop)
(response.status, response.response, responseHeaders);
} else if (isFunction(currentRequests[i].error)) {
currentRequests[i].error(response.status, response.response);
currentRequests[i].error(response.status, response.response, responseHeaders);
} else {
$error(currentRequests[i], response);
}
@@ -64,11 +65,11 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
});
(success || noop)();
},
function(code, response) {
function(code, response, responseHeaders) {
forEach(currentRequests, function(request, i) {
try {
if (isFunction(request.error)) {
request.error(code, response);
request.error(code, response, responseHeaders);
} else {
$error(request, response);
}
+9 -9
View File
@@ -22,8 +22,8 @@
* @param {string} method HTTP method.
* @param {string} url Destination URL.
* @param {(string|Object)=} post Request body.
* @param {function(number, (string|Object))} success Response success callback.
* @param {function(number, (string|Object))=} error Response error callback.
* @param {function(number, (string|Object), Function)} success Response success callback.
* @param {function(number, (string|Object), Function)} error Response error callback.
* @param {boolean=} [verifyCache=false] If `true` then a result is immediately returned from cache
* (if present) while a request is sent to the server for a fresh response that will update the
* cached entry. The `success` function will be called when the response is received.
@@ -55,9 +55,9 @@ angularServiceInject('$xhr.cache', function($xhr, $defer, $error, $log) {
if (dataCached = cache.data[url]) {
if (sync) {
success(200, copy(dataCached.value));
success(200, copy(dataCached.value), copy(dataCached.headers));
} else {
$defer(function() { success(200, copy(dataCached.value)); });
$defer(function() { success(200, copy(dataCached.value), copy(dataCached.headers)); });
}
if (!verifyCache)
@@ -70,20 +70,20 @@ angularServiceInject('$xhr.cache', function($xhr, $defer, $error, $log) {
} else {
inflight[url] = {successes: [success], errors: [error]};
cache.delegate(method, url, post,
function(status, response) {
function(status, response, responseHeaders) {
if (status == 200)
cache.data[url] = {value: response};
cache.data[url] = {value: response, headers: responseHeaders};
var successes = inflight[url].successes;
delete inflight[url];
forEach(successes, function(success) {
try {
(success||noop)(status, copy(response));
(success||noop)(status, copy(response), responseHeaders);
} catch(e) {
$log.error(e);
}
});
},
function(status, response) {
function(status, response, responseHeaders) {
var errors = inflight[url].errors,
successes = inflight[url].successes;
delete inflight[url];
@@ -91,7 +91,7 @@ angularServiceInject('$xhr.cache', function($xhr, $defer, $error, $log) {
forEach(errors, function(error, i) {
try {
if (isFunction(error)) {
error(status, copy(response));
error(status, copy(response), copy(responseHeaders));
} else {
$error(
{method: method, url: url, data: post, success: successes[i]},
+1 -1
View File
@@ -19,7 +19,7 @@
* - `method` `{string}` The http request method.
* - `url` `{string}` The request destination.
* - `data` `{(string|Object)=} An optional request body.
* - `callback` `{function()}` The callback function
* - `success` `{function()}` The success callback function
*
* @param {Object} response Response object.
*
+47 -16
View File
@@ -6,7 +6,7 @@
* @name angular.service.$xhr
* @function
* @requires $browser $xhr delegates all XHR requests to the `$browser.xhr()`. A mock version
* of the $browser exists which allows setting expectaitions on XHR requests
* of the $browser exists which allows setting expectations on XHR requests
* in your tests
* @requires $xhr.error $xhr delegates all non `2xx` response code to this service.
* @requires $log $xhr delegates all exceptions to `$log.error()`.
@@ -83,7 +83,7 @@
* cookie called `XSRF-TOKEN` on first HTTP GET request. On subsequent non-GET requests the server
* can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure that only
* JavaScript running on your domain could have read the token. The token must be unique for each
* user and must be verifiable by the server (to prevent the JavaScript making up its own tokens).
* user and must be verifiable by the server (to prevent the JavaScript making 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}.
*
@@ -96,7 +96,7 @@
* angular generated callback function.
* @param {(string|Object)=} post Request content as either a string or an object to be stringified
* as JSON before sent to the server.
* @param {function(number, (string|Object))} success A function to be called when the response is
* @param {function(number, (string|Object), Function)} success A function to be called when the response is
* received. The success function will be called with:
*
* - {number} code [HTTP status code](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) of
@@ -104,27 +104,35 @@
* {@link angular.service.$xhr.error} service (or custom error callback).
* - {string|Object} response Response object as string or an Object if the response was in JSON
* format.
* - {function(string=)} responseHeaders A function that when called with a {string} header name,
* returns the value of that header or null if it does not exist; when called without
* arguments, returns an object containing every response header
* @param {function(number, (string|Object))} error A function to be called if the response code is
* not 2xx.. Accepts the same arguments as success, above.
*
* @example
<doc:example>
<doc:source>
<doc:source jsfiddle="false">
<script>
function FetchCntl($xhr) {
var self = this;
this.fetch = function() {
self.clear();
self.code = null;
self.response = null;
$xhr(self.method, self.url, function(code, response) {
self.code = code;
self.response = response;
}, function(code, response) {
self.code = code;
self.response = response || "Request failed";
});
};
this.clear = function() {
self.code = null;
self.response = null;
this.updateModel = function(method, url) {
self.method = method;
self.url = url;
};
}
FetchCntl.$inject = ['$xhr'];
@@ -134,15 +142,38 @@
<option>GET</option>
<option>JSON</option>
</select>
<input type="text" name="url" value="index.html" size="80"/><br/>
<button ng:click="fetch()">fetch</button>
<button ng:click="clear()">clear</button>
<a href="" ng:click="method='GET'; url='index.html'">sample</a>
<a href="" ng:click="method='JSON'; url='https://www.googleapis.com/buzz/v1/activities/googlebuzz/@self?alt=json&callback=JSON_CALLBACK'">buzz</a>
<input type="text" name="url" value="index.html" size="80"/>
<button ng:click="fetch()">fetch</button><br>
<button ng:click="updateModel('GET', 'index.html')">Sample GET</button>
<button ng:click="updateModel('JSON', 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">Sample JSONP</button>
<button ng:click="updateModel('JSON', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')">Invalid JSONP</button>
<pre>code={{code}}</pre>
<pre>response={{response}}</pre>
</div>
</doc:source>
<doc:scenario>
it('should make xhr GET request', function() {
element(':button:contains("Sample GET")').click();
element(':button:contains("fetch")').click();
expect(binding('code')).toBe('code=200');
expect(binding('response')).toMatch(/angularjs.org/);
});
it('should make JSONP request to the angularjs.org', function() {
element(':button:contains("Sample JSONP")').click();
element(':button:contains("fetch")').click();
expect(binding('code')).toBe('code=200');
expect(binding('response')).toMatch(/Super Hero!/);
});
it('should make JSONP request to invalid URL and invoke the error handler',
function() {
element(':button:contains("Invalid JSONP")').click();
element(':button:contains("fetch")').click();
expect(binding('code')).toBe('code=');
expect(binding('response')).toBe('response=Request failed');
});
</doc:scenario>
</doc:example>
*/
angularServiceInject('$xhr', function($browser, $error, $log, $updateView){
@@ -170,7 +201,7 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){
post = toJson(post);
}
$browser.xhr(method, url, post, function(code, response){
$browser.xhr(method, url, post, function(code, response, responseHeaders){
try {
if (isString(response)) {
if (response.match(/^\)\]\}',\n/)) response=response.substr(6);
@@ -179,9 +210,9 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){
}
}
if (200 <= code && code < 300) {
success(code, response);
success(code, response, responseHeaders);
} else if (isFunction(error)) {
error(code, response);
error(code, response, responseHeaders);
} else {
$error(
{method: method, url: url, data: post, success: success},
+2 -1
View File
@@ -287,7 +287,8 @@ extend(angularValidator, {
if (value.match(/^\+\d{2,3} (\(\d{1,5}\))?[\d ]+\d$/)) {
return null;
}
return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly.";
return "Phone number needs to be in 1(987)654-3210 format in North America " +
"or +999 (123) 45678 906 internationally.";
},
/**
+11 -16
View File
@@ -6,7 +6,7 @@
* @name angular.widget
* @description
*
* An angular widget can be either a custom attribute that modifies an existing DOM elements or an
* An angular widget can be either a custom attribute that modifies an existing DOM element or an
* entirely new DOM element.
*
* During html compilation, widgets are processed after {@link angular.markup markup}, but before
@@ -39,7 +39,7 @@
* @description
* The most common widgets you will use will be in the form of the
* standard HTML set. These widgets are bound using the `name` attribute
* to an expression. In addition they can have `ng:validate`, `ng:required`,
* to an expression. In addition, they can have `ng:validate`, `ng:required`,
* `ng:format`, `ng:change` attribute to further control their behavior.
*
* @usageContent
@@ -292,7 +292,7 @@ function compileFormatter(expr) {
*
* @description
* The `ng:format` attribute widget formats stored data to user-readable text and parses the text
* back to the stored form. You might find this useful for example if you collect user input in a
* back to the stored form. You might find this useful, for example, if you collect user input in a
* text field but need to store the data in the model as a list. Check out
* {@link angular.formatter formatters} to learn more.
*
@@ -437,7 +437,7 @@ function noopAccessor() { return { get: noop, set: noop }; }
/*
* TODO: refactor
*
* The table bellow is not quite right. In some cases the formatter is on the model side
* The table below is not quite right. In some cases the formatter is on the model side
* and in some cases it is on the view side. This is a historical artifact
*
* The concept of model/view accessor is useful for anyone who is trying to develop UI, and
@@ -447,16 +447,11 @@ function noopAccessor() { return { get: noop, set: noop }; }
*
*/
var textWidget = inputWidget('keydown change', modelAccessor, valueAccessor, initWidgetValue(), true),
buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop),
INPUT_TYPE = {
'text': textWidget,
'textarea': textWidget,
'hidden': textWidget,
'password': textWidget,
'button': buttonWidget,
'submit': buttonWidget,
'reset': buttonWidget,
'image': buttonWidget,
'checkbox': inputWidget('click', modelFormattedAccessor, checkedAccessor, initWidgetValue(false)),
'radio': inputWidget('click', modelFormattedAccessor, radioAccessor, radioInit),
'select-one': inputWidget('change', modelAccessor, valueAccessor, initWidgetValue(null)),
@@ -567,7 +562,7 @@ function inputWidgetSelector(element){
angularWidget('input', inputWidgetSelector);
angularWidget('textarea', inputWidgetSelector);
angularWidget('button', inputWidgetSelector);
/**
* @workInProgress
@@ -738,7 +733,7 @@ angularWidget('select', function(element){
var optionGroup,
collection = valuesFn(scope) || [],
key = selectElement.val(),
tempScope = scope.$new(),
tempScope = inherit(scope),
value, optionElement, index, groupIndex, length, groupLength;
try {
@@ -796,7 +791,7 @@ angularWidget('select', function(element){
fragment,
groupIndex, index,
optionElement,
optionScope = scope.$new(),
optionScope = inherit(scope),
modelValue = model.get(),
selected,
selectedSet = false, // nothing is selected yet
@@ -960,7 +955,7 @@ angularWidget('select', function(element){
*
* @example
<doc:example>
<doc:source>
<doc:source jsfiddle="false">
<select name="url">
<option value="examples/ng-include/template1.html">template1.html</option>
<option value="examples/ng-include/template2.html">template2.html</option>
@@ -1341,12 +1336,12 @@ angularWidget('@ng:repeat', function(expression, element){
* Sometimes it is necessary to write code which looks like bindings but which should be left alone
* by angular. Use `ng:non-bindable` to make angular ignore a chunk of HTML.
*
* NOTE: `ng:non-bindable` looks like a directive, but is actually an attribute widget.
* Note: `ng:non-bindable` looks like a directive, but is actually an attribute widget.
*
* @element ANY
*
* @example
* In this example there are two location where a siple binding (`{{}}`) is present, but the one
* In this example there are two location where a simple binding (`{{}}`) is present, but the one
* wrapped in `ng:non-bindable` is left alone.
*
* @example
@@ -1394,7 +1389,7 @@ angularWidget("@ng:non-bindable", noop);
*
* @example
<doc:example>
<doc:source>
<doc:source jsfiddle="false">
<script>
function MyCtrl($route) {
$route.when('/overview',
+117 -21
View File
@@ -49,6 +49,13 @@ describe('browser', function(){
this.send = function(post){
xhr.post = post;
};
this.getResponseHeader = function(header) {
return header;
};
this.getAllResponseHeaders = function() {
return 'Content-Type: application/json\n\rContent-Encoding: gzip\n\rContent-Type: text/json';
}
};
logs = {log:[], warn:[], info:[], error:[]};
@@ -87,27 +94,81 @@ describe('browser', function(){
describe('xhr', function(){
describe('JSON', function(){
it('should add script tag for request', function() {
var callback = jasmine.createSpy('callback');
var log = "";
browser.xhr('JSON', 'http://example.org/path?cb=JSON_CALLBACK', null, function(code, data){
log += code + ':' + data + ';';
});
browser.notifyWhenNoOutstandingRequests(callback);
expect(callback).not.toHaveBeenCalled();
expect(scripts.length).toEqual(1);
var script = scripts[0];
var url = script.src.split('?cb=');
expect(url[0]).toEqual('http://example.org/path');
expect(typeof fakeWindow[url[1]]).toEqual($function);
fakeWindow[url[1]]('data');
expect(callback).toHaveBeenCalled();
expect(log).toEqual('200:data;');
expect(scripts).toEqual(removedScripts);
expect(fakeWindow[url[1]]).toBeUndefined();
var log;
function callback(code, data) {
log += code + ':' + data + ';';
}
beforeEach(function() {
log = "";
});
// We don't have unit tests for IE because script.readyState is readOnly.
// Instead we run e2e tests on all browsers - see e2e for $xhr.
if (!msie) {
it('should add script tag for JSONP request', function() {
var notify = jasmine.createSpy('notify');
browser.xhr('JSON', 'http://example.org/path?cb=JSON_CALLBACK', null, callback);
browser.notifyWhenNoOutstandingRequests(notify);
expect(notify).not.toHaveBeenCalled();
expect(scripts.length).toEqual(1);
var script = scripts[0];
var url = script.src.split('?cb=');
expect(url[0]).toEqual('http://example.org/path');
expect(typeof fakeWindow[url[1]]).toEqual($function);
fakeWindow[url[1]]('data');
script.onload();
expect(notify).toHaveBeenCalled();
expect(log).toEqual('200:data;');
expect(scripts).toEqual(removedScripts);
expect(fakeWindow[url[1]]).toBeUndefined();
});
it('should call callback when script fails to load', function() {
browser.xhr('JSON', 'http://example.org/path?cb=JSON_CALLBACK', null, callback);
var script = scripts[0];
expect(typeof script.onload).toBe($function);
expect(typeof script.onerror).toBe($function);
script.onerror();
expect(log).toEqual('undefined:undefined;');
});
it('should update the outstandingRequests counter for successful requests', function() {
var notify = jasmine.createSpy('notify');
browser.xhr('JSON', 'http://example.org/path?cb=JSON_CALLBACK', null, callback);
browser.notifyWhenNoOutstandingRequests(notify);
expect(notify).not.toHaveBeenCalled();
var script = scripts[0];
var url = script.src.split('?cb=');
fakeWindow[url[1]]('data');
script.onload();
expect(notify).toHaveBeenCalled();
});
it('should update the outstandingRequests counter for failed requests', function() {
var notify = jasmine.createSpy('notify');
browser.xhr('JSON', 'http://example.org/path?cb=JSON_CALLBACK', null, callback);
browser.notifyWhenNoOutstandingRequests(notify);
expect(notify).not.toHaveBeenCalled();
scripts[0].onerror();
expect(notify).toHaveBeenCalled();
});
}
});
it('should normalize IE\'s 1223 status code into 204', function() {
var callback = jasmine.createSpy('XHR');
@@ -144,6 +205,41 @@ describe('browser', function(){
expect(code).toEqual(202);
expect(response).toEqual('RESPONSE');
});
describe('response headers', function() {
it('should return a single response header', function() {
var headerA;
browser.xhr('GET', 'URL', null, function(code, resp, headers) {
headerA = headers('A-Header');
});
xhr.status = 200;
xhr.responseText = 'RESPONSE';
xhr.readyState = 4;
xhr.onreadystatechange();
expect(headerA).toEqual('a-header');
});
it('should return an object containing all response headers', function() {
var allHeaders;
browser.xhr('GET', 'URL', null, function(code, resp, headers) {
allHeaders = headers();
});
xhr.status = 200;
xhr.responseText = 'RESPONSE';
xhr.readyState = 4;
xhr.onreadystatechange();
expect(allHeaders).toEqual({
'content-type': 'application/json, text/json',
'content-encoding': 'gzip'
});
});
});
});
describe('defer', function() {
@@ -281,17 +377,17 @@ describe('browser', function(){
it('should log warnings when 4kb per cookie storage limit is reached', function() {
var i, longVal = '', cookieStr;
for(i=0; i<4092; i++) {
for(i=0; i<4091; i++) {
longVal += '+';
}
cookieStr = document.cookie;
browser.cookies('x', longVal); //total size 4094-4096, so it should go through
browser.cookies('x', longVal); //total size 4093-4096, so it should go through
expect(document.cookie).not.toEqual(cookieStr);
expect(browser.cookies()['x']).toEqual(longVal);
expect(logs.warn).toEqual([]);
browser.cookies('x', longVal + 'xxx'); //total size 4097-4099, a warning should be logged
browser.cookies('x', longVal + 'xxxx'); //total size 4097-4099, a warning should be logged
expect(logs.warn).toEqual(
[[ "Cookie 'x' possibly not set or overflowed because it was too large (4097 > 4096 " +
"bytes)!" ]]);
+50 -12
View File
@@ -75,6 +75,15 @@ describe("resource", function() {
nakedExpect(item).toEqual({id:'abc'});
});
it("should build resource with action default param overriding default param", function(){
xhr.expectGET('/Customer/123').respond({id:'abc'});
var TypeItem = resource.route('/:type/:typeId', {type: 'Order'},
{get: {method: 'GET', params: {type: 'Customer'}}});
var item = TypeItem.get({typeId:123});
xhr.flush();
nakedExpect(item).toEqual({id:'abc'});
});
it("should create resource", function(){
xhr.expectPOST('/CreditCard', {name:'misko'}).respond({id:123, name:'misko'});
@@ -83,7 +92,8 @@ describe("resource", function() {
expect(callback).not.toHaveBeenCalled();
xhr.flush();
nakedExpect(cc).toEqual({id:123, name:'misko'});
expect(callback).toHaveBeenCalledWith(cc);
expect(callback.mostRecentCall.args[0]).toEqual(cc);
expect(callback.mostRecentCall.args[1]()).toEqual({});
});
it("should read resource", function(){
@@ -94,7 +104,8 @@ describe("resource", function() {
expect(callback).not.toHaveBeenCalled();
xhr.flush();
nakedExpect(cc).toEqual({id:123, number:'9876'});
expect(callback).toHaveBeenCalledWith(cc);
expect(callback.mostRecentCall.args[0]).toEqual(cc);
expect(callback.mostRecentCall.args[1]()).toEqual({});
});
it("should read partial resource", function(){
@@ -108,7 +119,8 @@ describe("resource", function() {
expect(cc.number).not.toBeDefined();
cc.$get(callback);
xhr.flush();
expect(callback).toHaveBeenCalledWith(cc);
expect(callback.mostRecentCall.args[0]).toEqual(cc);
expect(callback.mostRecentCall.args[1]()).toEqual({});
expect(cc.number).toEqual('9876');
});
@@ -129,7 +141,8 @@ describe("resource", function() {
expect(callback).not.toHaveBeenCalled();
xhr.flush();
nakedExpect(ccs).toEqual([{id:1}, {id:2}]);
expect(callback).toHaveBeenCalledWith(ccs);
expect(callback.mostRecentCall.args[0]).toEqual(ccs);
expect(callback.mostRecentCall.args[1]()).toEqual({});
});
it("should have all arguments optional", function(){
@@ -147,14 +160,16 @@ describe("resource", function() {
CreditCard.remove({id:123}, callback);
expect(callback).not.toHaveBeenCalled();
xhr.flush();
nakedExpect(callback.mostRecentCall.args).toEqual([{}]);
nakedExpect(callback.mostRecentCall.args[0]).toEqual({});
nakedExpect(callback.mostRecentCall.args[1]()).toEqual({});
callback.reset();
xhr.expectDELETE("/CreditCard/333").respond(204, null);
CreditCard.remove({id:333}, callback);
expect(callback).not.toHaveBeenCalled();
xhr.flush();
nakedExpect(callback.mostRecentCall.args).toEqual([{}]);
nakedExpect(callback.mostRecentCall.args[0]).toEqual({});
nakedExpect(callback.mostRecentCall.args[1]()).toEqual({});
});
it('should post charge verb', function(){
@@ -171,7 +186,7 @@ describe("resource", function() {
});
it('should create on save', function(){
xhr.expectPOST('/CreditCard', {name:'misko'}).respond({id:123});
xhr.expectPOST('/CreditCard', {name:'misko'}).respond({id: 123}, {foo: 'bar'});
var cc = new CreditCard();
expect(cc.$get).toBeDefined();
expect(cc.$query).toBeDefined();
@@ -183,7 +198,9 @@ describe("resource", function() {
nakedExpect(cc).toEqual({name:'misko'});
xhr.flush();
nakedExpect(cc).toEqual({id:123});
expect(callback).toHaveBeenCalledWith(cc);
expect(callback.mostRecentCall.args[0]).toEqual(cc);
expect(callback.mostRecentCall.args[1]('foo')).toEqual('bar');
expect(callback.mostRecentCall.args[1]()).toEqual({foo: 'bar'});
});
it('should not mutate the resource object if response contains no body', function(){
@@ -244,22 +261,43 @@ describe("resource", function() {
describe('failure mode', function() {
var ERROR_CODE = 500,
ERROR_RESPONSE = 'Server Error';
ERROR_RESPONSE = 'Server Error',
errorCB,
headersFn;
beforeEach(function() {
xhr.expectGET('/CreditCard/123').respond(ERROR_CODE, ERROR_RESPONSE);
errorCB = jasmine.createSpy().andCallFake(function(code, response, headers) {
headersFn = headers;
});
xhr.expectGET('/CreditCard/123').respond(ERROR_CODE, ERROR_RESPONSE, {foo: 'bar'});
});
it('should report error when non 2xx if error callback is not provided', function() {
xhr.expectGET('/CreditCard/123').respond(ERROR_CODE, ERROR_RESPONSE);
CreditCard.get({id:123});
xhr.flush();
expect($xhrErr).toHaveBeenCalled();
});
it('should call the error callback if provided on non 2xx response', function() {
CreditCard.get({id:123}, noop, callback);
xhr.expectGET('/CreditCard/123').respond(ERROR_CODE, ERROR_RESPONSE);
CreditCard.get({id:123}, callback, errorCB);
xhr.flush();
expect(callback).toHaveBeenCalledWith(500, ERROR_RESPONSE);
expect(errorCB).toHaveBeenCalledWith(500, ERROR_RESPONSE, headersFn);
expect(callback).not.toHaveBeenCalled();
expect($xhrErr).not.toHaveBeenCalled();
});
it('should call the error callback if provided on non 2xx response', function() {
xhr.expectGET('/CreditCard').respond(ERROR_CODE, ERROR_RESPONSE, {foo: 'bar'});
CreditCard.get(callback, errorCB);
xhr.flush();
expect(errorCB).toHaveBeenCalledWith(500, ERROR_RESPONSE, headersFn);
expect(headersFn('foo')).toBe('bar');
expect(headersFn()).toEqual({'foo': 'bar'});
expect(callback).not.toHaveBeenCalled();
expect($xhrErr).not.toHaveBeenCalled();
});
});
+2 -1
View File
@@ -64,7 +64,8 @@ describe('ValidatorTest', function(){
});
it('Phone', function() {
var error = "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly.";
var error = "Phone number needs to be in 1(987)654-3210 format in North America " +
"or +999 (123) 45678 906 internationally.";
assertEquals(angular.validator.phone("ab"), error);
assertEquals(null, angular.validator.phone("1(408)757-3023"));
assertEquals(null, angular.validator.phone("+421 (0905) 933 297"));
+44
View File
@@ -220,6 +220,50 @@ describe("directive", function(){
expect(e2.hasClass('even')).toBeTruthy();
});
it('should allow both ng:class and ng:class-odd/even on the same element', function() {
var scope = compile('<ul>' +
'<li ng:repeat="i in [0,1]" ng:class="\'plainClass\'" ' +
'ng:class-odd="\'odd\'" ng:class-even="\'even\'"></li>' +
'<ul>');
scope.$eval();
var e1 = jqLite(element[0].childNodes[1]);
var e2 = jqLite(element[0].childNodes[2]);
expect(e1.hasClass('plainClass')).toBeTruthy();
expect(e1.hasClass('odd')).toBeTruthy();
expect(e1.hasClass('even')).toBeFalsy();
expect(e2.hasClass('plainClass')).toBeTruthy();
expect(e2.hasClass('even')).toBeTruthy();
expect(e2.hasClass('odd')).toBeFalsy();
});
it('should allow both ng:class and ng:class-odd/even with multiple classes', function() {
var scope = compile('<ul>' +
'<li ng:repeat="i in [0,1]" ng:class="[\'A\', \'B\']" ' +
'ng:class-odd="[\'C\', \'D\']" ng:class-even="[\'E\', \'F\']"></li>' +
'<ul>');
scope.$eval();
var e1 = jqLite(element[0].childNodes[1]);
var e2 = jqLite(element[0].childNodes[2]);
expect(e1.hasClass('A')).toBeTruthy();
expect(e1.hasClass('B')).toBeTruthy();
expect(e1.hasClass('C')).toBeTruthy();
expect(e1.hasClass('D')).toBeTruthy();
expect(e1.hasClass('E')).toBeFalsy();
expect(e1.hasClass('F')).toBeFalsy();
expect(e2.hasClass('A')).toBeTruthy();
expect(e2.hasClass('B')).toBeTruthy();
expect(e2.hasClass('E')).toBeTruthy();
expect(e2.hasClass('F')).toBeTruthy();
expect(e2.hasClass('C')).toBeFalsy();
expect(e2.hasClass('D')).toBeFalsy();
});
describe('ng:style', function(){
it('should set', function(){
var scope = compile('<div ng:style="{height: \'40px\'}"></div>');
-56
View File
@@ -511,62 +511,6 @@ describe('jqLite', function(){
});
describe('hide', function() {
var element;
afterEach(function() {
if (element) dealoc(element);
});
it('should hide the element', function() {
element = jqLite('<div></div>');
expect(isCssVisible(element)).toBe(true);
element.hide();
expect(isCssVisible(element)).toBe(false);
});
});
describe('show', function() {
var element;
afterEach(function() {
if (element) dealoc(element);
element.remove();
});
it('should show the element ', function() {
element = jqLite('<div></div>');
element[0].style.display = 'none';
expect(isCssVisible(element)).toBe(false);
element.show();
expect(isCssVisible(element)).toBe(true);
});
it('should show previously hidden element and preserve the display value', function() {
element = jqLite('<div style="display:inline">xx</div>');
jqLite(document.body).append(element);
element.hide();
expect(isCssVisible(element)).toBe(false);
element.show();
expect(element[0].style.display).toBe('inline');
expect(isCssVisible(element)).toBe(true);
element[0].style.display = 'block';
element.hide();
expect(isCssVisible(element)).toBe(false);
element.show();
expect(isCssVisible(element)).toBe(true);
// this totally doesn't make sense, it should be 'block', but jquery (1.4.2+1.6.2) behaves
// this way.
expect(element[0].style.display).toBe('inline');
});
});
describe('eq', function() {
it('should select the nth element ', function() {
var element = jqLite('<div><span>aa</span></div><div><span>bb</span></div>');
+161 -2
View File
@@ -55,14 +55,37 @@ describe('$route', function() {
it('should return fn registered with onChange()', function() {
var scope = angular.scope(),
$route = scope.$service('$route'),
var $route = scope.$service('$route'),
fn = function() {};
expect($route.onChange(fn)).toBe(fn);
});
it('should match a route that contains special chars in the path', function() {
var $route = scope.$service('$route'),
$location = scope.$service('$location');
$route.when('/$test.23/foo(bar)/:baz', {template: 'test.html'});
$location.hashPath = '/test';
scope.$eval();
expect($route.current).toBe(null);
$location.hashPath = '/$testX23/foo(bar)/222';
scope.$eval();
expect($route.current).toBe(null);
$location.hashPath = '/$test.23/foo(bar)/222';
scope.$eval();
expect($route.current).toBeDefined();
$location.hashPath = '/$test.23/foo\\(bar)/222';
scope.$eval();
expect($route.current).toBe(null);
});
it('should allow routes to be defined with just templates without controllers', function() {
var scope = angular.scope(),
$location = scope.$service('$location'),
@@ -227,4 +250,140 @@ describe('$route', function() {
}
});
});
describe('reloadOnSearch', function() {
it('should reload a route when reloadOnSearch is enabled and hashSearch changes', function() {
var scope = angular.scope(),
$location = scope.$service('$location'),
$route = scope.$service('$route'),
reloaded = jasmine.createSpy('route reload');
$route.when('/foo', {controller: FooCtrl});
$route.onChange(reloaded);
function FooCtrl() {
reloaded();
}
$location.updateHash('/foo');
scope.$eval();
expect(reloaded).toHaveBeenCalled();
reloaded.reset();
// trigger reload
$location.hashSearch.foo = 'bar';
scope.$eval();
expect(reloaded).toHaveBeenCalled();
});
it('should not reload a route when reloadOnSearch is disabled and only hashSearch changes',
function() {
var scope = angular.scope(),
$location = scope.$service('$location'),
$route = scope.$service('$route'),
reloaded = jasmine.createSpy('route reload');
$route.when('/foo', {controller: FooCtrl, reloadOnSearch: false});
$route.onChange(reloaded);
function FooCtrl() {
reloaded();
}
expect(reloaded).not.toHaveBeenCalled();
$location.updateHash('/foo');
scope.$eval();
expect(reloaded).toHaveBeenCalled();
reloaded.reset();
// don't trigger reload
$location.hashSearch.foo = 'bar';
scope.$eval();
expect(reloaded).not.toHaveBeenCalled();
});
it('should reload reloadOnSearch route when url differs only in route path param', function() {
var scope = angular.scope(),
$location = scope.$service('$location'),
$route = scope.$service('$route'),
reloaded = jasmine.createSpy('routeReload'),
onRouteChange = jasmine.createSpy('onRouteChange');
$route.when('/foo/:fooId', {controller: FooCtrl, reloadOnSearch: false});
$route.onChange(onRouteChange);
function FooCtrl() {
reloaded();
}
expect(reloaded).not.toHaveBeenCalled();
expect(onRouteChange).not.toHaveBeenCalled();
$location.updateHash('/foo/aaa');
scope.$eval();
expect(reloaded).toHaveBeenCalled();
expect(onRouteChange).toHaveBeenCalled();
reloaded.reset();
onRouteChange.reset();
$location.updateHash('/foo/bbb');
scope.$eval();
expect(reloaded).toHaveBeenCalled();
expect(onRouteChange).toHaveBeenCalled();
reloaded.reset();
onRouteChange.reset();
$location.hashSearch.foo = 'bar';
scope.$eval();
expect(reloaded).not.toHaveBeenCalled();
expect(onRouteChange).not.toHaveBeenCalled();
});
it('should update route params when reloadOnSearch is disabled and hashSearch', function() {
var scope = angular.scope(),
$location = scope.$service('$location'),
$route = scope.$service('$route'),
routeParams = jasmine.createSpy('routeParams');
$route.when('/foo', {controller: FooCtrl});
$route.when('/bar/:barId', {controller: FooCtrl, reloadOnSearch: false});
function FooCtrl() {
this.$watch(function() {
return $route.current.params;
}, function(params) {
routeParams(params);
});
}
expect(routeParams).not.toHaveBeenCalled();
$location.updateHash('/foo');
scope.$eval();
expect(routeParams).toHaveBeenCalledWith({});
routeParams.reset();
// trigger reload
$location.hashSearch.foo = 'bar';
scope.$eval();
expect(routeParams).toHaveBeenCalledWith({foo: 'bar'});
routeParams.reset();
$location.updateHash('/bar/123');
scope.$eval();
expect(routeParams).toHaveBeenCalledWith({barId: '123'});
routeParams.reset();
// don't trigger reload
$location.hashSearch.foo = 'bar';
scope.$eval();
$route.current.scope.$eval(); // ng:view propagates evals so we have to do it by hand here
expect(routeParams).toHaveBeenCalledWith({barId: '123', foo: 'bar'});
});
});
});
+5 -2
View File
@@ -18,8 +18,9 @@ describe('$xhr.bulk', function() {
});
function callback(code, response) {
function callback(code, response, responseHeaders) {
expect(code).toEqual(200);
expect(responseHeaders()).toEqual({});
log = log + toJson(response) + ';';
}
@@ -81,6 +82,8 @@ describe('$xhr.bulk', function() {
$browserXhr.flush();
expect($xhrError).not.toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith(404, 'NotFound');
expect(callback.mostRecentCall.args[0]).toEqual(404);
expect(callback.mostRecentCall.args[1]).toEqual('NotFound');
expect(callback.mostRecentCall.args[2]()).toEqual({});
});
});
+26 -9
View File
@@ -1,7 +1,7 @@
'use strict';
describe('$xhr.cache', function() {
var scope, $browser, $browserXhr, $xhrErr, cache, log;
var scope, $browser, $browserXhr, $xhrErr, cache, log, rHeaders;
beforeEach(function() {
scope = angular.scope({}, null, {'$xhr.error': $xhrErr = jasmine.createSpy('$xhr.error')});
@@ -9,6 +9,7 @@ describe('$xhr.cache', function() {
$browserXhr = $browser.xhr;
cache = scope.$service('$xhr.cache');
log = '';
rHeaders = {};
});
@@ -17,18 +18,21 @@ describe('$xhr.cache', function() {
});
function callback(code, response) {
function callback(code, response, responseHeaders) {
expect(code).toEqual(200);
expect(responseHeaders()).toEqual(rHeaders);
log = log + toJson(response) + ';';
}
it('should cache requests', function(){
$browserXhr.expectGET('/url').respond('first');
rHeaders = {foo: 'bar'};
$browserXhr.expectGET('/url').respond('first', rHeaders);
cache('GET', '/url', null, callback);
$browserXhr.flush();
$browserXhr.expectGET('/url').respond('ERROR');
$browserXhr.expectGET('/url').respond('ERROR', rHeaders);
cache('GET', '/url', null, callback);
$browser.defer.flush();
expect(log).toEqual('"first";"first";');
@@ -40,11 +44,13 @@ describe('$xhr.cache', function() {
it('should first return cache request, then return server request', function(){
$browserXhr.expectGET('/url').respond('first');
rHeaders = {foo: 'bar'};
$browserXhr.expectGET('/url').respond('first', rHeaders);
cache('GET', '/url', null, callback, true);
$browserXhr.flush();
$browserXhr.expectGET('/url').respond('ERROR');
$browserXhr.expectGET('/url').respond('ERROR', rHeaders);
cache('GET', '/url', null, callback, true);
$browser.defer.flush();
expect(log).toEqual('"first";"first";');
@@ -55,7 +61,14 @@ describe('$xhr.cache', function() {
it('should serve requests from cache', function(){
cache.data.url = {value:'123'};
rHeaders = {foo: 'bar'};
cache.data.url = {
value: '123',
headers: function() {
return rHeaders;
}
};
cache('GET', 'url', null, callback);
$browser.defer.flush();
expect(log).toEqual('"123";');
@@ -152,13 +165,17 @@ describe('$xhr.cache', function() {
cache('GET', '/url', null, successSpy, errorSpy, false, true);
$browserXhr.flush();
expect(errorSpy).toHaveBeenCalledWith(500, 'error');
expect(errorSpy.mostRecentCall.args[0]).toEqual(500);
expect(errorSpy.mostRecentCall.args[1]).toEqual('error');
expect(errorSpy.mostRecentCall.args[2]()).toEqual({});
expect(successSpy).not.toHaveBeenCalled();
errorSpy.reset();
cache('GET', '/url', successSpy, errorSpy, false, true);
$browserXhr.flush();
expect(errorSpy).toHaveBeenCalledWith(500, 'error');
expect(errorSpy.mostRecentCall.args[0]).toEqual(500);
expect(errorSpy.mostRecentCall.args[1]).toEqual('error');
expect(errorSpy.mostRecentCall.args[2]()).toEqual({});
expect(successSpy).not.toHaveBeenCalled();
});
+22 -15
View File
@@ -18,15 +18,16 @@ describe('$xhr', function() {
});
function callback(code, response) {
log = log + '{code=' + code + '; response=' + toJson(response) + '}';
function callback(code, response, responseHeader) {
log = log + '{code=' + code + '; response=' + toJson(response) + '; responseHeaders=' +
toJson(responseHeader()) + '}';
}
it('should forward the request to $browser and decode JSON', function(){
$browserXhr.expectGET('/reqGET').respond('first');
$browserXhr.expectGET('/reqGETjson').respond('["second"]');
$browserXhr.expectPOST('/reqPOST', {post:'data'}).respond('third');
$browserXhr.expectGET('/reqGET').respond('first', {h: 'first'});
$browserXhr.expectGET('/reqGETjson').respond('["second"]', {h: 'second'});
$browserXhr.expectPOST('/reqPOST', {post:'data'}).respond('third', {h: 'third'});
$xhr('GET', '/reqGET', null, callback);
$xhr('GET', '/reqGETjson', null, callback);
@@ -35,23 +36,23 @@ describe('$xhr', function() {
$browserXhr.flush();
expect(log).toEqual(
'{code=200; response="third"}' +
'{code=200; response=["second"]}' +
'{code=200; response="first"}');
'{code=200; response="third"; responseHeaders={"h":"third"}}' +
'{code=200; response=["second"]; responseHeaders={"h":"second"}}' +
'{code=200; response="first"; responseHeaders={"h":"first"}}');
});
it('should allow all 2xx requests', function(){
$browserXhr.expectGET('/req1').respond(200, '1');
$browserXhr.expectGET('/req1').respond(200, '1', {h: '1'});
$xhr('GET', '/req1', null, callback);
$browserXhr.flush();
$browserXhr.expectGET('/req2').respond(299, '2');
$browserXhr.expectGET('/req2').respond(299, '2', {h: '2'});
$xhr('GET', '/req2', null, callback);
$browserXhr.flush();
expect(log).toEqual(
'{code=200; response="1"}' +
'{code=299; response="2"}');
'{code=200; response="1"; responseHeaders={"h":"1"}}' +
'{code=299; response="2"; responseHeaders={"h":"2"}}');
});
@@ -120,18 +121,24 @@ describe('$xhr', function() {
var errorSpy = jasmine.createSpy('error'),
successSpy = jasmine.createSpy('success');
$browserXhr.expectGET('/url').respond(500, 'error');
$browserXhr.expectGET('/url').respond(500, 'error', {foo: 'bar'});
$xhr('GET', '/url', null, successSpy, errorSpy);
$browserXhr.flush();
expect(errorSpy).toHaveBeenCalledWith(500, 'error');
expect(errorSpy.mostRecentCall.args[0]).toEqual(500);
expect(errorSpy.mostRecentCall.args[1]).toEqual('error');
expect(errorSpy.mostRecentCall.args[2]('foo')).toEqual('bar');
expect(errorSpy.mostRecentCall.args[2]()).toEqual({foo: 'bar'});
expect(successSpy).not.toHaveBeenCalled();
errorSpy.reset();
$xhr('GET', '/url', successSpy, errorSpy);
$browserXhr.flush();
expect(errorSpy).toHaveBeenCalledWith(500, 'error');
expect(errorSpy.mostRecentCall.args[0]).toEqual(500);
expect(errorSpy.mostRecentCall.args[1]).toEqual('error');
expect(errorSpy.mostRecentCall.args[2]('foo')).toEqual('bar');
expect(errorSpy.mostRecentCall.args[2]()).toEqual({foo: 'bar'});
expect(successSpy).not.toHaveBeenCalled();
});
+22
View File
@@ -90,6 +90,28 @@ beforeEach(function(){
return "Expected " + expected + " to match an Error with message " + toJson(messageRegexp);
};
return this.actual.name == 'Error' && messageRegexp.test(this.actual.message);
},
toHaveBeenCalledOnce: function() {
if (arguments.length > 0) {
throw new Error('toHaveBeenCalledOnce does not take arguments, use toHaveBeenCalledWith');
}
if (!jasmine.isSpy(this.actual)) {
throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
}
this.message = function() {
var msg = 'Expected spy ' + this.actual.identity + ' to have been called once, but was ',
count = this.actual.callCount;
return [
count == 0 ? msg + 'never called.'
: msg + 'called ' + count + ' times.',
msg.replace('to have', 'not to have') + 'called once.'
];
};
return this.actual.callCount == 1;
}
});
-20
View File
@@ -293,18 +293,6 @@ describe("widget", function(){
expect(scope.$get('name')).toEqual('Kai');
});
it('should call ng:change on button click', function(){
compile('<input type="button" value="Click Me" ng:change="clicked = true"/>');
browserTrigger(element);
expect(scope.$get('clicked')).toEqual(true);
});
it('should support button alias', function(){
compile('<button ng:change="clicked = true">Click {{"Me"}}.</button>');
browserTrigger(element);
expect(scope.$get('clicked')).toEqual(true);
expect(scope.$element.text()).toEqual("Click Me.");
});
describe('radio', function(){
@@ -417,14 +405,6 @@ describe("widget", function(){
expect(scope.$service('$log').error.logs.shift()[0]).
toMatchError(/Syntax Error: Token '''' is an unexpected token/);
});
it('should report error on ng:change exception', function(){
compile('<button ng:change="a-2=x">click</button>');
browserTrigger(element);
expect(element.hasClass('ng-exception')).toBeTruthy();
expect(scope.$service('$log').error.logs.shift()[0]).
toMatchError(/Syntax Error: Token '=' implies assignment but \[a-2\] can not be assigned to/);
});
});
describe('ng:switch', function(){
+3 -3
View File
@@ -1,4 +1,4 @@
# <angular/> build config file
# AngularJS build config file
---
version: 0.9.18
codename: jiggling-armfat
version: 0.9.20
codename: ???