Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d3d2a3a374 | |||
| 527d0a1600 | |||
| 23875cb330 | |||
| b0be87f663 | |||
| 9ccd2f0412 | |||
| 99004b0aed | |||
| ab040254f0 | |||
| 4f5d5029c2 | |||
| f534def0c6 | |||
| c3e32f1a51 | |||
| 4f22d6866c | |||
| aab3df7aea | |||
| 0a6cf70deb | |||
| c79aba92f6 | |||
| 84dedb81e7 | |||
| e999740044 | |||
| 0ad39dde4f | |||
| 4c71824a69 | |||
| 47c454a315 | |||
| 16086aa37c | |||
| c0a26b1853 | |||
| 9db2170dcf | |||
| b28dee7fd5 | |||
| 142a985f33 | |||
| bd5ec7c32a | |||
| bdc251c5a5 | |||
| ad9537cdf6 | |||
| 807d8c92b3 | |||
| 454626ad39 | |||
| 247c99a8a4 | |||
| da1d50fbe9 | |||
| 67d064820c | |||
| b2631f6170 | |||
| 1430c6d6b1 | |||
| 3ea5941f0e | |||
| d0270d9256 | |||
| 5f080193cb | |||
| cf891428bf | |||
| 5b9967518e | |||
| 38f462d572 | |||
| 56eeba0f3c | |||
| 5a534235b6 | |||
| e7a0fb250f | |||
| e3ddc2bcc4 | |||
| d11088eb43 | |||
| a5df1fc41f | |||
| ec4d446f89 | |||
| b225083a21 | |||
| e84d3334b0 |
+2
-1
@@ -2,5 +2,6 @@ build/
|
||||
angularjs.netrc
|
||||
jstd.log
|
||||
.DS_Store
|
||||
regression/temp.html
|
||||
regression/temp*.html
|
||||
performance/temp*.html
|
||||
.idea/workspace.xml
|
||||
|
||||
@@ -1,3 +1,88 @@
|
||||
# <angular/> 0.9.9 time-shift (2011-01-13) #
|
||||
|
||||
### Security
|
||||
- Added a just in case security check for JSON parsing. (commit 5f080193)
|
||||
- Completed security review with the Google Security Team.
|
||||
|
||||
### Performance
|
||||
- $location and $cookies services are now lazily initialized to avoid the polling overhead when
|
||||
not needed.
|
||||
- $location service now listens for `onhashchange` events (if supported by browser) instead of
|
||||
constant polling. (commit 16086aa3)
|
||||
- input widgets known listens on keydown events instead of keyup which improves perceived
|
||||
performance (commit 47c454a3)
|
||||
- angular boots significantly sooner by listening for DOMContentLoaded event instead of
|
||||
window.load when supported by browser (commit c79aba92)
|
||||
- new service $updateView which may be used in favor of $root.$eval() to run a complete eval on
|
||||
the entire document. This service bulks and throttles DOM updates to improve performance.
|
||||
(commit 47c454a3)
|
||||
|
||||
### Docs
|
||||
- Major improvements to the doc parser (commit 4f22d686)
|
||||
- Docs now offline enabled (all dependencies are bundled in the tarball) (commit 4f5d5029)
|
||||
- Added support for navigating the docs app with keyboard shortcuts (tab and ctrl+alt+s)
|
||||
|
||||
### Bugfixes
|
||||
- `angular.Object.equals` now properly handless comparing an object with a null (commit b0be87f6)
|
||||
- Several issues were addressed in the `$location` service (commit 23875cb3)
|
||||
- angular.filter.date now properly handles some corner-cases (issue #159 - fix contributed by Vojta)
|
||||
|
||||
### Breaking changes
|
||||
- API for accessing registered services — `scope.$inject` — was renamed to
|
||||
[`scope.$service`](http://docs.angularjs.org/#!angular.scope.$service). (commit b2631f61)
|
||||
|
||||
- Support for `eager-published` services was removed. This change was done to make explicit
|
||||
dependency declaration always required in order to allow making relatively expensive services
|
||||
lazily initialized (e.g. $cookie, $location), as well as remove 'magic' and reduce unnecessary
|
||||
scope namespace pollution. (commit 3ea5941f)
|
||||
|
||||
Complete list of affected services:
|
||||
|
||||
- $location
|
||||
- $route
|
||||
- $cookies
|
||||
- $window
|
||||
- $document
|
||||
- $exceptionHandler
|
||||
- $invalidWidgets
|
||||
|
||||
To temporarily preserve the 'eager-published' status for these services, you may use `ng:init`
|
||||
(e.g. `ng:init="$location = $service('$location'), ...`) in the view or more correctly create
|
||||
a service like this:
|
||||
|
||||
angular.service('published-svc-shim', function() {
|
||||
this.$location = this.$service('$location');
|
||||
this.$route = this.$service('$route');
|
||||
this.$cookies = this.$service('$cookies');
|
||||
this.$window = this.$service('$window');
|
||||
this.$document = this.$service('$document');
|
||||
this.$exceptionHandler = this.$service('$exceptionHandler');
|
||||
this.$invalidWidgets = this.$service('$invalidWidgets');
|
||||
}, {$eager: true});
|
||||
|
||||
- In the light of the `eager-published` change, to complete the cleanup we renamed `$creation`
|
||||
property of services to `eager` with its value being a boolean.
|
||||
To transition, please rename all `$creation: 'eager'` declarations to `$eager: true`.
|
||||
(commit 1430c6d6)
|
||||
|
||||
- `angular.foreach` was renamed to `angular.forEach` to make the api consistent. (commit 0a6cf70d)
|
||||
|
||||
- The `toString` method of the `angular.service.$location` service was removed. (commit 23875cb3)
|
||||
|
||||
|
||||
# <angular/> 0.9.8 astral-projection (2010-12-23) #
|
||||
|
||||
### Docs/Getting started
|
||||
- angular-seed project to get you hacking on an angular apps quickly
|
||||
https://github.com/angular/angular-seed
|
||||
|
||||
### Performance
|
||||
- Delegate JSON parsing to native parser (JSON.parse) if available
|
||||
|
||||
### Bug Fixes
|
||||
- Ignore input widgets which have no name (issue #153)
|
||||
|
||||
|
||||
# <angular/> 0.9.7 sonic-scream (2010-12-10) #
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -158,7 +158,7 @@ end
|
||||
|
||||
desc 'Generate docs'
|
||||
task :docs do
|
||||
`node docs/collect.js`
|
||||
`node docs/src/gen-docs.js`
|
||||
end
|
||||
|
||||
|
||||
|
||||
-440
@@ -1,440 +0,0 @@
|
||||
require.paths.push("./lib");
|
||||
require.paths.push(__dirname);
|
||||
var fs = require('fs'),
|
||||
spawn = require('child_process').spawn,
|
||||
mustache = require('mustache'),
|
||||
callback = require('callback'),
|
||||
Showdown = require('showdown').Showdown;
|
||||
|
||||
var documentation = {
|
||||
pages:[],
|
||||
byName: {}
|
||||
};
|
||||
var keywordPages = [];
|
||||
|
||||
|
||||
var SRC_DIR = "docs/";
|
||||
var OUTPUT_DIR = "build/docs/";
|
||||
var NEW_LINE = /\n\r?/;
|
||||
var TEMPLATES = {};
|
||||
var start = now();
|
||||
|
||||
function now(){ return new Date().getTime(); }
|
||||
var work = callback.chain(function () {
|
||||
console.log('Parsing Angular Reference Documentation');
|
||||
findJsFiles('src', work.waitMany(function(file) {
|
||||
//console.log('reading', file, '...');
|
||||
findNgDocInJsFile(file, work.waitMany(function(doc) {
|
||||
parseNgDoc(doc);
|
||||
processNgDoc(documentation, doc);
|
||||
}));
|
||||
}));
|
||||
findNgDocInDir(SRC_DIR, work.waitMany(function(doc){
|
||||
parseNgDoc(doc);
|
||||
processNgDoc(documentation, doc);
|
||||
}));
|
||||
loadTemplates(TEMPLATES, work.waitFor());
|
||||
mkdirPath(OUTPUT_DIR, work.waitFor());
|
||||
}).onError(function(err){
|
||||
console.log('ERROR:', err.stack || err);
|
||||
}).onDone(function(){
|
||||
keywordPages.sort(keywordSort);
|
||||
writeDoc(documentation.pages);
|
||||
mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(keywordPages)}, callback.chain());
|
||||
mergeTemplate('docs-scenario.js', 'docs-scenario.js', documentation, callback.chain());
|
||||
copy('docs-scenario.html', callback.chain());
|
||||
copy('index.html', callback.chain());
|
||||
copy('docs.css', callback.chain());
|
||||
mergeTemplate('docs.js', 'docs.js', documentation, callback.chain());
|
||||
mergeTemplate('doc_widgets.css', 'doc_widgets.css', documentation, callback.chain());
|
||||
mergeTemplate('doc_widgets.js', 'doc_widgets.js', documentation, callback.chain());
|
||||
console.log('DONE', now() - start, 'ms.');
|
||||
});
|
||||
if (!this.testmode) work();
|
||||
////////////////////
|
||||
|
||||
function keywords(text){
|
||||
var keywords = {};
|
||||
var words = [];
|
||||
var tokens = text.toLowerCase().split(/[,\.\`\'\"\s]+/mg);
|
||||
tokens.forEach(function(key){
|
||||
var match = key.match(/^(([a-z]|ng\:)[\w\_\-]{2,})/);
|
||||
if (match){
|
||||
key = match[1];
|
||||
if (!keywords[key]) {
|
||||
keywords[key] = true;
|
||||
words.push(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
words.sort();
|
||||
return words.join(' ');
|
||||
}
|
||||
|
||||
function noop(){}
|
||||
function mkdirPath(path, callback) {
|
||||
var parts = path.split(/\//);
|
||||
path = '.';
|
||||
(function next(){
|
||||
if (parts.length) {
|
||||
path += '/' + parts.shift();
|
||||
fs.mkdir(path, 0777, next);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
function copy(name, callback){
|
||||
fs.readFile(SRC_DIR + name, callback.waitFor(function(err, content){
|
||||
if (err) return this.error(err);
|
||||
fs.writeFile(OUTPUT_DIR + name, content, callback);
|
||||
}));
|
||||
}
|
||||
|
||||
function mergeTemplate(template, output, doc, callback){
|
||||
fs.readFile(SRC_DIR + template,
|
||||
callback.waitFor(function(err, template){
|
||||
if (err) return this.error(err);
|
||||
var content = mustache.to_html(template.toString(), doc);
|
||||
fs.writeFile(OUTPUT_DIR + output, content, callback);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function trim(text) {
|
||||
var MAX = 9999;
|
||||
var empty = RegExp.prototype.test.bind(/^\s*$/);
|
||||
var lines = text.split('\n');
|
||||
var minIndent = MAX;
|
||||
lines.forEach(function(line){
|
||||
minIndent = Math.min(minIndent, indent(line));
|
||||
});
|
||||
for ( var i = 0; i < lines.length; i++) {
|
||||
lines[i] = lines[i].substring(minIndent);
|
||||
}
|
||||
// remove leading lines
|
||||
while (empty(lines[0])) {
|
||||
lines.shift();
|
||||
}
|
||||
// remove trailing
|
||||
while (empty(lines[lines.length - 1])) {
|
||||
lines.pop();
|
||||
}
|
||||
return lines.join('\n');
|
||||
|
||||
function indent(line) {
|
||||
for(var i = 0; i < line.length; i++) {
|
||||
if (line.charAt(i) != ' ') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return MAX;
|
||||
}
|
||||
}
|
||||
|
||||
function unknownTag(doc, name) {
|
||||
var error = "[" + doc.raw.file + ":" + doc.raw.line + "]: unknown tag: " + name;
|
||||
console.log(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
function valueTag(doc, name, value) {
|
||||
doc[name] = value;
|
||||
}
|
||||
|
||||
function escapedHtmlTag(doc, name, value) {
|
||||
doc[name] = value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function markdownTag(doc, name, value) {
|
||||
doc[name] = markdown(value.replace(/^#/gm, '##')).
|
||||
replace(/\<pre\>/gmi, '<div ng:non-bindable><pre class="brush: js; html-script: true;">').
|
||||
replace(/\<\/pre\>/gmi, '</pre></div>');
|
||||
}
|
||||
|
||||
var R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m;
|
||||
// 1 123 3 4 42
|
||||
|
||||
function markdown(text) {
|
||||
var parts = text.split(/(<pre>[\s\S]*?<\/pre>)/),
|
||||
match;
|
||||
|
||||
parts.forEach(function(text, i){
|
||||
if (!text.match(/^<pre>/)) {
|
||||
text = text.replace(/<angular\/>/gm, '<tt><angular/></tt>');
|
||||
text = new Showdown.converter().makeHtml(text);
|
||||
|
||||
while (match = text.match(R_LINK)) {
|
||||
text = text.replace(match[0], '<a href="#!' + match[1] + '"><code>' +
|
||||
(match[4] || match[1]) +
|
||||
'</code></a>');
|
||||
}
|
||||
|
||||
parts[i] = text;
|
||||
}
|
||||
});
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
function markdownNoP(text) {
|
||||
var lines = markdown(text).split(NEW_LINE);
|
||||
var last = lines.length - 1;
|
||||
lines[0] = lines[0].replace(/^<p>/, '');
|
||||
lines[last] = lines[last].replace(/<\/p>$/, '');
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function requiresTag(doc, name, value) {
|
||||
doc.requires = doc.requires || [];
|
||||
doc.requires.push({name: value});
|
||||
}
|
||||
|
||||
function propertyTag(doc, name, value) {
|
||||
doc[name] = doc[name] || [];
|
||||
var match = value.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/);
|
||||
|
||||
if (match) {
|
||||
var tag = {
|
||||
type: match[2],
|
||||
name: match[3],
|
||||
description: match[5] || false
|
||||
};
|
||||
} else {
|
||||
throw "[" + doc.raw.file + ":" + doc.raw.line +
|
||||
"]: @" + name + " must be in format '{type} name description' got: " + value;
|
||||
}
|
||||
return doc[name].push(tag);
|
||||
}
|
||||
|
||||
function returnsTag(doc, name, value) {
|
||||
var match = value.match(/^{(\S+)}\s+([\s\S]*)?/);
|
||||
|
||||
if (match) {
|
||||
var tag = {
|
||||
type: match[1],
|
||||
description: markdownNoP(match[2]) || false
|
||||
};
|
||||
} else {
|
||||
throw "[" + doc.raw.file + ":" + doc.raw.line +
|
||||
"]: @" + name + " must be in format '{type} description' got: " + value;
|
||||
}
|
||||
return doc[name] = tag;
|
||||
}
|
||||
|
||||
var TAG = {
|
||||
ngdoc: valueTag,
|
||||
example: escapedHtmlTag,
|
||||
scenario: valueTag,
|
||||
namespace: valueTag,
|
||||
css: valueTag,
|
||||
see: valueTag,
|
||||
deprecated: valueTag,
|
||||
workInProgress: function(doc, name, value) {
|
||||
doc[name] = {description: markdown(value)};
|
||||
},
|
||||
usageContent: valueTag,
|
||||
'function': valueTag,
|
||||
description: markdownTag,
|
||||
TODO: markdownTag,
|
||||
paramDescription: markdownTag,
|
||||
exampleDescription: markdownTag,
|
||||
element: valueTag,
|
||||
methodOf: valueTag,
|
||||
name: function(doc, name, value) {
|
||||
var parts = value.split(/\./);
|
||||
doc.name = value;
|
||||
doc.shortName = parts.pop();
|
||||
doc.depth = parts.length;
|
||||
},
|
||||
param: function(doc, name, value){
|
||||
doc.param = doc.param || [];
|
||||
doc.paramRest = doc.paramRest || [];
|
||||
var match = value.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
|
||||
// 1 12 2 34 4 5 5 6 6 3 7 7
|
||||
if (match) {
|
||||
var param = {
|
||||
type: match[1],
|
||||
name: match[5] || match[4],
|
||||
optional: !!match[2],
|
||||
'default':match[6],
|
||||
description:markdownNoP(value.replace(match[0], match[7]))
|
||||
};
|
||||
doc.param.push(param);
|
||||
if (!doc.paramFirst) {
|
||||
doc.paramFirst = param;
|
||||
} else {
|
||||
doc.paramRest.push(param);
|
||||
}
|
||||
} else {
|
||||
throw "[" + doc.raw.file + ":" + doc.raw.line +
|
||||
"]: @param must be in format '{type} name=value description' got: " + value;
|
||||
}
|
||||
},
|
||||
property: propertyTag,
|
||||
requires: requiresTag,
|
||||
returns: returnsTag
|
||||
};
|
||||
|
||||
function parseNgDoc(doc){
|
||||
var atName;
|
||||
var atText;
|
||||
var match;
|
||||
doc.raw.text.split(NEW_LINE).forEach(function(line, lineNumber){
|
||||
if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
|
||||
// we found @name ...
|
||||
// if we have existing name
|
||||
if (atName) {
|
||||
(TAG[atName] || unknownTag)(doc, atName, trim(atText.join('\n')));
|
||||
}
|
||||
atName = match[1];
|
||||
atText = [];
|
||||
if(match[3]) atText.push(match[3]);
|
||||
} else {
|
||||
if (atName) {
|
||||
atText.push(line);
|
||||
} else {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
});
|
||||
if (atName) {
|
||||
(TAG[atName] || unknownTag)(doc, atName, atText.join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
function findNgDocInJsFile(file, callback) {
|
||||
fs.readFile(file, callback.waitFor(function(err, content){
|
||||
var lines = content.toString().split(NEW_LINE);
|
||||
var doc;
|
||||
var match;
|
||||
var inDoc = false;
|
||||
lines.forEach(function(line, lineNumber){
|
||||
lineNumber++;
|
||||
// is the comment starting?
|
||||
if (!inDoc && (match = line.match(/^\s*\/\*\*\s*(.*)$/))) {
|
||||
line = match[1];
|
||||
inDoc = true;
|
||||
doc = {raw:{file:file, line:lineNumber, text:[]}};
|
||||
}
|
||||
// are we done?
|
||||
if (inDoc && line.match(/\*\//)) {
|
||||
doc.raw.text = doc.raw.text.join('\n');
|
||||
doc.raw.text = doc.raw.text.replace(/^\n/, '');
|
||||
if (doc.raw.text.match(/@ngdoc/)){
|
||||
callback(doc);
|
||||
}
|
||||
doc = null;
|
||||
inDoc = false;
|
||||
}
|
||||
// is the comment add text
|
||||
if (inDoc){
|
||||
doc.raw.text.push(line.replace(/^\s*\*\s?/, ''));
|
||||
}
|
||||
});
|
||||
callback.done();
|
||||
}));
|
||||
}
|
||||
|
||||
function loadTemplates(cache, callback){
|
||||
fs.readdir('docs', callback.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
files.forEach(function(file){
|
||||
var match = file.match(/^(.*)\.template$/);
|
||||
if (match) {
|
||||
fs.readFile(SRC_DIR + file, callback.waitFor(function(err, content){
|
||||
if (err) return this.error(err);
|
||||
cache[match[1]] = content.toString();
|
||||
}));
|
||||
}
|
||||
});
|
||||
callback();
|
||||
}));
|
||||
};
|
||||
|
||||
function findJsFiles(dir, callback){
|
||||
fs.readdir(dir, callback.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
files.forEach(function(file){
|
||||
var path = dir + '/' + file;
|
||||
fs.lstat(path, callback.waitFor(function(err, stat){
|
||||
if (err) return this.error(err);
|
||||
if (stat.isDirectory())
|
||||
findJsFiles(path, callback.waitMany(callback));
|
||||
else if (/\.js$/.test(path))
|
||||
callback(path);
|
||||
}));
|
||||
});
|
||||
callback.done();
|
||||
}));
|
||||
}
|
||||
|
||||
function processNgDoc(documentation, doc) {
|
||||
if (!doc.ngdoc) return;
|
||||
//console.log('Found:', doc.ngdoc + ':' + doc.name);
|
||||
|
||||
documentation.byName[doc.name] = doc;
|
||||
|
||||
if (doc.methodOf) {
|
||||
if (parent = documentation.byName[doc.methodOf]) {
|
||||
(parent.method = parent.method || []).push(doc);
|
||||
} else {
|
||||
throw 'Owner "' + doc.methodOf + '" is not defined.';
|
||||
}
|
||||
} else {
|
||||
documentation.pages.push(doc);
|
||||
keywordPages.push({
|
||||
name:doc.name,
|
||||
type: doc.ngdoc,
|
||||
keywords:keywords(doc.raw.text)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function writeDoc(pages, callback) {
|
||||
pages.forEach(function(doc) {
|
||||
var template = TEMPLATES[doc.ngdoc];
|
||||
if (!template) throw new Error("No template for:" + doc.ngdoc);
|
||||
var content = mustache.to_html(template, doc);
|
||||
fs.writeFile(OUTPUT_DIR + doc.name + '.html', content, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function findNgDocInDir(directory, docNotify) {
|
||||
fs.readdir(directory, docNotify.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
files.forEach(function(file){
|
||||
console.log(file);
|
||||
if (!file.match(/\.ngdoc$/)) return;
|
||||
fs.readFile(directory + file, docNotify.waitFor(function(err, content){
|
||||
if (err) return this.error(err);
|
||||
docNotify({
|
||||
raw:{
|
||||
text:content.toString(),
|
||||
file: directory + file,
|
||||
line: 1}
|
||||
});
|
||||
}));
|
||||
});
|
||||
docNotify.done();
|
||||
}));
|
||||
}
|
||||
|
||||
function keywordSort(a,b){
|
||||
// supper ugly comparator that orders all utility methods and objects before all the other stuff
|
||||
// like widgets, directives, services, etc.
|
||||
// Mother of all beautiful code please forgive me for the sin that this code certainly is.
|
||||
|
||||
if (a.name === b.name) return 0;
|
||||
if (a.name === 'angular') return -1;
|
||||
if (b.name === 'angular') return 1;
|
||||
|
||||
function namespacedName(page) {
|
||||
return (page.name.match(/\./g).length === 1 && page.type !== 'overview' ? '0' : '1') + page.name;
|
||||
}
|
||||
|
||||
var namespacedA = namespacedName(a),
|
||||
namespacedB = namespacedName(b);
|
||||
|
||||
return namespacedA < namespacedB ? -1 : 1;
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<h1>{{name}}</h1>
|
||||
|
||||
{{#workInProgress}}
|
||||
<fieldset class="workInProgress">
|
||||
<legend>Work In Progress</legend>
|
||||
This page is currently being revised. It might be incomplete or contain inaccuracies.
|
||||
{{{workInProgress.description}}}
|
||||
</fieldset>
|
||||
{{/workInProgress}}
|
||||
|
||||
{{#deprecated}}
|
||||
<fieldset class="deprecated">
|
||||
<legend>Deprecated API</legend>
|
||||
{{deprecated}}
|
||||
</fieldset>
|
||||
{{/deprecated}}
|
||||
|
||||
<h2>Description</h2>
|
||||
{{{description}}}
|
||||
|
||||
<h2>Usage</h2>
|
||||
<h3>In HTML Template Binding</h3>
|
||||
<tt>
|
||||
<pre>
|
||||
<{{element}} {{shortName}}="{{paramFirst.name}}">
|
||||
...
|
||||
</{{element}}>
|
||||
</pre>
|
||||
</tt>
|
||||
|
||||
<h3>Parameters</h3>
|
||||
<ul>
|
||||
{{#param}}
|
||||
<li><tt>{{name}}</tt> –
|
||||
<tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt>
|
||||
<tt>{{#default}}[{{default}}]{{/default}}</tt>
|
||||
– {{{description}}}</li>
|
||||
{{/param}}
|
||||
</ul>
|
||||
{{{paramDescription}}}
|
||||
|
||||
{{#css}}
|
||||
<h3>CSS</h3>
|
||||
{{{css}}}
|
||||
{{/css}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
@@ -1 +0,0 @@
|
||||
NG_PAGES={{{JSON}}};
|
||||
@@ -1,9 +0,0 @@
|
||||
{{#pages}}
|
||||
describe('{{name}}', function(){
|
||||
beforeEach(function(){
|
||||
browser().navigateTo('index.html#!{{name}}');
|
||||
});
|
||||
// {{raw.file}}:{{raw.line}}
|
||||
{{{scenario}}}
|
||||
});
|
||||
{{/pages}}
|
||||
@@ -1,65 +0,0 @@
|
||||
<h1>{{name}}</h1>
|
||||
|
||||
{{#workInProgress}}
|
||||
<fieldset class="workInProgress">
|
||||
<legend>Work In Progress</legend>
|
||||
This page is currently being revised. It might be incomplete or contain inaccuracies.
|
||||
{{{workInProgress.description}}}
|
||||
</fieldset>
|
||||
{{/workInProgress}}
|
||||
|
||||
{{#deprecated}}
|
||||
<fieldset class="deprecated">
|
||||
<legend>Deprecated API</legend>
|
||||
{{deprecated}}
|
||||
</fieldset>
|
||||
{{/deprecated}}
|
||||
|
||||
<h2>Description</h2>
|
||||
{{{description}}}
|
||||
|
||||
<h2>Usage</h2>
|
||||
<h3>In HTML Template Binding</h3>
|
||||
<tt>
|
||||
<span>{{</span>
|
||||
{{paramFirst.name}}_expression
|
||||
| {{shortName}}{{#paramRest}}{{^default}}:{{name}}{{/default}}{{#default}}<i>[:{{name}}={{default}}]</i>{{/default}}{{/paramRest}}
|
||||
<span> }}</span>
|
||||
</tt>
|
||||
<h3>In JavaScript</h3>
|
||||
<tt ng:non-bindable>
|
||||
angular.filter.{{shortName}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/paramRest}} );
|
||||
</tt>
|
||||
|
||||
<h3>Parameters</h3>
|
||||
<ul>
|
||||
{{#param}}
|
||||
<li><tt>{{name}}</tt> –
|
||||
<tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt>
|
||||
<tt>{{#default}}[{{default}}]{{/default}}</tt>
|
||||
– {{{description}}}</li>
|
||||
{{/param}}
|
||||
</ul>
|
||||
|
||||
{{#returns}}
|
||||
<h3>Returns</h3>
|
||||
<tt>{{{{type}}}}</tt> {{{description}}}
|
||||
{{/returns}}
|
||||
|
||||
{{#css}}
|
||||
<h3>CSS</h3>
|
||||
{{{css}}}
|
||||
{{/css}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
@@ -1,53 +0,0 @@
|
||||
<h1>{{name}}</h1>
|
||||
|
||||
{{#workInProgress}}
|
||||
<fieldset class="workInProgress">
|
||||
<legend>Work In Progress</legend>
|
||||
This page is currently being revised. It might be incomplete or contain inaccuracies.
|
||||
{{{workInProgress.description}}}
|
||||
</fieldset>
|
||||
{{/workInProgress}}
|
||||
|
||||
{{#deprecated}}
|
||||
<fieldset class="deprecated">
|
||||
<legend>Deprecated API</legend>
|
||||
{{deprecated}}
|
||||
</fieldset>
|
||||
{{/deprecated}}
|
||||
|
||||
<h2>Description</h2>
|
||||
{{{description}}}
|
||||
|
||||
<h2>Usage</h2>
|
||||
<h3>In HTML Template Binding</h3>
|
||||
<tt>
|
||||
<input type="text" ng:format="{{shortName}}">
|
||||
</tt>
|
||||
<h3>In JavaScript</h3>
|
||||
<tt ng:non-bindable>
|
||||
var userInputString = angular.formatter.{{shortName}}.format(modelValue);<br/>
|
||||
var modelValue = angular.formatter.{{shortName}}.parse(userInputString);
|
||||
</tt>
|
||||
|
||||
{{#returns}}
|
||||
<h3>Returns</h3>
|
||||
<tt>{{{{type}}}}</tt> {{{description}}}
|
||||
{{/returns}}
|
||||
|
||||
{{#css}}
|
||||
<h3>CSS</h3>
|
||||
{{{css}}}
|
||||
{{/css}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
@@ -1,52 +0,0 @@
|
||||
<h1>{{name}}</h1>
|
||||
|
||||
{{#workInProgress}}
|
||||
<fieldset class="workInProgress">
|
||||
<legend>Work In Progress</legend>
|
||||
This page is currently being revised. It might be incomplete or contain inaccuracies.
|
||||
{{{workInProgress.description}}}
|
||||
</fieldset>
|
||||
{{/workInProgress}}
|
||||
|
||||
{{#deprecated}}
|
||||
<fieldset class="deprecated">
|
||||
<legend>Deprecated API</legend>
|
||||
{{deprecated}}
|
||||
</fieldset>
|
||||
{{/deprecated}}
|
||||
|
||||
<h2>Description</h2>
|
||||
{{{description}}}
|
||||
|
||||
<h2>Usage</h2>
|
||||
<tt ng:non-bindable>
|
||||
{{name}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/paramRest}} );
|
||||
</tt>
|
||||
|
||||
<h3>Parameters</h3>
|
||||
<ul>
|
||||
{{#param}}
|
||||
<li><tt>{{name}}</tt> –
|
||||
<tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt>
|
||||
<tt>{{#default}}[{{default}}]{{/default}}</tt>
|
||||
– {{{description}}}</li>
|
||||
{{/param}}
|
||||
</ul>
|
||||
|
||||
{{#returns}}
|
||||
<h3>Returns</h3>
|
||||
<tt>{{{{type}}}}</tt> {{{description}}}
|
||||
{{/returns}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
@@ -1,31 +0,0 @@
|
||||
<h1>{{name}}</h1>
|
||||
|
||||
{{#workInProgress}}
|
||||
<fieldset class="workInProgress">
|
||||
<legend>Work In Progress</legend>
|
||||
This page is currently being revised. It might be incomplete or contain inaccuracies.
|
||||
{{{workInProgress.description}}}
|
||||
</fieldset>
|
||||
{{/workInProgress}}
|
||||
|
||||
{{#deprecated}}
|
||||
<fieldset class="deprecated">
|
||||
<legend>Deprecated API</legend>
|
||||
{{deprecated}}
|
||||
</fieldset>
|
||||
{{/deprecated}}
|
||||
|
||||
{{{description}}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
@@ -25,19 +25,23 @@
|
||||
{{/requires}}
|
||||
</ul>
|
||||
|
||||
{{#method.length}}
|
||||
<h2>Methods</h2>
|
||||
<ul>
|
||||
{{#method}}
|
||||
<li><tt>{{shortName}}</tt>: {{{description}}}</li>
|
||||
<li><tt>{{shortName}}()</tt>: {{{description}}}</li>
|
||||
{{/method}}
|
||||
</ul>
|
||||
{{/method.length}}
|
||||
|
||||
{{#property.length}}
|
||||
<h2>Properties</h2>
|
||||
<ul>
|
||||
{{#property}}
|
||||
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}</tt>{{#description}}: {{{description}}}{{/description}}</li>
|
||||
{{/property}}
|
||||
</ul>
|
||||
{{/property.length}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
console.log(__dirname);
|
||||
require.paths.push(__dirname + "/../");
|
||||
require.paths.push(__dirname + "/../../");
|
||||
var fs = require('fs');
|
||||
var Script = process.binding('evals').Script;
|
||||
var collect = load('docs/collect.js');
|
||||
|
||||
describe('collect', function(){
|
||||
describe('markdown', function(){
|
||||
it('should replace angular in markdown', function(){
|
||||
expect(collect.markdown('<angular/>')).
|
||||
toEqual('<p><tt><angular/></tt></p>');
|
||||
});
|
||||
|
||||
it('should not replace anything in <pre>', function(){
|
||||
expect(collect.markdown('bah x\n<pre>\nangular.k\n</pre>\n asdf x')).
|
||||
toEqual(
|
||||
'<p>bah x</p>' +
|
||||
'<pre>\nangular.k\n</pre>' +
|
||||
'<p>asdf x</p>');
|
||||
});
|
||||
|
||||
it('should replace text between two <pre></pre> tags', function() {
|
||||
expect(collect.markdown('<pre>x</pre># One<pre>b</pre>')).
|
||||
toEqual('<pre>x</pre><h1>One</h1><pre>b</pre>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('processNgDoc', function() {
|
||||
var processNgDoc = collect.processNgDoc,
|
||||
documentation;
|
||||
|
||||
beforeEach(function() {
|
||||
documentation = {
|
||||
pages: [],
|
||||
byName: {}
|
||||
};
|
||||
});
|
||||
|
||||
it('should store references to docs by name', function() {
|
||||
var doc = {ngdoc: 'section', name: 'fake', raw: {text:''}};
|
||||
processNgDoc(documentation, doc);
|
||||
expect(documentation.byName.fake).toBe(doc);
|
||||
});
|
||||
|
||||
it('should connect doc to owner (specified by @methodOf)', function() {
|
||||
var parentDoc = {ngdoc: 'section', name: 'parent', raw: {text:''}};
|
||||
var doc = {ngdoc: 'section', name: 'child', methodOf: 'parent', raw: {text:''}};
|
||||
processNgDoc(documentation, parentDoc);
|
||||
processNgDoc(documentation, doc);
|
||||
expect(documentation.byName.parent.method).toBeDefined();
|
||||
expect(documentation.byName.parent.method[0]).toBe(doc);
|
||||
});
|
||||
|
||||
it('should not add doc to sections if @memberOf specified', function() {
|
||||
var parentDoc = {ngdoc: 'parent', name: 'parent', raw: {text:''}};
|
||||
var doc = {ngdoc: 'child', name: 'child', methodOf: 'parent', raw: {text:''}};
|
||||
processNgDoc(documentation, parentDoc);
|
||||
processNgDoc(documentation, doc);
|
||||
expect(documentation.pages.child).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should throw exception if owner does not exist', function() {
|
||||
expect(function() {
|
||||
processNgDoc(documentation, {ngdoc: 'section', methodOf: 'not.exist', raw: {text:''}});
|
||||
}).toThrow('Owner "not.exist" is not defined.');
|
||||
});
|
||||
|
||||
it('should ignore non-ng docs', function() {
|
||||
var doc = {name: 'anything'};
|
||||
expect(function() {
|
||||
processNgDoc(documentation, doc);
|
||||
}).not.toThrow();
|
||||
expect(documentation.pages).not.toContain(doc);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TAG', function(){
|
||||
var TAG = collect.TAG;
|
||||
var doc;
|
||||
beforeEach(function(){
|
||||
doc = {};
|
||||
});
|
||||
|
||||
describe('@param', function(){
|
||||
it('should parse with no default', function(){
|
||||
TAG.param(doc, 'param',
|
||||
'{(number|string)} number Number \n to format.');
|
||||
expect(doc.param).toEqual([{
|
||||
type : '(number|string)',
|
||||
name : 'number',
|
||||
optional: false,
|
||||
'default' : undefined,
|
||||
description : 'Number \n to format.' }]);
|
||||
});
|
||||
it('should parse with default and optional', function(){
|
||||
TAG.param(doc, 'param',
|
||||
'{(number|string)=} [fractionSize=2] desc');
|
||||
expect(doc.param).toEqual([{
|
||||
type : '(number|string)',
|
||||
name : 'fractionSize',
|
||||
optional: true,
|
||||
'default' : '2',
|
||||
description : 'desc' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('@requires', function() {
|
||||
it('should parse more @requires tag into array', function() {
|
||||
TAG.requires(doc, 'requires', '$service');
|
||||
TAG.requires(doc, 'requires', '$another');
|
||||
|
||||
expect(doc.requires).toEqual([
|
||||
{name: '$service'},
|
||||
{name: '$another'}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('@property', function() {
|
||||
it('should parse @property tags into array', function() {
|
||||
TAG.property(doc, 'property', '{type} name1 desc');
|
||||
TAG.property(doc, 'property', '{type} name2 desc');
|
||||
expect(doc.property.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should parse @property with only name', function() {
|
||||
TAG.property(doc, 'property', 'fake');
|
||||
expect(doc.property[0].name).toEqual('fake');
|
||||
});
|
||||
|
||||
it('should parse @property with optional type', function() {
|
||||
TAG.property(doc, 'property', '{string} name');
|
||||
expect(doc.property[0].name).toEqual('name');
|
||||
expect(doc.property[0].type).toEqual('string');
|
||||
});
|
||||
|
||||
it('should parse @property with optional description', function() {
|
||||
TAG.property(doc, 'property', 'name desc rip tion');
|
||||
expect(doc.property[0].name).toEqual('name');
|
||||
expect(doc.property[0].description).toEqual('desc rip tion');
|
||||
});
|
||||
|
||||
it('should parse @property with type and description both', function() {
|
||||
TAG.property(doc, 'property', '{bool} name desc rip tion');
|
||||
expect(doc.property[0].name).toEqual('name');
|
||||
expect(doc.property[0].type).toEqual('bool');
|
||||
expect(doc.property[0].description).toEqual('desc rip tion');
|
||||
});
|
||||
|
||||
/**
|
||||
* If property description is undefined, this variable is not set in the template,
|
||||
* so the whole @description tag is used instead
|
||||
*/
|
||||
it('should set undefined description to "false"', function() {
|
||||
TAG.property(doc, 'property', 'name');
|
||||
expect(doc.property[0].description).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('@methodOf', function() {
|
||||
it('should parse @methodOf tag', function() {
|
||||
expect(function() {
|
||||
TAG.methodOf(doc, 'methodOf', 'parentName');
|
||||
}).not.toThrow();
|
||||
expect(doc.methodOf).toEqual('parentName');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@returns', function() {
|
||||
it('should not parse @returns without type', function() {
|
||||
expect(function() {TAG.returns(doc, 'returns', 'lala');})
|
||||
.toThrow();
|
||||
});
|
||||
|
||||
it('should parse @returns with type and description', function() {
|
||||
TAG.returns(doc, 'returns', '{string} descrip tion');
|
||||
expect(doc.returns).toEqual({type: 'string', description: 'descrip tion'});
|
||||
});
|
||||
|
||||
it('should transform description of @returns with markdown', function() {
|
||||
TAG.returns(doc, 'returns', '{string} descrip *tion*');
|
||||
expect(doc.returns).toEqual({type: 'string', description: 'descrip <em>tion</em>'});
|
||||
});
|
||||
|
||||
it('should support multiline content', function() {
|
||||
TAG.returns(doc, 'returns', '{string} description\n new line\n another line');
|
||||
expect(doc.returns).
|
||||
toEqual({type: 'string', description: 'description\n new line\n another line'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('@description', function(){
|
||||
it('should support pre blocks', function(){
|
||||
TAG.description(doc, 'description', '<pre>abc</pre>');
|
||||
expect(doc.description).
|
||||
toBe('<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>');
|
||||
});
|
||||
|
||||
it('should support multiple pre blocks', function() {
|
||||
TAG.description(doc, 'description', 'foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>');
|
||||
expect(doc.description).
|
||||
toBe('<p>foo </p>' +
|
||||
'<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>' +
|
||||
'<h2>bah</h2>\n\n' +
|
||||
'<p>foo </p>' +
|
||||
'<div ng:non-bindable><pre class="brush: js; html-script: true;">cba</pre></div>');
|
||||
|
||||
});
|
||||
|
||||
it('should support nested @link annotations with or without description', function() {
|
||||
TAG.description(doc, 'description',
|
||||
'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' +
|
||||
'dad{@link angular.foo}\n\n' +
|
||||
'{@link angular.directive.ng:foo ng:foo}');
|
||||
expect(doc.description).
|
||||
toBe('<p>foo <a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' +
|
||||
'<p>da <a href="#!angular.foo"><code>bar foo bar</code></a> </p>\n\n' +
|
||||
'<p>dad<a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' +
|
||||
'<p><a href="#!angular.directive.ng:foo"><code>ng:foo</code></a></p>');
|
||||
});
|
||||
|
||||
it('should increment all headings by one', function() {
|
||||
TAG.description(doc, 'description', '# foo\nabc');
|
||||
expect(doc.description).
|
||||
toBe('<h2>foo</h2>\n\n<p>abc</p>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@example', function(){
|
||||
it('should not remove {{}}', function(){
|
||||
TAG.example(doc, 'example', 'text {{ abc }}');
|
||||
expect(doc.example).toEqual('text {{ abc }}');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@deprecated', function() {
|
||||
it('should parse @deprecated', function() {
|
||||
TAG.deprecated(doc, 'deprecated', 'Replaced with foo.');
|
||||
expect(doc.deprecated).toBe('Replaced with foo.');
|
||||
})
|
||||
});
|
||||
|
||||
describe('@workInProgress', function() {
|
||||
it('should parse @workInProgress without a description and default to true', function() {
|
||||
TAG.workInProgress(doc, 'workInProgress', '');
|
||||
expect(doc.workInProgress).toEqual({description: ''});
|
||||
});
|
||||
|
||||
it('should parse @workInProgress with a description', function() {
|
||||
TAG.workInProgress(doc, 'workInProgress', 'my description');
|
||||
expect(doc.workInProgress).toEqual({description: '<p>my description</p>'});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('trim', function(){
|
||||
var trim = collect.trim;
|
||||
it('should remove leading/trailing space', function(){
|
||||
expect(trim(' \nabc\n ')).toEqual('abc');
|
||||
});
|
||||
|
||||
it('should remove leading space on every line', function(){
|
||||
expect(trim('\n 1\n 2\n 3\n')).toEqual('1\n 2\n 3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('keywords', function(){
|
||||
var keywords = collect.keywords;
|
||||
it('should collect keywords', function(){
|
||||
expect(keywords('\nHello: World! @ignore.')).toEqual('hello world');
|
||||
expect(keywords('The `ng:class-odd` and ')).toEqual('and ng:class-odd the');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function load(path){
|
||||
var sandbox = {
|
||||
require: require,
|
||||
console: console,
|
||||
__dirname: __dirname,
|
||||
testmode: true
|
||||
};
|
||||
Script.runInNewContext(fs.readFileSync(path), sandbox, path);
|
||||
return sandbox;
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
var ngdoc = require('ngdoc.js');
|
||||
|
||||
describe('ngdoc', function(){
|
||||
var Doc = ngdoc.Doc;
|
||||
describe('Doc', function(){
|
||||
describe('metadata', function(){
|
||||
|
||||
it('should find keywords', function(){
|
||||
expect(new Doc('\nHello: World! @ignore.').keywords()).toEqual('hello world');
|
||||
expect(new Doc('The `ng:class-odd` and').keywords()).toEqual('and ng:class-odd the');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parse', function(){
|
||||
it('should convert @names into properties', function(){
|
||||
var doc = new Doc('\n@name name\n@desc\ndesc\ndesc2\n@dep\n');
|
||||
doc.parse();
|
||||
expect(doc.name).toEqual('name');
|
||||
expect(doc.desc).toEqual('desc\ndesc2');
|
||||
expect(doc.dep).toEqual('');
|
||||
});
|
||||
|
||||
it('should parse parameters', function(){
|
||||
var doc = new Doc(
|
||||
'@param {*} a short\n' +
|
||||
'@param {Type} b med\n' +
|
||||
'@param {Class=} [c=2] long\nline');
|
||||
doc.parse();
|
||||
expect(doc.param).toEqual([
|
||||
{name:'a', description:'short', type:'*', optional:false, 'default':undefined},
|
||||
{name:'b', description:'med', type:'Type', optional:false, 'default':undefined},
|
||||
{name:'c', description:'long\nline', type:'Class', optional:true, 'default':'2'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse return', function(){
|
||||
var doc = new Doc('@returns {Type} text *bold*.');
|
||||
doc.parse();
|
||||
expect(doc.returns).toEqual({
|
||||
type: 'Type',
|
||||
description: 'text <em>bold</em>.'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe('markdown', function(){
|
||||
var markdown = ngdoc.markdown;
|
||||
|
||||
it('should replace angular in markdown', function(){
|
||||
expect(markdown('<angular/>')).
|
||||
toEqual('<p><tt><angular/></tt></p>');
|
||||
});
|
||||
|
||||
it('should not replace anything in <pre>', function(){
|
||||
expect(markdown('bah x\n<pre>\nangular.k\n</pre>\n asdf x')).
|
||||
toEqual(
|
||||
'<p>bah x</p>' +
|
||||
'<div ng:non-bindable><pre class="brush: js; html-script: true;">\n' +
|
||||
'angular.k\n' +
|
||||
'</pre></div>' +
|
||||
'<p>asdf x</p>');
|
||||
});
|
||||
|
||||
it('should replace text between two <pre></pre> tags', function() {
|
||||
expect(markdown('<pre>x</pre># One<pre>b</pre>')).
|
||||
toMatch('</div><h3>One</h3><div');
|
||||
});
|
||||
});
|
||||
|
||||
describe('trim', function(){
|
||||
var trim = ngdoc.trim;
|
||||
it('should remove leading/trailing space', function(){
|
||||
expect(trim(' \nabc\n ')).toEqual('abc');
|
||||
});
|
||||
|
||||
it('should remove leading space on every line', function(){
|
||||
expect(trim('\n 1\n 2\n 3\n')).toEqual('1\n 2\n 3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('merge', function(){
|
||||
it('should merge child with parent', function(){
|
||||
var parent = new Doc({name:'angular.service.abc'});
|
||||
var methodA = new Doc({name:'methodA', methodOf:'angular.service.abc'});
|
||||
var methodB = new Doc({name:'methodB', methodOf:'angular.service.abc'});
|
||||
var propA = new Doc({name:'propA', propertyOf:'angular.service.abc'});
|
||||
var propB = new Doc({name:'propB', propertyOf:'angular.service.abc'});
|
||||
;var docs = [methodB, methodA, propB, propA, parent]; // keep wrong order;
|
||||
ngdoc.merge(docs);
|
||||
expect(docs.length).toEqual(1);
|
||||
expect(docs[0].name).toEqual('angular.service.abc');
|
||||
expect(docs[0].methods).toEqual([methodA, methodB]);
|
||||
expect(docs[0].properties).toEqual([propA, propB]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
////////////////////////////////////////
|
||||
|
||||
describe('TAG', function(){
|
||||
describe('@param', function(){
|
||||
it('should parse with no default', function(){
|
||||
var doc = new Doc('@param {(number|string)} number Number \n to format.');
|
||||
doc.parse();
|
||||
expect(doc.param).toEqual([{
|
||||
type : '(number|string)',
|
||||
name : 'number',
|
||||
optional: false,
|
||||
'default' : undefined,
|
||||
description : 'Number \n to format.' }]);
|
||||
});
|
||||
|
||||
it('should parse with default and optional', function(){
|
||||
var doc = new Doc('@param {(number|string)=} [fractionSize=2] desc');
|
||||
doc.parse();
|
||||
expect(doc.param).toEqual([{
|
||||
type : '(number|string)',
|
||||
name : 'fractionSize',
|
||||
optional: true,
|
||||
'default' : '2',
|
||||
description : 'desc' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('@requires', function() {
|
||||
it('should parse more @requires tag into array', function() {
|
||||
var doc = new Doc('@requires $service\n@requires $another');
|
||||
doc.parse();
|
||||
expect(doc.requires).toEqual(['$service', '$another']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('@property', function() {
|
||||
it('should parse @property tags into array', function() {
|
||||
var doc = new Doc("@property {type} name1 desc\n@property {type} name2 desc");
|
||||
doc.parse();
|
||||
expect(doc.properties.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should parse @property with only name', function() {
|
||||
var doc = new Doc("@property fake");
|
||||
doc.parse();
|
||||
expect(doc.properties[0].name).toEqual('fake');
|
||||
});
|
||||
|
||||
it('should parse @property with optional type', function() {
|
||||
var doc = new Doc("@property {string} name");
|
||||
doc.parse();
|
||||
expect(doc.properties[0].name).toEqual('name');
|
||||
expect(doc.properties[0].type).toEqual('string');
|
||||
});
|
||||
|
||||
it('should parse @property with optional description', function() {
|
||||
var doc = new Doc("@property name desc rip tion");
|
||||
doc.parse();
|
||||
expect(doc.properties[0].name).toEqual('name');
|
||||
expect(doc.properties[0].description).toEqual('desc rip tion');
|
||||
});
|
||||
|
||||
it('should parse @property with type and description both', function() {
|
||||
var doc = new Doc("@property {bool} name desc rip tion");
|
||||
doc.parse();
|
||||
expect(doc.properties[0].name).toEqual('name');
|
||||
expect(doc.properties[0].type).toEqual('bool');
|
||||
expect(doc.properties[0].description).toEqual('desc rip tion');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('@returns', function() {
|
||||
it('should not parse @returns without type', function() {
|
||||
var doc = new Doc("@returns lala");
|
||||
expect(doc.parse).toThrow();
|
||||
});
|
||||
|
||||
it('should parse @returns with type and description', function() {
|
||||
var doc = new Doc("@returns {string} descrip tion");
|
||||
doc.parse();
|
||||
expect(doc.returns).toEqual({type: 'string', description: 'descrip tion'});
|
||||
});
|
||||
|
||||
it('should transform description of @returns with markdown', function() {
|
||||
var doc = new Doc("@returns {string} descrip *tion*");
|
||||
doc.parse();
|
||||
expect(doc.returns).toEqual({type: 'string', description: 'descrip <em>tion</em>'});
|
||||
});
|
||||
|
||||
it('should support multiline content', function() {
|
||||
var doc = new Doc("@returns {string} description\n new line\n another line");
|
||||
doc.parse();
|
||||
expect(doc.returns).
|
||||
toEqual({type: 'string', description: 'description\n new line\n another line'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('@description', function(){
|
||||
it('should support pre blocks', function(){
|
||||
var doc = new Doc("@description <pre>abc</pre>");
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
toBe('<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>');
|
||||
});
|
||||
|
||||
it('should support multiple pre blocks', function() {
|
||||
var doc = new Doc("@description foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>");
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
toBe('<p>foo </p>' +
|
||||
'<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>' +
|
||||
'<h3>bah</h3>\n\n' +
|
||||
'<p>foo </p>' +
|
||||
'<div ng:non-bindable><pre class="brush: js; html-script: true;">cba</pre></div>');
|
||||
|
||||
});
|
||||
|
||||
it('should support nested @link annotations with or without description', function() {
|
||||
var doc = new Doc("@description " +
|
||||
'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' +
|
||||
'dad{@link angular.foo}\n\n' +
|
||||
'{@link angular.directive.ng:foo ng:foo}');
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
toBe('<p>foo <a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' +
|
||||
'<p>da <a href="#!angular.foo"><code>bar foo bar</code></a> </p>\n\n' +
|
||||
'<p>dad<a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' +
|
||||
'<p><a href="#!angular.directive.ng:foo"><code>ng:foo</code></a></p>');
|
||||
});
|
||||
|
||||
it('should increment all headings by two', function() {
|
||||
var doc = new Doc('@description # foo\nabc\n## bar \n xyz');
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
toBe('<h3>foo</h3>\n\n<p>abc</p>\n\n<h4>bar</h4>\n\n<p>xyz</p>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@example', function(){
|
||||
it('should not remove {{}}', function(){
|
||||
var doc = new Doc('@example text {{ abc }}');
|
||||
doc.parse();
|
||||
expect(doc.example).toEqual('text {{ abc }}');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@deprecated', function() {
|
||||
it('should parse @deprecated', function() {
|
||||
var doc = new Doc('@deprecated Replaced with foo.');
|
||||
doc.parse();
|
||||
expect(doc.deprecated).toBe('Replaced with foo.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
if (global.jasmine) return;
|
||||
|
||||
require.paths.push(__dirname + "/../../lib");
|
||||
require.paths.push(__dirname + '/../src');
|
||||
var jasmine = require('jasmine-1.0.1');
|
||||
var sys = require('util');
|
||||
|
||||
for(var key in jasmine) {
|
||||
global[key] = jasmine[key];
|
||||
}
|
||||
|
||||
//Patch Jasmine for proper stack traces
|
||||
jasmine.Spec.prototype.fail = function (e) {
|
||||
var expectationResult = new jasmine.ExpectationResult({
|
||||
passed: false,
|
||||
message: e ? jasmine.util.formatException(e) : 'Exception'
|
||||
});
|
||||
// PATCH
|
||||
if (e) {
|
||||
expectationResult.trace = e;
|
||||
}
|
||||
this.results_.addResult(expectationResult);
|
||||
};
|
||||
|
||||
|
||||
|
||||
var isVerbose = false;
|
||||
var showColors = true;
|
||||
process.argv.forEach(function(arg){
|
||||
switch(arg) {
|
||||
case '--color': showColors = true; break;
|
||||
case '--noColor': showColors = false; break;
|
||||
case '--verbose': isVerbose = true; break;
|
||||
}
|
||||
});
|
||||
|
||||
jasmine.executeSpecsInFolder(__dirname, function(runner, log){
|
||||
process.exit(runner.results().failedCount);
|
||||
}, isVerbose, showColors);
|
||||
@@ -0,0 +1,18 @@
|
||||
var writer = require('writer.js');
|
||||
describe('writer', function(){
|
||||
describe('toString', function(){
|
||||
var toString = writer.toString;
|
||||
|
||||
it('should merge string', function(){
|
||||
expect(toString('abc')).toEqual('abc');
|
||||
});
|
||||
|
||||
it('should merge obj', function(){
|
||||
expect(toString({a:1})).toEqual('{"a":1}');
|
||||
});
|
||||
|
||||
it('should merge array', function(){
|
||||
expect(toString(['abc',{}])).toEqual('abc{}');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
require.paths.push("./lib");
|
||||
var jasmine = require('jasmine-1.0.1');
|
||||
var sys = require('util');
|
||||
|
||||
for(var key in jasmine) {
|
||||
global[key] = jasmine[key];
|
||||
}
|
||||
|
||||
var isVerbose = false;
|
||||
var showColors = true;
|
||||
process.argv.forEach(function(arg){
|
||||
switch(arg) {
|
||||
case '--color': showColors = true; break;
|
||||
case '--noColor': showColors = false; break;
|
||||
case '--verbose': isVerbose = true; break;
|
||||
}
|
||||
});
|
||||
|
||||
jasmine.executeSpecsInFolder(__dirname + '/spec', function(runner, log){
|
||||
process.exit(runner.results().failedCount);
|
||||
}, isVerbose, showColors);
|
||||
@@ -2,7 +2,10 @@ function noop(){}
|
||||
|
||||
function chain(delegateFn, explicitDone){
|
||||
var onDoneFn = noop;
|
||||
var onErrorFn = noop;
|
||||
var onErrorFn = function(e){
|
||||
console.error(e.stack || e);
|
||||
process.exit(-1);
|
||||
};
|
||||
var waitForCount = 1;
|
||||
delegateFn = delegateFn || noop;
|
||||
var stackError = new Error('capture stack');
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* DOM generation class
|
||||
*/
|
||||
|
||||
exports.DOM = DOM;
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
function DOM(){
|
||||
this.out = [];
|
||||
this.headingDepth = 1;
|
||||
}
|
||||
|
||||
var INLINE_TAGS = {
|
||||
i: true,
|
||||
b: true
|
||||
};
|
||||
|
||||
DOM.prototype = {
|
||||
toString: function() {
|
||||
return this.out.join('');
|
||||
},
|
||||
|
||||
text: function(content) {
|
||||
if (typeof content == "string") {
|
||||
this.out.push(content.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'));
|
||||
} else if (typeof content == 'function') {
|
||||
content.call(this, this);
|
||||
} else if (content instanceof Array) {
|
||||
this.ul(content);
|
||||
}
|
||||
},
|
||||
|
||||
html: function(html) {
|
||||
if (html) {
|
||||
this.out.push(html);
|
||||
}
|
||||
},
|
||||
|
||||
tag: function(name, attr, text) {
|
||||
if (!text) {
|
||||
text = attr;
|
||||
attr = {};
|
||||
if (name == 'code')
|
||||
attr['ng:non-bindable'] = '';
|
||||
}
|
||||
this.out.push('<' + name);
|
||||
for(var key in attr) {
|
||||
this.out.push(" " + key + '="' + attr[key] + '"');
|
||||
}
|
||||
this.out.push('>');
|
||||
this.text(text);
|
||||
this.out.push('</' + name + '>');
|
||||
if (!INLINE_TAGS[name])
|
||||
this.out.push('\n');
|
||||
},
|
||||
|
||||
code: function(text) {
|
||||
this.tag('div', {'ng:non-bindable':''}, function(){
|
||||
this.tag('pre', {'class':"brush: js; html-script: true;"}, text);
|
||||
});
|
||||
},
|
||||
|
||||
example: function(source, scenario) {
|
||||
if (source || scenario) {
|
||||
this.h('Example', function(){
|
||||
if (scenario === false) {
|
||||
this.code(source);
|
||||
} else {
|
||||
this.tag('doc:example', function(){
|
||||
if (source) this.tag('doc:source', source);
|
||||
if (scenario) this.tag('doc:scenario', scenario);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
h: function(heading, content, fn){
|
||||
if (content==undefined || content && content.legth == 0) return;
|
||||
this.tag('h' + this.headingDepth, heading);
|
||||
this.headingDepth++;
|
||||
if (content instanceof Array) {
|
||||
this.ul(content, {'class': heading.toLowerCase()}, fn);
|
||||
} else if (fn) {
|
||||
fn.call(this, content);
|
||||
} else {
|
||||
this.text(content);
|
||||
}
|
||||
this.headingDepth--;
|
||||
},
|
||||
|
||||
h1: function(attr, text) {
|
||||
this.tag('h1', attr, text);
|
||||
},
|
||||
|
||||
h2: function(attr, text) {
|
||||
this.tag('h2', attr, text);
|
||||
},
|
||||
|
||||
h3: function(attr, text) {
|
||||
this.tag('h3', attr, text);
|
||||
},
|
||||
|
||||
p: function(attr, text) {
|
||||
this.tag('p', attr, text);
|
||||
},
|
||||
|
||||
ul: function(list, attr, fn) {
|
||||
if (typeof attr == 'function') {
|
||||
fn = attr;
|
||||
attr = {};
|
||||
}
|
||||
this.tag('ul', attr, function(dom){
|
||||
list.forEach(function(item){
|
||||
dom.out.push('<li>');
|
||||
dom.text(fn ? fn(item) : item);
|
||||
dom.out.push('</li>\n');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
require.paths.push(__dirname);
|
||||
require.paths.push('lib');
|
||||
var reader = require('reader.js'),
|
||||
ngdoc = require('ngdoc.js'),
|
||||
writer = require('writer.js'),
|
||||
callback = require('callback.js');
|
||||
|
||||
var docs = [];
|
||||
var start;
|
||||
var work = callback.chain(function(){
|
||||
start = now();
|
||||
console.log('Generating Angular Reference Documentation...');
|
||||
reader.collect(work.waitMany(function(text, file, line){
|
||||
var doc = new ngdoc.Doc(text, file, line);
|
||||
docs.push(doc);
|
||||
doc.parse();
|
||||
}));
|
||||
});
|
||||
var writes = callback.chain(function(){
|
||||
ngdoc.merge(docs);
|
||||
docs.forEach(function(doc){
|
||||
writer.output(doc.name + '.html', doc.html(), writes.waitFor());
|
||||
});
|
||||
var metadata = ngdoc.metadata(docs);
|
||||
writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata), ';'], writes.waitFor());
|
||||
writer.copy('index.html', writes.waitFor());
|
||||
writer.copy('docs.js', writes.waitFor());
|
||||
writer.copy('docs.css', writes.waitFor());
|
||||
writer.copy('doc_widgets.js', writes.waitFor());
|
||||
writer.copy('doc_widgets.css', writes.waitFor());
|
||||
writer.copy('docs-scenario.html', writes.waitFor());
|
||||
writer.output('docs-scenario.js', ngdoc.scenarios(docs), writes.waitFor());
|
||||
writer.copy('syntaxhighlighter/shBrushJScript.js', writes.waitFor());
|
||||
writer.copy('syntaxhighlighter/shBrushXml.js', writes.waitFor());
|
||||
writer.copy('syntaxhighlighter/shCore.css', writes.waitFor());
|
||||
writer.copy('syntaxhighlighter/shCore.js', writes.waitFor());
|
||||
writer.copy('syntaxhighlighter/shThemeDefault.css', writes.waitFor());
|
||||
writer.copy('jquery.min.js', writes.waitFor());
|
||||
});
|
||||
writes.onDone(function(){
|
||||
console.log('DONE. Generated ' + docs.length + ' pages in ' +
|
||||
(now()-start) + 'ms.' );
|
||||
});
|
||||
work.onDone(writes);
|
||||
writer.makeDir('build/docs/syntaxhighlighter', work);
|
||||
|
||||
///////////////////////////////////
|
||||
function now(){ return new Date().getTime(); }
|
||||
@@ -0,0 +1,614 @@
|
||||
/**
|
||||
* All parsing/transformation code goes here. All code here should be sync to ease testing.
|
||||
*/
|
||||
|
||||
var Showdown = require('showdown').Showdown;
|
||||
var DOM = require('dom.js').DOM;
|
||||
var NEW_LINE = /\n\r?/;
|
||||
|
||||
exports.markdown = markdown;
|
||||
exports.markdownNoP = markdownNoP;
|
||||
exports.trim = trim;
|
||||
exports.metadata = metadata;
|
||||
exports.scenarios = scenarios;
|
||||
exports.merge = merge;
|
||||
exports.Doc = Doc;
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function Doc(text, file, line) {
|
||||
if (typeof text == 'object') {
|
||||
for ( var key in text) {
|
||||
this[key] = text[key];
|
||||
}
|
||||
} else {
|
||||
this.text = text;
|
||||
this.file = file;
|
||||
this.line = line;
|
||||
}
|
||||
}
|
||||
Doc.METADATA_IGNORE = (function(){
|
||||
var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8');
|
||||
return words.toString().split(/[,\s\n\r]+/gm);
|
||||
})();
|
||||
|
||||
|
||||
|
||||
Doc.prototype = {
|
||||
keywords: function keywords(){
|
||||
var keywords = {};
|
||||
Doc.METADATA_IGNORE.forEach(function(ignore){ keywords[ignore] = true; });
|
||||
var words = [];
|
||||
var tokens = this.text.toLowerCase().split(/[,\.\`\'\"\s]+/mg);
|
||||
tokens.forEach(function(key){
|
||||
var match = key.match(/^(([a-z]|ng\:)[\w\_\-]{2,})/);
|
||||
if (match){
|
||||
key = match[1];
|
||||
if (!keywords[key]) {
|
||||
keywords[key] = true;
|
||||
words.push(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
words.sort();
|
||||
return words.join(' ');
|
||||
},
|
||||
|
||||
parse: function(){
|
||||
var atName;
|
||||
var atText;
|
||||
var match;
|
||||
var self = this;
|
||||
self.text.split(NEW_LINE).forEach(function(line){
|
||||
if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
|
||||
// we found @name ...
|
||||
// if we have existing name
|
||||
flush();
|
||||
atName = match[1];
|
||||
atText = [];
|
||||
if(match[3]) atText.push(match[3]);
|
||||
} else {
|
||||
if (atName) {
|
||||
atText.push(line);
|
||||
}
|
||||
}
|
||||
});
|
||||
flush();
|
||||
this.shortName = (this.name || '').split(/[\.#]/).pop();
|
||||
this.description = markdown(this.description);
|
||||
|
||||
function flush(){
|
||||
if (atName) {
|
||||
var text = trim(atText.join('\n'));
|
||||
if (atName == 'param') {
|
||||
var match = text.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
|
||||
// 1 12 2 34 4 5 5 6 6 3 7 7
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'param' format: " + text);
|
||||
}
|
||||
var param = {
|
||||
name: match[5] || match[4],
|
||||
description:markdownNoP(text.replace(match[0], match[7])),
|
||||
type: match[1],
|
||||
optional: !!match[2],
|
||||
'default':match[6]
|
||||
};
|
||||
self.param = self.param || [];
|
||||
self.param.push(param);
|
||||
} else if (atName == 'returns') {
|
||||
var match = text.match(/^{([^}=]+)}\s+(.*)/);
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'returns' format: " + text);
|
||||
}
|
||||
self.returns = {
|
||||
type: match[1],
|
||||
description: markdownNoP(text.replace(match[0], match[2]))
|
||||
};
|
||||
} else if(atName == 'requires') {
|
||||
self.requires = self.requires || [];
|
||||
self.requires.push(text);
|
||||
} else if(atName == 'property') {
|
||||
var match = text.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/);
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'property' format: " + text);
|
||||
}
|
||||
var property = {
|
||||
type: match[2],
|
||||
name: match[3],
|
||||
description: match[5] || ''
|
||||
};
|
||||
self.properties = self.properties || [];
|
||||
self.properties.push(property);
|
||||
} else {
|
||||
self[atName] = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
html: function(){
|
||||
var dom = new DOM(),
|
||||
self = this;
|
||||
|
||||
dom.h(this.name, function(){
|
||||
notice('workInProgress', 'Work in Progress',
|
||||
'This page is currently being revised. It might be incomplete or contain inaccuracies.');
|
||||
notice('depricated', 'Depricated API');
|
||||
dom.h('Description', self.description, html);
|
||||
dom.h('Dependencies', self.requires);
|
||||
|
||||
usage();
|
||||
|
||||
dom.h('Methods', self.methods, function(method){
|
||||
var signature = (method.param || []).map(property('name'));
|
||||
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
|
||||
dom.html(method.description);
|
||||
method.html_usage_parameters(dom);
|
||||
dom.example(method.example, false);
|
||||
});
|
||||
});
|
||||
dom.h('Properties', self.properties, function(property){
|
||||
dom.h(property.name, function(){
|
||||
dom.text(property.description);
|
||||
dom.example(property.example, false);
|
||||
});
|
||||
});
|
||||
|
||||
dom.example(self.example, self.scenario);
|
||||
});
|
||||
|
||||
return dom.toString();
|
||||
|
||||
//////////////////////////
|
||||
|
||||
function html(text){
|
||||
this.html(text);
|
||||
}
|
||||
|
||||
function usage(){
|
||||
(self['html_usage_' + self.ngdoc] || function(){
|
||||
throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
|
||||
}).call(self, dom);
|
||||
}
|
||||
|
||||
function section(name, property, fn) {
|
||||
var value = self[property];
|
||||
if (value) {
|
||||
dom.h2(name);
|
||||
if (typeof value == 'string') {
|
||||
value = markdown(value) + '\n';
|
||||
fn ? fn(value) : dom.html(value);
|
||||
} else if (value instanceof Array) {
|
||||
dom.ul(value, fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function notice(name, legend, msg){
|
||||
if (self[name] == undefined) return;
|
||||
dom.tag('fieldset', {'class':name}, function(dom){
|
||||
dom.tag('legend', legend);
|
||||
dom.text(msg);
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
html_usage_parameters: function(dom) {
|
||||
dom.h('Parameters', this.param, function(param){
|
||||
dom.tag('code', function(){
|
||||
dom.text(param.name);
|
||||
if (param.optional) {
|
||||
dom.tag('i', function(){
|
||||
dom.text('(optional');
|
||||
if(param['default']) {
|
||||
dom.text('=' + param['default']);
|
||||
}
|
||||
dom.text(')');
|
||||
});
|
||||
}
|
||||
dom.text(' – {');
|
||||
dom.text(param.type);
|
||||
dom.text('} – ');
|
||||
});
|
||||
dom.html(param.description);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_returns: function(dom) {
|
||||
var self = this;
|
||||
if (self.returns) {
|
||||
dom.h('Returns', function(){
|
||||
dom.tag('code', self.returns.type);
|
||||
dom.text('– ');
|
||||
dom.html(self.returns.description);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
html_usage_function: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.code(function(){
|
||||
dom.text(self.name);
|
||||
dom.text('(');
|
||||
var first = true;
|
||||
(self.param || []).forEach(function(param){
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
dom.text(', ');
|
||||
}
|
||||
dom.text(param.name);
|
||||
});
|
||||
dom.text(');');
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_directive: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.tag('pre', {'class':"brush: js; html-script: true;"}, function(){
|
||||
dom.text('<' + self.element + ' ');
|
||||
dom.text(self.shortName);
|
||||
if (self.param) {
|
||||
dom.text('="' + self.param[0].name + '"');
|
||||
}
|
||||
dom.text('>\n ...\n');
|
||||
dom.text('</' + self.element + '>');
|
||||
});
|
||||
self.html_usage_parameters(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_filter: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.h('In HTML Template Binding', function(){
|
||||
dom.tag('code', function(){
|
||||
dom.text('{{ ');
|
||||
dom.text(self.shortName);
|
||||
dom.text('_expression | ');
|
||||
dom.text(self.shortName);
|
||||
var first = true;
|
||||
(self.param||[]).forEach(function(param){
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
if (param.optional) {
|
||||
dom.tag('i', function(){
|
||||
dom.text('[:' + param.name + ']');
|
||||
});
|
||||
} else {
|
||||
dom.text(':' + param.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
dom.text(' }}');
|
||||
});
|
||||
});
|
||||
|
||||
dom.h3('In JavaScript', function(){
|
||||
dom.tag('code', function(){
|
||||
dom.text('angular.filter.');
|
||||
dom.text(self.shortName);
|
||||
dom.text('(');
|
||||
var first = true;
|
||||
(self.param||[]).forEach(function(param){
|
||||
if (first) {
|
||||
first = false;
|
||||
dom.text(param.name);
|
||||
} else {
|
||||
if (param.optional) {
|
||||
dom.tag('i', function(){
|
||||
dom.text('[, ' + param.name + ']');
|
||||
});
|
||||
} else {
|
||||
dom.text(', ' + param.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
dom.text(')');
|
||||
});
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_formatter: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.h('In HTML Template Binding', function(){
|
||||
dom.code(function(){
|
||||
dom.text('<input type="text" ng:format="');
|
||||
dom.text(self.shortName);
|
||||
dom.text('">');
|
||||
});
|
||||
});
|
||||
|
||||
dom.h3('In JavaScript', function(){
|
||||
dom.code(function(){
|
||||
dom.text('var userInputString = angular.formatter.');
|
||||
dom.text(self.shortName);
|
||||
dom.text('.format(modelValue);');
|
||||
});
|
||||
dom.html('<br/>');
|
||||
dom.code(function(){
|
||||
dom.text('var modelValue = angular.formatter.');
|
||||
dom.text(self.shortName);
|
||||
dom.text('.parse(userInputString);');
|
||||
});
|
||||
});
|
||||
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_validator: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.h('In HTML Template Binding', function(){
|
||||
dom.code(function(){
|
||||
dom.text('<input type="text" ng:validate="');
|
||||
dom.text(self.shortName);
|
||||
var first = true;
|
||||
(self.param||[]).forEach(function(param){
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
if (param.optional) {
|
||||
dom.text('[:' + param.name + ']');
|
||||
} else {
|
||||
dom.text(':' + param.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
dom.text('"/>');
|
||||
});
|
||||
});
|
||||
|
||||
dom.h('In JavaScript', function(){
|
||||
dom.code(function(){
|
||||
dom.text('angular.validator.');
|
||||
dom.text(self.shortName);
|
||||
dom.text('(');
|
||||
var first = true;
|
||||
(self.param||[]).forEach(function(param){
|
||||
if (first) {
|
||||
first = false;
|
||||
dom.text(param.name);
|
||||
} else {
|
||||
if (param.optional) {
|
||||
dom.text('[, ' + param.name + ']');
|
||||
} else {
|
||||
dom.text(', ' + param.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
dom.text(')');
|
||||
});
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_widget: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.h('In HTML Template Binding', function(){
|
||||
dom.code(function(){
|
||||
if (self.shortName.match(/^@/)) {
|
||||
dom.text('<');
|
||||
dom.text(self.element);
|
||||
dom.text(' ');
|
||||
dom.text(self.shortName.substring(1));
|
||||
if (self.param) {
|
||||
dom.text('="');
|
||||
dom.text(self.param[0].name);
|
||||
dom.text('"');
|
||||
}
|
||||
dom.text('>\n ...\n</');
|
||||
dom.text(self.element);
|
||||
dom.text('>');
|
||||
} else {
|
||||
dom.text('<');
|
||||
dom.text(self.shortName);
|
||||
(self.param||[]).forEach(function(param){
|
||||
if (param.optional) {
|
||||
dom.text(' [' + param.name + '="..."]');
|
||||
} else {
|
||||
dom.text(' ' + param.name + '="..."');
|
||||
}
|
||||
});
|
||||
dom.text('></');
|
||||
dom.text(self.shortName);
|
||||
dom.text('>');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_overview: function(dom){
|
||||
},
|
||||
|
||||
html_usage_service: function(dom){
|
||||
}
|
||||
|
||||
};
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function markdown (text) {
|
||||
if (!text) return text;
|
||||
var parts = text.split(/(<pre>[\s\S]*?<\/pre>)/),
|
||||
match;
|
||||
|
||||
parts.forEach(function(text, i){
|
||||
if (text.match(/^<pre>/)) {
|
||||
text = text.
|
||||
replace(/^<pre>/, '<div ng:non-bindable><pre class="brush: js; html-script: true;">').
|
||||
replace(/<\/pre>/, '</pre></div>');
|
||||
} else {
|
||||
text = text.replace(/<angular\/>/gm, '<tt><angular/></tt>');
|
||||
text = new Showdown.converter().makeHtml(text.replace(/^#/gm, '###'));
|
||||
|
||||
while (match = text.match(R_LINK)) {
|
||||
text = text.replace(match[0], '<a href="#!' + match[1] + '"><code>' +
|
||||
(match[4] || match[1]) +
|
||||
'</code></a>');
|
||||
}
|
||||
}
|
||||
parts[i] = text;
|
||||
});
|
||||
return parts.join('');
|
||||
};
|
||||
var R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m;
|
||||
// 1 123 3 4 42
|
||||
function markdownNoP(text) {
|
||||
var lines = markdown(text).split(NEW_LINE);
|
||||
var last = lines.length - 1;
|
||||
lines[0] = lines[0].replace(/^<p>/, '');
|
||||
lines[last] = lines[last].replace(/<\/p>$/, '');
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function scenarios(docs){
|
||||
var specs = [];
|
||||
docs.forEach(function(doc){
|
||||
if (doc.scenario) {
|
||||
specs.push('describe("');
|
||||
specs.push(doc.name);
|
||||
specs.push('", function(){\n');
|
||||
specs.push(' beforeEach(function(){\n');
|
||||
specs.push(' browser().navigateTo("index.html#!' + doc.name + '");');
|
||||
specs.push(' });\n\n');
|
||||
specs.push(doc.scenario);
|
||||
specs.push('\n});\n\n');
|
||||
}
|
||||
});
|
||||
return specs;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function metadata(docs){
|
||||
var words = [];
|
||||
docs.forEach(function(doc){
|
||||
words.push({
|
||||
name:doc.name,
|
||||
type: doc.ngdoc,
|
||||
keywords:doc.keywords()
|
||||
});
|
||||
});
|
||||
words.sort(keywordSort);
|
||||
return words;
|
||||
}
|
||||
|
||||
function keywordSort(a,b){
|
||||
// supper ugly comparator that orders all utility methods and objects before all the other stuff
|
||||
// like widgets, directives, services, etc.
|
||||
// Mother of all beautiful code please forgive me for the sin that this code certainly is.
|
||||
|
||||
if (a.name === b.name) return 0;
|
||||
if (a.name === 'angular') return -1;
|
||||
if (b.name === 'angular') return 1;
|
||||
|
||||
function namespacedName(page) {
|
||||
return (page.name.match(/\./g).length === 1 && page.type !== 'overview' ? '0' : '1') + page.name;
|
||||
}
|
||||
|
||||
var namespacedA = namespacedName(a),
|
||||
namespacedB = namespacedName(b);
|
||||
|
||||
return namespacedA < namespacedB ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function trim(text) {
|
||||
var MAX = 9999;
|
||||
var empty = RegExp.prototype.test.bind(/^\s*$/);
|
||||
var lines = text.split('\n');
|
||||
var minIndent = MAX;
|
||||
lines.forEach(function(line){
|
||||
minIndent = Math.min(minIndent, indent(line));
|
||||
});
|
||||
for ( var i = 0; i < lines.length; i++) {
|
||||
lines[i] = lines[i].substring(minIndent);
|
||||
}
|
||||
// remove leading lines
|
||||
while (empty(lines[0])) {
|
||||
lines.shift();
|
||||
}
|
||||
// remove trailing
|
||||
while (empty(lines[lines.length - 1])) {
|
||||
lines.pop();
|
||||
}
|
||||
return lines.join('\n');
|
||||
|
||||
function indent(line) {
|
||||
for(var i = 0; i < line.length; i++) {
|
||||
if (line.charAt(i) != ' ') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return MAX;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function merge(docs){
|
||||
var byName = {};
|
||||
docs.forEach(function(doc){
|
||||
byName[doc.name] = doc;
|
||||
});
|
||||
for(var i=0; i<docs.length;) {
|
||||
if (findParent(docs[i], 'method') ||
|
||||
findParent(docs[i], 'property')) {
|
||||
docs.splice(i, 1);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
function findParent(doc, name){
|
||||
var parentName = doc[name+'Of'];
|
||||
if (!parentName) return false;
|
||||
|
||||
var parent = byName[parentName];
|
||||
if (!parent)
|
||||
throw new Error("No parent named '" + parentName + "' for '" +
|
||||
doc.name + "' in @" + name + "Of.");
|
||||
|
||||
var listName = (name + 's').replace(/ys$/, 'ies');
|
||||
var list = parent[listName] = (parent[listName] || []);
|
||||
list.push(doc);
|
||||
list.sort(orderByName);
|
||||
return true;
|
||||
}
|
||||
|
||||
function orderByName(a, b){
|
||||
return a.name < b.name ? -1 : (a.name > b.name ? 1 : 0);
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
function property(name) {
|
||||
return function(value){
|
||||
return value[name];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* All reading related code here. This is so that we can separate the async code from sync code
|
||||
* for testability
|
||||
*/
|
||||
require.paths.push(__dirname);
|
||||
var fs = require('fs'),
|
||||
callback = require('callback');
|
||||
|
||||
var NEW_LINE = /\n\r?/;
|
||||
|
||||
function collect(callback){
|
||||
findJsFiles('src', callback.waitMany(function(file) {
|
||||
//console.log('reading', file, '...');
|
||||
findNgDocInJsFile(file, callback.waitMany(function(doc, line) {
|
||||
callback(doc, file, line);
|
||||
}));
|
||||
}));
|
||||
findNgDocInDir('docs/', callback.waitMany(callback));
|
||||
callback.done();
|
||||
}
|
||||
|
||||
function findJsFiles(dir, callback){
|
||||
fs.readdir(dir, callback.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
files.forEach(function(file){
|
||||
var path = dir + '/' + file;
|
||||
fs.lstat(path, callback.waitFor(function(err, stat){
|
||||
if (err) return this.error(err);
|
||||
if (stat.isDirectory())
|
||||
findJsFiles(path, callback.waitMany(callback));
|
||||
else if (/\.js$/.test(path))
|
||||
callback(path);
|
||||
}));
|
||||
});
|
||||
callback.done();
|
||||
}));
|
||||
}
|
||||
|
||||
function findNgDocInDir(directory, docNotify) {
|
||||
fs.readdir(directory, docNotify.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
files.forEach(function(file){
|
||||
//console.log('reading', directory + file, '...');
|
||||
if (!file.match(/\.ngdoc$/)) return;
|
||||
fs.readFile(directory + file, docNotify.waitFor(function(err, content){
|
||||
if (err) return this.error(err);
|
||||
docNotify(content.toString(), directory + file, 1);
|
||||
}));
|
||||
});
|
||||
docNotify.done();
|
||||
}));
|
||||
}
|
||||
|
||||
function findNgDocInJsFile(file, callback) {
|
||||
fs.readFile(file, callback.waitFor(function(err, content){
|
||||
var lines = content.toString().split(NEW_LINE);
|
||||
var text;
|
||||
var startingLine ;
|
||||
var match;
|
||||
var inDoc = false;
|
||||
lines.forEach(function(line, lineNumber){
|
||||
lineNumber++;
|
||||
// is the comment starting?
|
||||
if (!inDoc && (match = line.match(/^\s*\/\*\*\s*(.*)$/))) {
|
||||
line = match[1];
|
||||
inDoc = true;
|
||||
text = [];
|
||||
startingLine = lineNumber;
|
||||
}
|
||||
// are we done?
|
||||
if (inDoc && line.match(/\*\//)) {
|
||||
text = text.join('\n');
|
||||
text = text.replace(/^\n/, '');
|
||||
if (text.match(/@ngdoc/)){
|
||||
callback(text, startingLine);
|
||||
}
|
||||
doc = null;
|
||||
inDoc = false;
|
||||
}
|
||||
// is the comment add text
|
||||
if (inDoc){
|
||||
text.push(line.replace(/^\s*\*\s?/, ''));
|
||||
}
|
||||
});
|
||||
callback.done();
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
exports.collect = collect;
|
||||
@@ -100,6 +100,12 @@ a {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
#main ul.methods h3,
|
||||
#main ul.properties h3 {
|
||||
margin-top: 1.5em;
|
||||
font-family: "Courier New", monospace;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
float: right;
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
SyntaxHighlighter['defaults'].toolbar = false;
|
||||
|
||||
DocsController.$inject = ['$location', '$browser', '$window'];
|
||||
function DocsController($location, $browser, $window) {
|
||||
this.pages = NG_PAGES;
|
||||
@@ -38,10 +36,12 @@ function DocsController($location, $browser, $window) {
|
||||
return "mailto:angular@googlegroups.com?" +
|
||||
"subject=" + escape("Feedback on " + $location.href) + "&" +
|
||||
"body=" + escape("Hi there,\n\nI read " + $location.href + " and wanted to ask ....");
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
angular.filter('short', function(name){
|
||||
return (name||'').split(/\./).pop();
|
||||
});
|
||||
});
|
||||
|
||||
SyntaxHighlighter['defaults'].toolbar = false;
|
||||
@@ -9,31 +9,32 @@
|
||||
|
||||
<link rel="stylesheet" href="doc_widgets.css" type="text/css" />
|
||||
<link rel="stylesheet" href="docs.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shCore.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="syntaxhighlighter/shCore.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="syntaxhighlighter/shThemeDefault.css" type="text/css"/>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
|
||||
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js"></script>
|
||||
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js"></script>
|
||||
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js"></script>
|
||||
<script src="jquery.min.js"></script>
|
||||
<script src="syntaxhighlighter/shCore.js"></script>
|
||||
<script src="syntaxhighlighter/shBrushJScript.js"></script>
|
||||
<script src="syntaxhighlighter/shBrushXml.js"></script>
|
||||
|
||||
<script src="../angular.min.js" ng:autobind></script>
|
||||
<script src="docs.js"></script>
|
||||
<script src="doc_widgets.js"></script>
|
||||
<script src="docs-data.js"></script>
|
||||
<script src="docs-keywords.js"></script>
|
||||
</head>
|
||||
<body style="display:none;" ng:show="true">
|
||||
<div id="header">
|
||||
<h1>
|
||||
<span class="main-title">{{getTitle()}}</span>
|
||||
<a href="#"><span class="angular"><angular/></span> Docs</a>
|
||||
<a href="#" tabindex="0"><span class="angular"><angular/></span> Docs</a>
|
||||
</h1>
|
||||
</div>
|
||||
<div id="sidebar">
|
||||
<input type="text" name="search" id="search-box" placeholder="search the docs"/>
|
||||
<input type="text" name="search" id="search-box" placeholder="search the docs"
|
||||
tabindex="1" accesskey="s"/>
|
||||
<ul id="api-list">
|
||||
<li ng:repeat="page in pages.$filter(search)" ng:class="getClass(page)">
|
||||
<a href="{{getUrl(page)}}" ng:click="">{{page.name | short}}</a>
|
||||
<a href="{{getUrl(page)}}" ng:click="" tabindex="2">{{page.name | short}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../lib/jquery/jquery-1.4.2.min.js
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* SyntaxHighlighter
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter
|
||||
*
|
||||
* SyntaxHighlighter is donationware. If you are using it, please donate.
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
|
||||
*
|
||||
* @version
|
||||
* 3.0.83 (July 02 2010)
|
||||
*
|
||||
* @copyright
|
||||
* Copyright (C) 2004-2010 Alex Gorbatchev.
|
||||
*
|
||||
* @license
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
*/
|
||||
;(function()
|
||||
{
|
||||
// CommonJS
|
||||
typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
|
||||
|
||||
function Brush()
|
||||
{
|
||||
var keywords = 'break case catch continue ' +
|
||||
'default delete do else false ' +
|
||||
'for function if in instanceof ' +
|
||||
'new null return super switch ' +
|
||||
'this throw true try typeof var while with'
|
||||
;
|
||||
|
||||
var r = SyntaxHighlighter.regexLib;
|
||||
|
||||
this.regexList = [
|
||||
{ regex: r.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings
|
||||
{ regex: r.multiLineSingleQuotedString, css: 'string' }, // single quoted strings
|
||||
{ regex: r.singleLineCComments, css: 'comments' }, // one line comments
|
||||
{ regex: r.multiLineCComments, css: 'comments' }, // multiline comments
|
||||
{ regex: /\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion
|
||||
{ regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keywords
|
||||
];
|
||||
|
||||
this.forHtmlScript(r.scriptScriptTags);
|
||||
};
|
||||
|
||||
Brush.prototype = new SyntaxHighlighter.Highlighter();
|
||||
Brush.aliases = ['js', 'jscript', 'javascript'];
|
||||
|
||||
SyntaxHighlighter.brushes.JScript = Brush;
|
||||
|
||||
// CommonJS
|
||||
typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
|
||||
})();
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* SyntaxHighlighter
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter
|
||||
*
|
||||
* SyntaxHighlighter is donationware. If you are using it, please donate.
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
|
||||
*
|
||||
* @version
|
||||
* 3.0.83 (July 02 2010)
|
||||
*
|
||||
* @copyright
|
||||
* Copyright (C) 2004-2010 Alex Gorbatchev.
|
||||
*
|
||||
* @license
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
*/
|
||||
;(function()
|
||||
{
|
||||
// CommonJS
|
||||
typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
|
||||
|
||||
function Brush()
|
||||
{
|
||||
function process(match, regexInfo)
|
||||
{
|
||||
var constructor = SyntaxHighlighter.Match,
|
||||
code = match[0],
|
||||
tag = new XRegExp('(<|<)[\\s\\/\\?]*(?<name>[:\\w-\\.]+)', 'xg').exec(code),
|
||||
result = []
|
||||
;
|
||||
|
||||
if (match.attributes != null)
|
||||
{
|
||||
var attributes,
|
||||
regex = new XRegExp('(?<name> [\\w:\\-\\.]+)' +
|
||||
'\\s*=\\s*' +
|
||||
'(?<value> ".*?"|\'.*?\'|\\w+)',
|
||||
'xg');
|
||||
|
||||
while ((attributes = regex.exec(code)) != null)
|
||||
{
|
||||
result.push(new constructor(attributes.name, match.index + attributes.index, 'color1'));
|
||||
result.push(new constructor(attributes.value, match.index + attributes.index + attributes[0].indexOf(attributes.value), 'string'));
|
||||
}
|
||||
}
|
||||
|
||||
if (tag != null)
|
||||
result.push(
|
||||
new constructor(tag.name, match.index + tag[0].indexOf(tag.name), 'keyword')
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
this.regexList = [
|
||||
{ regex: new XRegExp('(\\<|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\\>|>)', 'gm'), css: 'color2' }, // <![ ... [ ... ]]>
|
||||
{ regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // <!-- ... -->
|
||||
{ regex: new XRegExp('(<|<)[\\s\\/\\?]*(\\w+)(?<attributes>.*?)[\\s\\/\\?]*(>|>)', 'sg'), func: process }
|
||||
];
|
||||
};
|
||||
|
||||
Brush.prototype = new SyntaxHighlighter.Highlighter();
|
||||
Brush.aliases = ['xml', 'xhtml', 'xslt', 'html'];
|
||||
|
||||
SyntaxHighlighter.brushes.Xml = Brush;
|
||||
|
||||
// CommonJS
|
||||
typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
|
||||
})();
|
||||
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* SyntaxHighlighter
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter
|
||||
*
|
||||
* SyntaxHighlighter is donationware. If you are using it, please donate.
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
|
||||
*
|
||||
* @version
|
||||
* 3.0.83 (July 02 2010)
|
||||
*
|
||||
* @copyright
|
||||
* Copyright (C) 2004-2010 Alex Gorbatchev.
|
||||
*
|
||||
* @license
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
*/
|
||||
.syntaxhighlighter a,
|
||||
.syntaxhighlighter div,
|
||||
.syntaxhighlighter code,
|
||||
.syntaxhighlighter table,
|
||||
.syntaxhighlighter table td,
|
||||
.syntaxhighlighter table tr,
|
||||
.syntaxhighlighter table tbody,
|
||||
.syntaxhighlighter table thead,
|
||||
.syntaxhighlighter table caption,
|
||||
.syntaxhighlighter textarea {
|
||||
-moz-border-radius: 0 0 0 0 !important;
|
||||
-webkit-border-radius: 0 0 0 0 !important;
|
||||
background: none !important;
|
||||
border: 0 !important;
|
||||
bottom: auto !important;
|
||||
float: none !important;
|
||||
height: auto !important;
|
||||
left: auto !important;
|
||||
line-height: 1.1em !important;
|
||||
margin: 0 !important;
|
||||
outline: 0 !important;
|
||||
overflow: visible !important;
|
||||
padding: 0 !important;
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
text-align: left !important;
|
||||
top: auto !important;
|
||||
vertical-align: baseline !important;
|
||||
width: auto !important;
|
||||
box-sizing: content-box !important;
|
||||
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important;
|
||||
font-weight: normal !important;
|
||||
font-style: normal !important;
|
||||
font-size: 1em !important;
|
||||
min-height: inherit !important;
|
||||
min-height: auto !important;
|
||||
}
|
||||
|
||||
.syntaxhighlighter {
|
||||
width: 100% !important;
|
||||
margin: 1em 0 1em 0 !important;
|
||||
position: relative !important;
|
||||
overflow: auto !important;
|
||||
font-size: 1em !important;
|
||||
}
|
||||
.syntaxhighlighter.source {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
.syntaxhighlighter .bold {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
.syntaxhighlighter .italic {
|
||||
font-style: italic !important;
|
||||
}
|
||||
.syntaxhighlighter .line {
|
||||
white-space: pre !important;
|
||||
}
|
||||
.syntaxhighlighter table {
|
||||
width: 100% !important;
|
||||
}
|
||||
.syntaxhighlighter table caption {
|
||||
text-align: left !important;
|
||||
padding: .5em 0 0.5em 1em !important;
|
||||
}
|
||||
.syntaxhighlighter table td.code {
|
||||
width: 100% !important;
|
||||
}
|
||||
.syntaxhighlighter table td.code .container {
|
||||
position: relative !important;
|
||||
}
|
||||
.syntaxhighlighter table td.code .container textarea {
|
||||
box-sizing: border-box !important;
|
||||
position: absolute !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
border: none !important;
|
||||
background: white !important;
|
||||
padding-left: 1em !important;
|
||||
overflow: hidden !important;
|
||||
white-space: pre !important;
|
||||
}
|
||||
.syntaxhighlighter table td.gutter .line {
|
||||
text-align: right !important;
|
||||
padding: 0 0.5em 0 1em !important;
|
||||
}
|
||||
.syntaxhighlighter table td.code .line {
|
||||
padding: 0 1em !important;
|
||||
}
|
||||
.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line {
|
||||
padding-left: 0em !important;
|
||||
}
|
||||
.syntaxhighlighter.show {
|
||||
display: block !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed table {
|
||||
display: none !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar {
|
||||
padding: 0.1em 0.8em 0em 0.8em !important;
|
||||
font-size: 1em !important;
|
||||
position: static !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar span {
|
||||
display: inline !important;
|
||||
margin-right: 1em !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar span a {
|
||||
padding: 0 !important;
|
||||
display: none !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar span a.expandSource {
|
||||
display: inline !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar {
|
||||
position: absolute !important;
|
||||
right: 1px !important;
|
||||
top: 1px !important;
|
||||
width: 11px !important;
|
||||
height: 11px !important;
|
||||
font-size: 10px !important;
|
||||
z-index: 10 !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar span.title {
|
||||
display: inline !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar a {
|
||||
display: block !important;
|
||||
text-align: center !important;
|
||||
text-decoration: none !important;
|
||||
padding-top: 1px !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar a.expandSource {
|
||||
display: none !important;
|
||||
}
|
||||
.syntaxhighlighter.ie {
|
||||
font-size: .9em !important;
|
||||
padding: 1px 0 1px 0 !important;
|
||||
}
|
||||
.syntaxhighlighter.ie .toolbar {
|
||||
line-height: 8px !important;
|
||||
}
|
||||
.syntaxhighlighter.ie .toolbar a {
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .line.alt1 .content,
|
||||
.syntaxhighlighter.printing .line.alt2 .content,
|
||||
.syntaxhighlighter.printing .line.highlighted .number,
|
||||
.syntaxhighlighter.printing .line.highlighted.alt1 .content,
|
||||
.syntaxhighlighter.printing .line.highlighted.alt2 .content {
|
||||
background: none !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .line .number {
|
||||
color: #bbbbbb !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .line .content {
|
||||
color: black !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .toolbar {
|
||||
display: none !important;
|
||||
}
|
||||
.syntaxhighlighter.printing a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a {
|
||||
color: black !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a {
|
||||
color: #008200 !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a {
|
||||
color: blue !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .keyword {
|
||||
color: #006699 !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .preprocessor {
|
||||
color: gray !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .variable {
|
||||
color: #aa7700 !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .value {
|
||||
color: #009900 !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .functions {
|
||||
color: #ff1493 !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .constants {
|
||||
color: #0066cc !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .script {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a {
|
||||
color: gray !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a {
|
||||
color: #ff1493 !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a {
|
||||
color: red !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a {
|
||||
color: black !important;
|
||||
}
|
||||
+17
File diff suppressed because one or more lines are too long
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* SyntaxHighlighter
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter
|
||||
*
|
||||
* SyntaxHighlighter is donationware. If you are using it, please donate.
|
||||
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
|
||||
*
|
||||
* @version
|
||||
* 3.0.83 (July 02 2010)
|
||||
*
|
||||
* @copyright
|
||||
* Copyright (C) 2004-2010 Alex Gorbatchev.
|
||||
*
|
||||
* @license
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
*/
|
||||
.syntaxhighlighter {
|
||||
background-color: white !important;
|
||||
}
|
||||
.syntaxhighlighter .line.alt1 {
|
||||
background-color: white !important;
|
||||
}
|
||||
.syntaxhighlighter .line.alt2 {
|
||||
background-color: white !important;
|
||||
}
|
||||
.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 {
|
||||
background-color: #e0e0e0 !important;
|
||||
}
|
||||
.syntaxhighlighter .line.highlighted.number {
|
||||
color: black !important;
|
||||
}
|
||||
.syntaxhighlighter table caption {
|
||||
color: black !important;
|
||||
}
|
||||
.syntaxhighlighter .gutter {
|
||||
color: #afafaf !important;
|
||||
}
|
||||
.syntaxhighlighter .gutter .line {
|
||||
border-right: 3px solid #6ce26c !important;
|
||||
}
|
||||
.syntaxhighlighter .gutter .line.highlighted {
|
||||
background-color: #6ce26c !important;
|
||||
color: white !important;
|
||||
}
|
||||
.syntaxhighlighter.printing .line .content {
|
||||
border: none !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed {
|
||||
overflow: visible !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar {
|
||||
color: blue !important;
|
||||
background: white !important;
|
||||
border: 1px solid #6ce26c !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar a {
|
||||
color: blue !important;
|
||||
}
|
||||
.syntaxhighlighter.collapsed .toolbar a:hover {
|
||||
color: red !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar {
|
||||
color: white !important;
|
||||
background: #6ce26c !important;
|
||||
border: none !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar a {
|
||||
color: white !important;
|
||||
}
|
||||
.syntaxhighlighter .toolbar a:hover {
|
||||
color: black !important;
|
||||
}
|
||||
.syntaxhighlighter .plain, .syntaxhighlighter .plain a {
|
||||
color: black !important;
|
||||
}
|
||||
.syntaxhighlighter .comments, .syntaxhighlighter .comments a {
|
||||
color: #008200 !important;
|
||||
}
|
||||
.syntaxhighlighter .string, .syntaxhighlighter .string a {
|
||||
color: blue !important;
|
||||
}
|
||||
.syntaxhighlighter .keyword {
|
||||
color: #006699 !important;
|
||||
}
|
||||
.syntaxhighlighter .preprocessor {
|
||||
color: gray !important;
|
||||
}
|
||||
.syntaxhighlighter .variable {
|
||||
color: #aa7700 !important;
|
||||
}
|
||||
.syntaxhighlighter .value {
|
||||
color: #009900 !important;
|
||||
}
|
||||
.syntaxhighlighter .functions {
|
||||
color: #ff1493 !important;
|
||||
}
|
||||
.syntaxhighlighter .constants {
|
||||
color: #0066cc !important;
|
||||
}
|
||||
.syntaxhighlighter .script {
|
||||
font-weight: bold !important;
|
||||
color: #006699 !important;
|
||||
background-color: none !important;
|
||||
}
|
||||
.syntaxhighlighter .color1, .syntaxhighlighter .color1 a {
|
||||
color: gray !important;
|
||||
}
|
||||
.syntaxhighlighter .color2, .syntaxhighlighter .color2 a {
|
||||
color: #ff1493 !important;
|
||||
}
|
||||
.syntaxhighlighter .color3, .syntaxhighlighter .color3 a {
|
||||
color: red !important;
|
||||
}
|
||||
|
||||
.syntaxhighlighter .keyword {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
3.0.83
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* All writing related code here. This is so that we can separate the async code from sync code
|
||||
* for testability
|
||||
*/
|
||||
require.paths.push(__dirname);
|
||||
var fs = require('fs');
|
||||
var OUTPUT_DIR = "build/docs/";
|
||||
|
||||
function output(docs, content, callback){
|
||||
callback();
|
||||
}
|
||||
|
||||
exports.output = function(file, content, callback){
|
||||
//console.log('writing', OUTPUT_DIR + file, '...');
|
||||
fs.writeFile(
|
||||
OUTPUT_DIR + file,
|
||||
exports.toString(content),
|
||||
callback);
|
||||
};
|
||||
|
||||
|
||||
exports.toString = function toString(obj){
|
||||
switch (typeof obj) {
|
||||
case 'string':
|
||||
return obj;
|
||||
case 'object':
|
||||
if (obj instanceof Array) {
|
||||
obj.forEach(function (value, key){
|
||||
obj[key] = toString(value);
|
||||
});
|
||||
return obj.join('');
|
||||
} else {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
exports.makeDir = function (path, callback) {
|
||||
var parts = path.split(/\//);
|
||||
path = '.';
|
||||
(function next(){
|
||||
if (parts.length) {
|
||||
path += '/' + parts.shift();
|
||||
fs.mkdir(path, 0777, next);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
exports.copy = function(filename, callback){
|
||||
//console.log('writing', OUTPUT_DIR + filename, '...');
|
||||
fs.readFile('docs/src/templates/' + filename, function(err, content){
|
||||
if (err) return callback.error(err);
|
||||
fs.writeFile(
|
||||
OUTPUT_DIR + filename,
|
||||
content,
|
||||
callback);
|
||||
});
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
<h1>{{name}}</h1>
|
||||
|
||||
{{#workInProgress}}
|
||||
<fieldset class="workInProgress">
|
||||
<legend>Work In Progress</legend>
|
||||
This page is currently being revised. It might be incomplete or contain inaccuracies.
|
||||
{{{workInProgress.description}}}
|
||||
</fieldset>
|
||||
{{/workInProgress}}
|
||||
|
||||
{{#deprecated}}
|
||||
<fieldset class="deprecated">
|
||||
<legend>Deprecated API</legend>
|
||||
{{deprecated}}
|
||||
</fieldset>
|
||||
{{/deprecated}}
|
||||
|
||||
<h2>Description</h2>
|
||||
{{{description}}}
|
||||
|
||||
<h2>Usage</h2>
|
||||
<h3>In HTML Template Binding</h3>
|
||||
<tt>
|
||||
<input type="text" ng:validate="{{shortName}}{{#paramRest}}{{^default}}:{{name}}{{/default}}{{#default}}<i>[:{{name}}]</i>{{/default}}{{/paramRest}}"/>
|
||||
</tt>
|
||||
|
||||
<h3>In JavaScript</h3>
|
||||
<tt ng:non-bindable>
|
||||
angular.validator.{{shortName}}({{paramFirst.name}}{{#paramRest}}{{^default}}, {{name}}{{/default}}{{#default}}<i>[, {{name}}]</i>{{/default}}{{/paramRest}} );
|
||||
</tt>
|
||||
|
||||
<h3>Parameters</h3>
|
||||
<ul>
|
||||
{{#param}}
|
||||
<li><tt>{{name}}</tt> –
|
||||
<tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt>
|
||||
<tt>{{#default}}[{{default}}]{{/default}}</tt>
|
||||
– {{{description}}}</li>
|
||||
{{/param}}
|
||||
</ul>
|
||||
{{{paramDescription}}}
|
||||
|
||||
{{#css}}
|
||||
<h3>CSS</h3>
|
||||
{{{css}}}
|
||||
{{/css}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
@@ -1,68 +0,0 @@
|
||||
<h1>{{name}}</h1>
|
||||
|
||||
{{#workInProgress}}
|
||||
<fieldset class="workInProgress">
|
||||
<legend>Work In Progress</legend>
|
||||
This page is currently being revised. It might be incomplete or contain inaccuracies.
|
||||
{{{workInProgress.description}}}
|
||||
</fieldset>
|
||||
{{/workInProgress}}
|
||||
|
||||
{{#deprecated}}
|
||||
<fieldset class="deprecated">
|
||||
<legend>Deprecated API</legend>
|
||||
{{deprecated}}
|
||||
</fieldset>
|
||||
{{/deprecated}}
|
||||
|
||||
<h2>Description</h2>
|
||||
{{{description}}}
|
||||
|
||||
<h2>Usage</h2>
|
||||
<h3>In HTML Template Binding</h3>
|
||||
<tt>
|
||||
{{^element}}
|
||||
<pre>
|
||||
<{{shortName}}{{#param}} {{#default}}<i>[</i>{{/default}}{{name}}="..."{{#default}}<i>]</i>{{/default}}{{/param}}>{{#usageContent}}
|
||||
|
||||
{{usageContent}}
|
||||
{{/usageContent}}</{{shortName}}>
|
||||
</pre>
|
||||
{{/element}}
|
||||
{{#element}}
|
||||
<pre>
|
||||
<{{element}} {{shortName}}{{#paramFirst}}="{{paramFirst.name}}{{/paramFirst}}">
|
||||
...
|
||||
</{{element}}>
|
||||
</pre>
|
||||
{{/element}}
|
||||
</tt>
|
||||
|
||||
<h3>Parameters</h3>
|
||||
<ul>
|
||||
{{#param}}
|
||||
<li><tt>{{name}}</tt> –
|
||||
<tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt>
|
||||
<tt>{{#default}}[{{default}}]{{/default}}</tt>
|
||||
– {{{description}}}</li>
|
||||
{{/param}}
|
||||
</ul>
|
||||
{{{paramDescription}}}
|
||||
|
||||
{{#css}}
|
||||
<h3>CSS</h3>
|
||||
{{{css}}}
|
||||
{{/css}}
|
||||
|
||||
{{#example}}
|
||||
<h2>Example</h2>
|
||||
{{{exampleDescription}}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
{{/example}}
|
||||
{{{example}}}
|
||||
{{#example}}
|
||||
</doc:source>
|
||||
<doc:scenario>{{{scenario}}}</doc:scenario>
|
||||
</doc:example>
|
||||
{{/example}}
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<!-- TODO: we need to expose $root so that we can delete cookies in the scenario runner, there
|
||||
must be a better way to do this -->
|
||||
<body ng:controller="example.personalLog.LogCtrl" ng:init="$window.$root = $root">
|
||||
<body ng:controller="example.personalLog.LogCtrl">
|
||||
|
||||
<form action="" ng:submit="addLog(newMsg)">
|
||||
<input type="text" name="newMsg" />
|
||||
|
||||
@@ -81,15 +81,14 @@ angular.scenario.dsl('clearCookies', function() {
|
||||
*/
|
||||
return function() {
|
||||
this.addFutureAction('clear all cookies', function($window, $document, done) {
|
||||
//TODO: accessing angular services is pretty nasty, we need a better way to reach them
|
||||
var $cookies = $window.$root.$cookies,
|
||||
var rootScope = $window.angular.element($document[0]).data('$scope'),
|
||||
$cookies = rootScope.$service('$cookies'),
|
||||
cookieName;
|
||||
|
||||
for (cookieName in $cookies) {
|
||||
console.log('deleting cookie: ' + cookieName);
|
||||
delete $cookies[cookieName];
|
||||
}
|
||||
$window.$root.$eval();
|
||||
rootScope.$eval();
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ describe('example.personalLog.LogCtrl', function() {
|
||||
|
||||
function createNotesCtrl() {
|
||||
var scope = angular.scope();
|
||||
scope.$cookies = scope.$service('$cookies');
|
||||
return scope.$new(example.personalLog.LogCtrl);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
node docs/specs.js --noColor && node docs/collect.js
|
||||
#!/bin/bash
|
||||
. ~/.bashrc
|
||||
node docs/spec/specs.js --noColor && node docs/src/gen-docs.js
|
||||
|
||||
@@ -8,6 +8,7 @@ load:
|
||||
- src/Angular.js
|
||||
- src/JSON.js
|
||||
- src/*.js
|
||||
- example/personalLog/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Scenario.js
|
||||
- src/scenario/output/*.js
|
||||
@@ -16,6 +17,7 @@ load:
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/*.js
|
||||
- example/personalLog/test/*.js
|
||||
|
||||
exclude:
|
||||
- src/angular.prefix
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
server: http://localhost:9876
|
||||
|
||||
load:
|
||||
- lib/jasmine-1.0.1/jasmine.js
|
||||
- lib/jasmine-jstd-adapter/JasmineAdapter.js
|
||||
- lib/jquery/jquery-1.4.2.js
|
||||
- test/jquery_remove.js
|
||||
- build/angular.min.js
|
||||
- perf/data/*.js
|
||||
- perf/testUtils.js
|
||||
- perf/*.js
|
||||
|
||||
exclude:
|
||||
@@ -1,111 +1,176 @@
|
||||
/**
|
||||
* @fileoverview Jasmine JsTestDriver Adapter.
|
||||
* @author ibolmo@gmail.com (Olmo Maldonado)
|
||||
* @author misko@hevery.com (Misko Hevery)
|
||||
*/
|
||||
(function(window) {
|
||||
var rootDescribes = new Describes(window);
|
||||
var describePath = [];
|
||||
rootDescribes.collectMode();
|
||||
|
||||
var jasmineTest = TestCase('Jasmine Adapter Tests');
|
||||
|
||||
var jasminePlugin = {
|
||||
name:'jasmine',
|
||||
runTestConfiguration: function(testRunConfiguration, onTestDone, onTestRunConfigurationComplete){
|
||||
if (testRunConfiguration.testCaseInfo_.template_ !== jasmineTest) return;
|
||||
|
||||
var jasmineEnv = jasmine.currentEnv_ = new jasmine.Env();
|
||||
rootDescribes.playback();
|
||||
var specLog = jstestdriver.console.log_ = [];
|
||||
var start;
|
||||
jasmineEnv.specFilter = function(spec) {
|
||||
return rootDescribes.isExclusive(spec);
|
||||
};
|
||||
jasmineEnv.reporter = {
|
||||
log: function(str){
|
||||
specLog.push(str);
|
||||
},
|
||||
|
||||
(function() {
|
||||
reportRunnerStarting: function(runner) { },
|
||||
|
||||
function bind(_this, _function){
|
||||
return function(){
|
||||
return _function.call(_this);
|
||||
};
|
||||
reportSpecStarting: function(spec) {
|
||||
specLog = jstestdriver.console.log_ = [];
|
||||
start = new Date().getTime();
|
||||
},
|
||||
|
||||
reportSpecResults: function(spec) {
|
||||
var suite = spec.suite;
|
||||
var results = spec.results();
|
||||
if (results.skipped) return;
|
||||
var end = new Date().getTime();
|
||||
var messages = [];
|
||||
var resultItems = results.getItems();
|
||||
var state = 'passed';
|
||||
for ( var i = 0; i < resultItems.length; i++) {
|
||||
if (!resultItems[i].passed()) {
|
||||
state = resultItems[i].message.match(/AssertionError:/) ? 'error' : 'failed';
|
||||
messages.push(resultItems[i].toString());
|
||||
messages.push(formatStack(resultItems[i].trace.stack));
|
||||
}
|
||||
}
|
||||
onTestDone(
|
||||
new jstestdriver.TestResult(
|
||||
suite.getFullName(),
|
||||
spec.description,
|
||||
state,
|
||||
messages.join('\n'),
|
||||
specLog.join('\n'),
|
||||
end - start));
|
||||
},
|
||||
|
||||
reportSuiteResults: function(suite) {},
|
||||
|
||||
reportRunnerResults: function(runner) {
|
||||
onTestRunConfigurationComplete();
|
||||
}
|
||||
};
|
||||
jasmineEnv.execute();
|
||||
return true;
|
||||
},
|
||||
onTestsFinish: function(){
|
||||
jasmine.currentEnv_ = null;
|
||||
rootDescribes.collectMode();
|
||||
}
|
||||
};
|
||||
jstestdriver.pluginRegistrar.register(jasminePlugin);
|
||||
|
||||
function formatStack(stack) {
|
||||
var lines = (stack||'').split(/\r?\n/);
|
||||
var frames = [];
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
if (!lines[i].match(/\/jasmine[\.-]/)) {
|
||||
frames.push(lines[i].replace(/https?:\/\/\w+(:\d+)?\/test\//, '').replace(/^\s*/, ' '));
|
||||
}
|
||||
}
|
||||
return frames.join('\n');
|
||||
}
|
||||
|
||||
var currentFrame = frame(null, null);
|
||||
|
||||
function frame(parent, name){
|
||||
var caseName = (parent && parent.caseName ? parent.caseName + " " : '') + (name ? name : '');
|
||||
var frame = {
|
||||
name: name,
|
||||
caseName: caseName,
|
||||
parent: parent,
|
||||
testCase: TestCase(caseName),
|
||||
before: [],
|
||||
after: [],
|
||||
runBefore: function(){
|
||||
if (parent) parent.runBefore.apply(this);
|
||||
for ( var i = 0; i < frame.before.length; i++) {
|
||||
frame.before[i].apply(this);
|
||||
}
|
||||
},
|
||||
runAfter: function(){
|
||||
for ( var i = 0; i < frame.after.length; i++) {
|
||||
frame.after[i].apply(this);
|
||||
}
|
||||
if (parent) parent.runAfter.apply(this);
|
||||
}
|
||||
};
|
||||
return frame;
|
||||
};
|
||||
|
||||
jasmine.Env.prototype.describe = (function(describe){
|
||||
return function(description){
|
||||
currentFrame = frame(currentFrame, description);
|
||||
var val = describe.apply(this, arguments);
|
||||
currentFrame = currentFrame.parent;
|
||||
return val;
|
||||
};
|
||||
|
||||
})(jasmine.Env.prototype.describe);
|
||||
|
||||
var id = 0;
|
||||
|
||||
jasmine.Env.prototype.it = (function(it){
|
||||
return function(desc, itFn){
|
||||
var self = this;
|
||||
var spec = it.apply(this, arguments);
|
||||
var currentSpec = this.currentSpec;
|
||||
if (!currentSpec.$id) {
|
||||
currentSpec.$id = id++;
|
||||
}
|
||||
var frame = this.jstdFrame = currentFrame;
|
||||
var name = 'test that it ' + desc;
|
||||
if (this.jstdFrame.testCase.prototype[name])
|
||||
throw "Spec with name '" + desc + "' already exists.";
|
||||
this.jstdFrame.testCase.prototype[name] = function(){
|
||||
jasmine.getEnv().currentSpec = currentSpec;
|
||||
frame.runBefore.apply(currentSpec);
|
||||
try {
|
||||
itFn.apply(currentSpec);
|
||||
} finally {
|
||||
frame.runAfter.apply(currentSpec);
|
||||
function noop(){}
|
||||
function Describes(window){
|
||||
var describes = {};
|
||||
var beforeEachs = {};
|
||||
var afterEachs = {};
|
||||
var exclusive;
|
||||
var collectMode = true;
|
||||
intercept('describe', describes);
|
||||
intercept('xdescribe', describes);
|
||||
intercept('beforeEach', beforeEachs);
|
||||
intercept('afterEach', afterEachs);
|
||||
|
||||
function intercept(functionName, collection){
|
||||
window[functionName] = function(desc, fn){
|
||||
if (collectMode) {
|
||||
collection[desc] = function(){
|
||||
jasmine.getEnv()[functionName](desc, fn);
|
||||
};
|
||||
} else {
|
||||
jasmine.getEnv()[functionName](desc, fn);
|
||||
}
|
||||
};
|
||||
return spec;
|
||||
}
|
||||
window.ddescribe = function(name, fn){
|
||||
exclusive = true;
|
||||
console.log('ddescribe', name);
|
||||
window.describe(name, function(){
|
||||
var oldIt = window.it;
|
||||
window.it = window.iit;
|
||||
try {
|
||||
fn.call(this);
|
||||
} finally {
|
||||
window.it = oldIt;
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
})(jasmine.Env.prototype.it);
|
||||
|
||||
|
||||
jasmine.Env.prototype.beforeEach = (function(beforeEach){
|
||||
return function(beforeEachFunction) {
|
||||
beforeEach.apply(this, arguments);
|
||||
currentFrame.before.push(beforeEachFunction);
|
||||
window.iit = function(name, fn){
|
||||
exclusive = fn.exclusive = true;
|
||||
console.log(fn);
|
||||
jasmine.getEnv().it(name, fn);
|
||||
};
|
||||
|
||||
})(jasmine.Env.prototype.beforeEach);
|
||||
|
||||
|
||||
jasmine.Env.prototype.afterEach = (function(afterEach){
|
||||
return function(afterEachFunction) {
|
||||
afterEach.apply(this, arguments);
|
||||
currentFrame.after.push(afterEachFunction);
|
||||
|
||||
|
||||
this.collectMode = function() {
|
||||
collectMode = true;
|
||||
exclusive = false;
|
||||
};
|
||||
|
||||
})(jasmine.Env.prototype.afterEach);
|
||||
|
||||
|
||||
jasmine.NestedResults.prototype.addResult = (function(addResult){
|
||||
return function(result) {
|
||||
addResult.call(this, result);
|
||||
if (result.type != 'MessageResult' && !result.passed()) fail(result.message);
|
||||
this.playback = function(){
|
||||
collectMode = false;
|
||||
playback(beforeEachs);
|
||||
playback(afterEachs);
|
||||
playback(describes);
|
||||
|
||||
function playback(set) {
|
||||
for ( var name in set) {
|
||||
set[name]();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.isExclusive = function(spec) {
|
||||
if (exclusive) {
|
||||
var blocks = spec.queue.blocks;
|
||||
for ( var i = 0; i < blocks.length; i++) {
|
||||
if (blocks[i].func.exclusive) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
})(window);
|
||||
|
||||
})(jasmine.NestedResults.prototype.addResult);
|
||||
// Patch Jasmine for proper stack traces
|
||||
jasmine.Spec.prototype.fail = function (e) {
|
||||
var expectationResult = new jasmine.ExpectationResult({
|
||||
passed: false,
|
||||
message: e ? jasmine.util.formatException(e) : 'Exception'
|
||||
});
|
||||
// PATCH
|
||||
if (e) {
|
||||
expectationResult.trace = e;
|
||||
}
|
||||
this.results_.addResult(expectationResult);
|
||||
};
|
||||
|
||||
// Reset environment with overriden methods.
|
||||
jasmine.currentEnv_ = null;
|
||||
jasmine.getEnv();
|
||||
|
||||
})();
|
||||
|
||||
-77
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -1,21 +0,0 @@
|
||||
Copyright (c) 2009 Chris Wanstrath (Ruby)
|
||||
Copyright (c) 2010 Jan Lehnardt (JavaScript)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,344 +0,0 @@
|
||||
/*
|
||||
* CommonJS-compatible mustache.js module
|
||||
*
|
||||
* See http://github.com/janl/mustache.js for more info.
|
||||
*/
|
||||
/*
|
||||
mustache.js Ñ Logic-less templates in JavaScript
|
||||
|
||||
See http://mustache.github.com/ for more info.
|
||||
*/
|
||||
|
||||
var Mustache = function() {
|
||||
var Renderer = function() {};
|
||||
|
||||
Renderer.prototype = {
|
||||
otag: "{{",
|
||||
ctag: "}}",
|
||||
pragmas: {},
|
||||
buffer: [],
|
||||
pragmas_implemented: {
|
||||
"IMPLICIT-ITERATOR": true
|
||||
},
|
||||
context: {},
|
||||
|
||||
render: function(template, context, partials, in_recursion) {
|
||||
// reset buffer & set context
|
||||
if(!in_recursion) {
|
||||
this.context = context;
|
||||
this.buffer = []; // TODO: make this non-lazy
|
||||
}
|
||||
|
||||
// fail fast
|
||||
if(!this.includes("", template)) {
|
||||
if(in_recursion) {
|
||||
return template;
|
||||
} else {
|
||||
this.send(template);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template = this.render_pragmas(template);
|
||||
var html = this.render_section(template, context, partials);
|
||||
if(in_recursion) {
|
||||
return this.render_tags(html, context, partials, in_recursion);
|
||||
}
|
||||
|
||||
this.render_tags(html, context, partials, in_recursion);
|
||||
},
|
||||
|
||||
/*
|
||||
Sends parsed lines
|
||||
*/
|
||||
send: function(line) {
|
||||
if(line != "") {
|
||||
this.buffer.push(line);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
Looks for %PRAGMAS
|
||||
*/
|
||||
render_pragmas: function(template) {
|
||||
// no pragmas
|
||||
if(!this.includes("%", template)) {
|
||||
return template;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
|
||||
this.ctag);
|
||||
return template.replace(regex, function(match, pragma, options) {
|
||||
if(!that.pragmas_implemented[pragma]) {
|
||||
throw({message:
|
||||
"This implementation of mustache doesn't understand the '" +
|
||||
pragma + "' pragma"});
|
||||
}
|
||||
that.pragmas[pragma] = {};
|
||||
if(options) {
|
||||
var opts = options.split("=");
|
||||
that.pragmas[pragma][opts[0]] = opts[1];
|
||||
}
|
||||
return "";
|
||||
// ignore unknown pragmas silently
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
Tries to find a partial in the curent scope and render it
|
||||
*/
|
||||
render_partial: function(name, context, partials) {
|
||||
name = this.trim(name);
|
||||
if(!partials || partials[name] === undefined) {
|
||||
throw({message: "unknown_partial '" + name + "'"});
|
||||
}
|
||||
if(typeof(context[name]) != "object") {
|
||||
return this.render(partials[name], context, partials, true);
|
||||
}
|
||||
return this.render(partials[name], context[name], partials, true);
|
||||
},
|
||||
|
||||
/*
|
||||
Renders inverted (^) and normal (#) sections
|
||||
*/
|
||||
render_section: function(template, context, partials) {
|
||||
if(!this.includes("#", template) && !this.includes("^", template)) {
|
||||
return template;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
// CSW - Added "+?" so it finds the tighest bound, not the widest
|
||||
var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
|
||||
"\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
|
||||
"\\s*", "mg");
|
||||
|
||||
// for each {{#foo}}{{/foo}} section do...
|
||||
return template.replace(regex, function(match, type, name, content) {
|
||||
var value = that.find(name, context);
|
||||
if(type == "^") { // inverted section
|
||||
if(!value || that.is_array(value) && value.length === 0) {
|
||||
// false or empty list, render it
|
||||
return that.render(content, context, partials, true);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
} else if(type == "#") { // normal section
|
||||
if(that.is_array(value)) { // Enumerable, Let's loop!
|
||||
return that.map(value, function(row) {
|
||||
return that.render(content, that.create_context(row),
|
||||
partials, true);
|
||||
}).join("");
|
||||
} else if(that.is_object(value)) { // Object, Use it as subcontext!
|
||||
return that.render(content, that.create_context(value),
|
||||
partials, true);
|
||||
} else if(typeof value === "function") {
|
||||
// higher order section
|
||||
return value.call(context, content, function(text) {
|
||||
return that.render(text, context, partials, true);
|
||||
});
|
||||
} else if(value) { // boolean section
|
||||
return that.render(content, context, partials, true);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
Replace {{foo}} and friends with values from our view
|
||||
*/
|
||||
render_tags: function(template, context, partials, in_recursion) {
|
||||
// tit for tat
|
||||
var that = this;
|
||||
|
||||
var new_regex = function() {
|
||||
return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
|
||||
that.ctag + "+", "g");
|
||||
};
|
||||
|
||||
var regex = new_regex();
|
||||
var tag_replace_callback = function(match, operator, name) {
|
||||
switch(operator) {
|
||||
case "!": // ignore comments
|
||||
return "";
|
||||
case "=": // set new delimiters, rebuild the replace regexp
|
||||
that.set_delimiters(name);
|
||||
regex = new_regex();
|
||||
return "";
|
||||
case ">": // render partial
|
||||
return that.render_partial(name, context, partials);
|
||||
case "{": // the triple mustache is unescaped
|
||||
return that.find(name, context);
|
||||
default: // escape the value
|
||||
return that.escape(that.find(name, context));
|
||||
}
|
||||
};
|
||||
var lines = template.split("\n");
|
||||
for(var i = 0; i < lines.length; i++) {
|
||||
lines[i] = lines[i].replace(regex, tag_replace_callback, this);
|
||||
if(!in_recursion) {
|
||||
this.send(lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if(in_recursion) {
|
||||
return lines.join("\n");
|
||||
}
|
||||
},
|
||||
|
||||
set_delimiters: function(delimiters) {
|
||||
var dels = delimiters.split(" ");
|
||||
this.otag = this.escape_regex(dels[0]);
|
||||
this.ctag = this.escape_regex(dels[1]);
|
||||
},
|
||||
|
||||
escape_regex: function(text) {
|
||||
// thank you Simon Willison
|
||||
if(!arguments.callee.sRE) {
|
||||
var specials = [
|
||||
'/', '.', '*', '+', '?', '|',
|
||||
'(', ')', '[', ']', '{', '}', '\\'
|
||||
];
|
||||
arguments.callee.sRE = new RegExp(
|
||||
'(\\' + specials.join('|\\') + ')', 'g'
|
||||
);
|
||||
}
|
||||
return text.replace(arguments.callee.sRE, '\\$1');
|
||||
},
|
||||
|
||||
/*
|
||||
find `name` in current `context`. That is find me a value
|
||||
from the view object
|
||||
*/
|
||||
find: function(name, context) {
|
||||
name = this.trim(name);
|
||||
|
||||
// Checks whether a value is thruthy or false or 0
|
||||
function is_kinda_truthy(bool) {
|
||||
return bool === false || bool === 0 || bool;
|
||||
}
|
||||
|
||||
var value = context;
|
||||
var path = name.split(/\./);
|
||||
for(var i = 0; i < path.length; i++) {
|
||||
name = path[i];
|
||||
if(value && is_kinda_truthy(value[name])) {
|
||||
value = value[name];
|
||||
} else if(i == 0 && is_kinda_truthy(this.context[name])) {
|
||||
value = this.context[name];
|
||||
} else {
|
||||
value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if(typeof value === "function") {
|
||||
return value.apply(context);
|
||||
}
|
||||
if(value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
// silently ignore unkown variables
|
||||
return "";
|
||||
},
|
||||
|
||||
// Utility methods
|
||||
|
||||
/* includes tag */
|
||||
includes: function(needle, haystack) {
|
||||
return haystack.indexOf(this.otag + needle) != -1;
|
||||
},
|
||||
|
||||
/*
|
||||
Does away with nasty characters
|
||||
*/
|
||||
escape: function(s) {
|
||||
s = String(s === null ? "" : s);
|
||||
return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
|
||||
switch(s) {
|
||||
case "&": return "&";
|
||||
case "\\": return "\\\\";
|
||||
case '"': return '"';
|
||||
case "'": return ''';
|
||||
case "<": return "<";
|
||||
case ">": return ">";
|
||||
default: return s;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// by @langalex, support for arrays of strings
|
||||
create_context: function(_context) {
|
||||
if(this.is_object(_context)) {
|
||||
return _context;
|
||||
} else {
|
||||
var iterator = ".";
|
||||
if(this.pragmas["IMPLICIT-ITERATOR"]) {
|
||||
iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
|
||||
}
|
||||
var ctx = {};
|
||||
ctx[iterator] = _context;
|
||||
return ctx;
|
||||
}
|
||||
},
|
||||
|
||||
is_object: function(a) {
|
||||
return a && typeof a == "object";
|
||||
},
|
||||
|
||||
is_array: function(a) {
|
||||
return Object.prototype.toString.call(a) === '[object Array]';
|
||||
},
|
||||
|
||||
/*
|
||||
Gets rid of leading and trailing whitespace
|
||||
*/
|
||||
trim: function(s) {
|
||||
return s.replace(/^\s*|\s*$/g, "");
|
||||
},
|
||||
|
||||
/*
|
||||
Why, why, why? Because IE. Cry, cry cry.
|
||||
*/
|
||||
map: function(array, fn) {
|
||||
if (typeof array.map == "function") {
|
||||
return array.map(fn);
|
||||
} else {
|
||||
var r = [];
|
||||
var l = array.length;
|
||||
for(var i = 0; i < l; i++) {
|
||||
r.push(fn(array[i]));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return({
|
||||
name: "mustache.js",
|
||||
version: "0.3.1-dev",
|
||||
|
||||
/*
|
||||
Turns a template and view into HTML
|
||||
*/
|
||||
to_html: function(template, view, partials, send_fun) {
|
||||
var renderer = new Renderer();
|
||||
if(send_fun) {
|
||||
renderer.send = send_fun;
|
||||
}
|
||||
renderer.render(template, view, partials);
|
||||
if(!send_fun) {
|
||||
return renderer.buffer.join("\n");
|
||||
}
|
||||
}
|
||||
});
|
||||
}();
|
||||
|
||||
|
||||
exports.name = Mustache.name;
|
||||
exports.version = Mustache.version;
|
||||
|
||||
exports.to_html = function() {
|
||||
return Mustache.to_html.apply(this, arguments);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
|
||||
This test demonstrates the time difference between document's DOMContentLoaded and window's load events.
|
||||
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
startTS = new Date().getTime();
|
||||
onDOMContentLoadedTS = 0; // default for browsers where DOMCL is not supported
|
||||
</script>
|
||||
<title>DOMContentLoaded test</title>
|
||||
<script src="../build/angular.min.js" ng:autobind></script>
|
||||
<script>
|
||||
angular.element(document).bind('DOMContentLoaded', function(e) {onDOMContentLoadedTS = new Date().getTime()});
|
||||
angular.element(window).bind('load', function(e) {
|
||||
onloadTS = new Date().getTime();
|
||||
log.innerHTML = 'start: ' + new Date(startTS) + '<br/>DOMContentLoaded: +' + (onDOMContentLoadedTS - startTS) + 'ms<br/> load: +' + (onloadTS - startTS) + 'ms';
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>DOMContentLoaded test</h1>
|
||||
<p>{{ 'yay!' || 'angular starting...' }}</p>
|
||||
|
||||
<img width="100px" src="http://lh5.ggpht.com/_BLyMhylclm0/TST_bbGH0zI/AAAAAAAAATY/oNUn9kivKN8/s912/1020047.jpg" />
|
||||
<img width="100px" src="http://lh5.ggpht.com/_MqEybfAuUFk/TSOOiegUlPI/AAAAAAAADHY/AEwEWc64_-M/s800/IMG_7294.JPG" />
|
||||
<img width="100px" src="http://lh3.ggpht.com/_LdjD3ua8rpE/TSOW99rwjZI/AAAAAAAAFC0/0qJRhhN45RM/s912/Saison%2010%20%2834%29.JPG" />
|
||||
<img width="100px" src="http://lh6.ggpht.com/_oy_-am3CVUw/TSOQBddZpwI/AAAAAAAACaw/ogFgoD79bVE/s912/P1100886.JPG" />
|
||||
<img width="100px" src="http://lh4.ggpht.com/_srSaA7ZN7oc/TDdxXbA_i1I/AAAAAAAAQ2w/ii3vgrnfCrM/s800/Urlaub10%20157.jpg" />
|
||||
<img width="100px" src="http://lh5.ggpht.com/_y6vXu6iRrfM/SIaYhRQBYNI/AAAAAAAAAmE/lV2NYwxtsQM/s912/North%20Dakota%20Trip%20014.JPG" />
|
||||
<img width="100px" src="http://lh5.ggpht.com/_Jjv9cIn9cS8/RuwZCgfOl6I/AAAAAAAAAOc/QrrMe8vpawg/s800/Shark%20Trip%20-%20day%202%20513.JPG" />
|
||||
|
||||
<p id="log"></p>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,49 @@
|
||||
def generate_object(f, objName, iterations)
|
||||
f.write("var #{objName}='[");
|
||||
|
||||
iterations.times do |i|
|
||||
f.write('{')
|
||||
|
||||
f.write('"simpleStringProperty":') #23
|
||||
f.write('"some string value ' + ('%07d' % i) + '"') #27
|
||||
f.write(',')
|
||||
|
||||
f.write('"stringWithQuotes":') #19
|
||||
f.write('"some string with \\\\"quotes\\\\" ' + ('%07d' % i) + '"') #36
|
||||
f.write(',')
|
||||
|
||||
f.write('"stringWithUnicode":')
|
||||
f.write('"short string with \\u1234 unicode \\u2345 chars ' + ('%07d' % i) + '"')
|
||||
f.write(',')
|
||||
|
||||
f.write('"aNumber":') #10
|
||||
f.write(i) #?
|
||||
f.write(',')
|
||||
|
||||
f.write('"smallArray":')
|
||||
f.write('["a",23,"b",42,' + i.to_s + ']')
|
||||
f.write(',')
|
||||
|
||||
f.write('"smallObj":')
|
||||
f.write('{"foo":"bar","baz":543,"num":' + i.to_s + ',"fuz":"fuz buz huz duz ' + i.to_s + '"}')
|
||||
f.write(',')
|
||||
|
||||
f.write('"timeStamp":')
|
||||
f.write('"2010-12-22T04:58:01.' + ("%03d" % (i%1000)) + '"')
|
||||
|
||||
f.write('},')
|
||||
end
|
||||
|
||||
f.write('"just a padding string"]\';' + "\n\n");
|
||||
end
|
||||
|
||||
file_path = File.join(File.dirname(__FILE__), 'jsonParserPayload.js')
|
||||
|
||||
File.open(file_path, 'w') do |f|
|
||||
generate_object(f, 'superTinyJsonString', 1) #~300b
|
||||
generate_object(f, 'tinyJsonString', 3) #~1kb
|
||||
generate_object(f, 'smallJsonString', 30) #~10kb
|
||||
generate_object(f, 'mediumJsonString', 600) #~200kb
|
||||
generate_object(f, 'largeJsonString', 2000) #~650kb
|
||||
end
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
describe('json', function() {
|
||||
|
||||
it('angular parser', function() {
|
||||
var duration = time(function() {
|
||||
expect(angular.fromJson(largeJsonString)).toBeTruthy();
|
||||
}, 1);
|
||||
|
||||
dump(duration/1 + ' ms per iteration');
|
||||
});
|
||||
|
||||
|
||||
it('angular delegating to native parser', function() {
|
||||
var duration = time(function() {
|
||||
expect(angular.fromJson(largeJsonString, true)).toBeTruthy();
|
||||
}, 100);
|
||||
|
||||
dump(duration/100 + ' ms per iteration');
|
||||
});
|
||||
|
||||
|
||||
it('native json', function() {
|
||||
var duration = time(function() {
|
||||
expect(JSON.parse(largeJsonString)).toBeTruthy();
|
||||
}, 100);
|
||||
|
||||
dump(duration/100 + ' ms per iteration');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<script>
|
||||
function el(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
function update() {
|
||||
el("output").innerHTML = el("input").value;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Your name: <input id="input" type="text" value="World"
|
||||
onkeydown="setTimeout(update,0)"/>
|
||||
<hr/>
|
||||
Hello <span id="output">{{yourname}}</span>!
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
if (window.jstestdriver) {
|
||||
jstd = jstestdriver;
|
||||
dump = angular.bind(jstd.console, jstd.console.log);
|
||||
}
|
||||
|
||||
function time(fn, times) {
|
||||
times = times || 1;
|
||||
|
||||
var i,
|
||||
start,
|
||||
duration = 0;
|
||||
|
||||
for (i=0; i<times; i++) {
|
||||
start = Date.now();
|
||||
fn();
|
||||
duration += Date.now() - start;
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876 --browserTimeout 20000
|
||||
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876 --browserTimeout 90000
|
||||
|
||||
+34
-26
@@ -108,13 +108,14 @@ var _undefined = undefined,
|
||||
/** @name angular.service */
|
||||
angularService = extensionMap(angular, 'service'),
|
||||
angularCallbacks = extensionMap(angular, 'callbacks'),
|
||||
nodeName,
|
||||
rngScript = /^(|.*\/)angular(-.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/;
|
||||
nodeName_,
|
||||
rngScript = /^(|.*\/)angular(-.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/,
|
||||
DATE_ISOSTRING_LN = 24;
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.foreach
|
||||
* @name angular.forEach
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
@@ -122,11 +123,13 @@ var _undefined = undefined,
|
||||
* be an object or an array. The `iterator` function is invoked with `iterator(value, key)`, where
|
||||
* `value` is the value of an object property or an array element and `key` is the object property
|
||||
* key or array element index. Optionally, `context` can be specified for the iterator function.
|
||||
*
|
||||
* Note: this function was previously known as `angular.foreach`.
|
||||
*
|
||||
<pre>
|
||||
var values = {name: 'misko', gender: 'male'};
|
||||
var log = [];
|
||||
angular.foreach(values, function(value, key){
|
||||
angular.forEach(values, function(value, key){
|
||||
this.push(key + ': ' + value);
|
||||
}, log);
|
||||
expect(log).toEqual(['name: misko', 'gender:male']);
|
||||
@@ -137,7 +140,7 @@ var _undefined = undefined,
|
||||
* @param {Object} context Object to become context (`this`) for the iterator function.
|
||||
* @returns {Objet|Array} Reference to `obj`.
|
||||
*/
|
||||
function foreach(obj, iterator, context) {
|
||||
function forEach(obj, iterator, context) {
|
||||
var key;
|
||||
if (obj) {
|
||||
if (isFunction(obj)){
|
||||
@@ -146,7 +149,7 @@ function foreach(obj, iterator, context) {
|
||||
iterator.call(context, obj[key], key);
|
||||
}
|
||||
}
|
||||
} else if (obj.forEach) {
|
||||
} else if (obj.forEach && obj.forEach !== forEach) {
|
||||
obj.forEach(iterator, context);
|
||||
} else if (isObject(obj) && isNumber(obj.length)) {
|
||||
for (key = 0; key < obj.length; key++)
|
||||
@@ -159,7 +162,7 @@ function foreach(obj, iterator, context) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
function foreachSorted(obj, iterator, context) {
|
||||
function forEachSorted(obj, iterator, context) {
|
||||
var keys = [];
|
||||
for (var key in obj) keys.push(key);
|
||||
keys.sort();
|
||||
@@ -196,9 +199,9 @@ function formatError(arg) {
|
||||
* @param {...Object} src The source object(s).
|
||||
*/
|
||||
function extend(dst) {
|
||||
foreach(arguments, function(obj){
|
||||
forEach(arguments, function(obj){
|
||||
if (obj !== dst) {
|
||||
foreach(obj, function(value, key){
|
||||
forEach(obj, function(value, key){
|
||||
dst[key] = value;
|
||||
});
|
||||
}
|
||||
@@ -252,18 +255,11 @@ function identity($) {return $;}
|
||||
|
||||
function valueFn(value) {return function(){ return value; };}
|
||||
|
||||
|
||||
function extensionMap(angular, name, transform) {
|
||||
var extPoint;
|
||||
return angular[name] || (extPoint = angular[name] = function (name, fn, prop){
|
||||
name = (transform || identity)(name);
|
||||
if (isDefined(fn)) {
|
||||
if (isDefined(extPoint[name])) {
|
||||
foreach(extPoint[name], function(property, key) {
|
||||
if (key.charAt(0) == '$' && isUndefined(fn[key]))
|
||||
fn[key] = property;
|
||||
});
|
||||
}
|
||||
extPoint[name] = extend(fn, prop || {});
|
||||
}
|
||||
return extPoint[name];
|
||||
@@ -277,7 +273,7 @@ function jqLiteWrap(element) {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = element;
|
||||
element = new JQLite(div.childNodes);
|
||||
} else if (!(element instanceof JQLite) && isElement(element)) {
|
||||
} else if (!(element instanceof JQLite)) {
|
||||
element = new JQLite(element);
|
||||
}
|
||||
}
|
||||
@@ -406,8 +402,19 @@ function isArray(value) { return value instanceof Array; }
|
||||
function isFunction(value){ return typeof value == $function;}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if `obj` is a window object.
|
||||
*
|
||||
* @private
|
||||
* @param {*} obj Object to check
|
||||
* @returns {boolean} True if `obj` is a window obj.
|
||||
*/
|
||||
function isWindow(obj) {
|
||||
return obj && obj.document && obj.location && obj.alert && obj.setInterval;
|
||||
}
|
||||
|
||||
function isBoolean(value) { return typeof value == $boolean;}
|
||||
function isTextNode(node) { return nodeName(node) == '#text'; }
|
||||
function isTextNode(node) { return nodeName_(node) == '#text'; }
|
||||
function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }
|
||||
function isElement(node) {
|
||||
return node && (node.nodeName || node instanceof JQLite || (jQuery && node instanceof jQuery));
|
||||
@@ -431,12 +438,12 @@ function HTML(html, option) {
|
||||
}
|
||||
|
||||
if (msie) {
|
||||
nodeName = function(element) {
|
||||
nodeName_ = function(element) {
|
||||
element = element.nodeName ? element : element[0];
|
||||
return (element.scopeName && element.scopeName != 'HTML' ) ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName;
|
||||
};
|
||||
} else {
|
||||
nodeName = function(element) {
|
||||
nodeName_ = function(element) {
|
||||
return element.nodeName ? element.nodeName : element[0].nodeName;
|
||||
};
|
||||
}
|
||||
@@ -454,7 +461,7 @@ function isVisible(element) {
|
||||
|
||||
function map(obj, iterator, context) {
|
||||
var results = [];
|
||||
foreach(obj, function(value, index, list) {
|
||||
forEach(obj, function(value, index, list) {
|
||||
results.push(iterator.call(context, value, index, list));
|
||||
});
|
||||
return results;
|
||||
@@ -575,7 +582,7 @@ function copy(source, destination){
|
||||
destination.push(copy(source[i]));
|
||||
}
|
||||
} else {
|
||||
foreach(destination, function(value, key){
|
||||
forEach(destination, function(value, key){
|
||||
delete destination[key];
|
||||
});
|
||||
for ( var key in source) {
|
||||
@@ -624,6 +631,7 @@ function copy(source, destination){
|
||||
*/
|
||||
function equals(o1, o2) {
|
||||
if (o1 == o2) return true;
|
||||
if (o1 === null || o2 === null) return false;
|
||||
var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
|
||||
if (t1 == t2 && t1 == 'object') {
|
||||
if (o1 instanceof Array) {
|
||||
@@ -704,7 +712,7 @@ function concat(array1, array2, index) {
|
||||
*/
|
||||
function bind(self, fn) {
|
||||
var curryArgs = arguments.length > 2 ? slice.call(arguments, 2, arguments.length) : [];
|
||||
if (typeof fn == $function) {
|
||||
if (typeof fn == $function && !(fn instanceof RegExp)) {
|
||||
return curryArgs.length ? function() {
|
||||
return arguments.length ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0, arguments.length))) : fn.apply(self, curryArgs);
|
||||
}: function() {
|
||||
@@ -773,7 +781,7 @@ function compile(element, parentScope) {
|
||||
*/
|
||||
function parseKeyValue(/**string*/keyValue) {
|
||||
var obj = {}, key_value, key;
|
||||
foreach((keyValue || "").split('&'), function(keyValue){
|
||||
forEach((keyValue || "").split('&'), function(keyValue){
|
||||
if (keyValue) {
|
||||
key_value = keyValue.split('=');
|
||||
key = unescape(key_value[0]);
|
||||
@@ -785,7 +793,7 @@ function parseKeyValue(/**string*/keyValue) {
|
||||
|
||||
function toKeyValue(obj) {
|
||||
var parts = [];
|
||||
foreach(obj, function(value, key) {
|
||||
forEach(obj, function(value, key) {
|
||||
parts.push(escape(key) + (value === true ? '' : '=' + escape(value)));
|
||||
});
|
||||
return parts.length ? parts.join('&') : '';
|
||||
@@ -946,7 +954,7 @@ function angularInit(config){
|
||||
if (config.autobind) {
|
||||
// TODO default to the source of angular.js
|
||||
var scope = compile(window.document, _null, {'$config':config}),
|
||||
$browser = scope.$inject('$browser');
|
||||
$browser = scope.$service('$browser');
|
||||
|
||||
if (config.css)
|
||||
$browser.addCss(config.base_url + config.css);
|
||||
|
||||
+10
-10
@@ -10,18 +10,18 @@ var browserSingleton;
|
||||
*/
|
||||
angularService('$browser', function($log){
|
||||
if (!browserSingleton) {
|
||||
browserSingleton = new Browser(
|
||||
window.location,
|
||||
jqLite(window.document),
|
||||
jqLite(window.document.getElementsByTagName('head')[0]),
|
||||
XHR,
|
||||
$log,
|
||||
window.setTimeout);
|
||||
browserSingleton.startPoller(50, function(delay, fn){setTimeout(delay,fn);});
|
||||
browserSingleton = new Browser(window, jqLite(window.document), jqLite(window.document.body),
|
||||
XHR, $log);
|
||||
var addPollFn = browserSingleton.addPollFn;
|
||||
browserSingleton.addPollFn = function(){
|
||||
browserSingleton.addPollFn = addPollFn;
|
||||
browserSingleton.startPoller(100, function(delay, fn){setTimeout(delay,fn);});
|
||||
return addPollFn.apply(browserSingleton, arguments);
|
||||
};
|
||||
browserSingleton.bind();
|
||||
}
|
||||
return browserSingleton;
|
||||
}, {inject:['$log']});
|
||||
}, {$inject:['$log']});
|
||||
|
||||
extend(angular, {
|
||||
'element': jqLite,
|
||||
@@ -30,7 +30,7 @@ extend(angular, {
|
||||
'copy': copy,
|
||||
'extend': extend,
|
||||
'equals': equals,
|
||||
'foreach': foreach,
|
||||
'forEach': forEach,
|
||||
'injector': createInjector,
|
||||
'noop':noop,
|
||||
'bind':bind,
|
||||
|
||||
+68
-11
@@ -8,8 +8,29 @@ var XHR = window.XMLHttpRequest || function () {
|
||||
throw new Error("This browser does not support XMLHttpRequest.");
|
||||
};
|
||||
|
||||
function Browser(location, document, head, XHR, $log, setTimeout) {
|
||||
var self = this;
|
||||
/**
|
||||
* @private
|
||||
* @name Browser
|
||||
*
|
||||
* @description
|
||||
* Constructor for the object exposed as $browser service.
|
||||
*
|
||||
* This object has two goals:
|
||||
*
|
||||
* - hide all the global state in the browser caused by the window object
|
||||
* - abstract away all the browser specific features and inconsistencies
|
||||
*
|
||||
* @param {object} window The global window object.
|
||||
* @param {object} document jQuery wrapped document.
|
||||
* @param {object} body jQuery wrapped document.body.
|
||||
* @param {function()} XHR XMLHttpRequest constructor.
|
||||
* @param {object} $log console.log or an object with the same interface.
|
||||
*/
|
||||
function Browser(window, document, body, XHR, $log) {
|
||||
var self = this,
|
||||
location = window.location,
|
||||
setTimeout = window.setTimeout;
|
||||
|
||||
self.isMock = false;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
@@ -70,7 +91,7 @@ function Browser(location, document, head, XHR, $log, setTimeout) {
|
||||
window[callbackId] = _undefined;
|
||||
callback(200, data);
|
||||
};
|
||||
head.append(script);
|
||||
body.append(script);
|
||||
} else {
|
||||
var xhr = new XHR();
|
||||
xhr.open(method, url, true);
|
||||
@@ -115,7 +136,7 @@ function Browser(location, document, head, XHR, $log, setTimeout) {
|
||||
* @methodOf angular.service.$browser
|
||||
*/
|
||||
self.poll = function() {
|
||||
foreach(pollFns, function(pollFn){ pollFn(); });
|
||||
forEach(pollFns, function(pollFn){ pollFn(); });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -193,6 +214,41 @@ function Browser(location, document, head, XHR, $log, setTimeout) {
|
||||
return location.href;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#onHashChange
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @description
|
||||
* Detects if browser support onhashchange events and register a listener otherwise registers
|
||||
* $browser poller. The `listener` will then get called when the hash changes.
|
||||
*
|
||||
* The listener gets called with either HashChangeEvent object or simple object that also contains
|
||||
* `oldURL` and `newURL` properties.
|
||||
*
|
||||
* NOTE: this is a api is intended for sole use by $location service. Please use
|
||||
* {@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.
|
||||
* @return {function()} Returns the registered listener fn - handy if the fn is anonymous.
|
||||
*/
|
||||
self.onHashChange = function(listener) {
|
||||
if ('onhashchange' in window) {
|
||||
jqLite(window).bind('hashchange', listener);
|
||||
} else {
|
||||
var lastBrowserUrl = self.getUrl();
|
||||
|
||||
self.addPollFn(function() {
|
||||
if (lastBrowserUrl != self.getUrl()) {
|
||||
listener();
|
||||
}
|
||||
});
|
||||
}
|
||||
return listener;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Cookies API
|
||||
//////////////////////////////////////////////////////////////
|
||||
@@ -263,22 +319,23 @@ function Browser(location, document, head, XHR, $log, setTimeout) {
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#defer
|
||||
* @methodOf angular.service.$browser
|
||||
* @param {function()} fn A function, who's execution should be defered.
|
||||
* @param {int=} [delay=0] of milliseconds to defer the function execution.
|
||||
*
|
||||
* @description
|
||||
* Executes a fn asynchroniously via `setTimeout(fn, 0)`.
|
||||
* Executes a fn asynchroniously via `setTimeout(fn, delay)`.
|
||||
*
|
||||
* Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
|
||||
* `setTimeout` in tests, the fns are queued in an array, which can be programaticaly flushed via
|
||||
* `$browser.defer.flush()`.
|
||||
*
|
||||
* @param {function()} fn A function, who's execution should be defered.
|
||||
*/
|
||||
self.defer = function(fn) {
|
||||
self.defer = function(fn, delay) {
|
||||
outstandingRequestCount++;
|
||||
setTimeout(function() { completeOutstandingRequest(fn); }, 0);
|
||||
setTimeout(function() { completeOutstandingRequest(fn); }, delay || 0);
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
@@ -336,7 +393,7 @@ function Browser(location, document, head, XHR, $log, setTimeout) {
|
||||
link.attr('rel', 'stylesheet');
|
||||
link.attr('type', 'text/css');
|
||||
link.attr('href', url);
|
||||
head.append(link);
|
||||
body.append(link);
|
||||
};
|
||||
|
||||
|
||||
@@ -357,6 +414,6 @@ function Browser(location, document, head, XHR, $log, setTimeout) {
|
||||
script.attr('type', 'text/javascript');
|
||||
script.attr('src', url);
|
||||
if (dom_id) script.attr('id', dom_id);
|
||||
head.append(script);
|
||||
body.append(script);
|
||||
};
|
||||
}
|
||||
|
||||
+9
-8
@@ -16,8 +16,8 @@ Template.prototype = {
|
||||
init: function(element, scope) {
|
||||
var inits = {};
|
||||
this.collectInits(element, inits, scope);
|
||||
foreachSorted(inits, function(queue){
|
||||
foreach(queue, function(fn) {fn();});
|
||||
forEachSorted(inits, function(queue){
|
||||
forEach(queue, function(fn) {fn();});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -32,10 +32,10 @@ Template.prototype = {
|
||||
scope.$onEval(childScope.$eval);
|
||||
element.data($$scope, childScope);
|
||||
}
|
||||
foreach(this.inits, function(fn) {
|
||||
forEach(this.inits, function(fn) {
|
||||
queue.push(function() {
|
||||
childScope.$tryEval(function(){
|
||||
return childScope.$inject(fn, childScope, element);
|
||||
return childScope.$service(fn, childScope, element);
|
||||
}, element);
|
||||
});
|
||||
});
|
||||
@@ -74,6 +74,7 @@ Template.prototype = {
|
||||
*/
|
||||
function retrieveScope(element) {
|
||||
var scope;
|
||||
element = jqLite(element);
|
||||
while (element && !(scope = element.data($$scope))) {
|
||||
element = element.parent();
|
||||
}
|
||||
@@ -181,7 +182,7 @@ Compiler.prototype = {
|
||||
directiveFns = self.directives,
|
||||
descend = true,
|
||||
directives = true,
|
||||
elementName = nodeName(element),
|
||||
elementName = nodeName_(element),
|
||||
template,
|
||||
selfApi = {
|
||||
compile: bind(self, self.compile),
|
||||
@@ -231,7 +232,7 @@ Compiler.prototype = {
|
||||
for(var i=0, child=element[0].childNodes;
|
||||
i<child.length; i++) {
|
||||
if (isTextNode(child[i])) {
|
||||
foreach(self.markup, function(markup){
|
||||
forEach(self.markup, function(markup){
|
||||
if (i<child.length) {
|
||||
var textNode = jqLite(child[i]);
|
||||
markup.call(selfApi, textNode.text(), textNode, element);
|
||||
@@ -244,7 +245,7 @@ Compiler.prototype = {
|
||||
if (directives) {
|
||||
// Process attributes/directives
|
||||
eachAttribute(element, function(value, name){
|
||||
foreach(self.attrMarkup, function(markup){
|
||||
forEach(self.attrMarkup, function(markup){
|
||||
markup.call(selfApi, value, name, element);
|
||||
});
|
||||
});
|
||||
@@ -286,6 +287,6 @@ function eachAttribute(element, fn){
|
||||
}
|
||||
attrValue[name] = value;
|
||||
}
|
||||
foreachSorted(attrValue, fn);
|
||||
forEachSorted(attrValue, fn);
|
||||
}
|
||||
|
||||
|
||||
+17
-10
@@ -37,7 +37,7 @@ function createInjector(providerScope, providers, cache) {
|
||||
* none: same as object but use providerScope as place to publish.
|
||||
*/
|
||||
return function inject(value, scope, args){
|
||||
var returnValue, provider, creation;
|
||||
var returnValue, provider;
|
||||
if (isString(value)) {
|
||||
if (!cache.hasOwnProperty(value)) {
|
||||
provider = providers[value];
|
||||
@@ -47,25 +47,32 @@ function createInjector(providerScope, providers, cache) {
|
||||
returnValue = cache[value];
|
||||
} else if (isArray(value)) {
|
||||
returnValue = [];
|
||||
foreach(value, function(name) {
|
||||
forEach(value, function(name) {
|
||||
returnValue.push(inject(name));
|
||||
});
|
||||
} else if (isFunction(value)) {
|
||||
returnValue = inject(value.$inject || []);
|
||||
returnValue = value.apply(scope, concat(returnValue, arguments, 2));
|
||||
} else if (isObject(value)) {
|
||||
foreach(providers, function(provider, name){
|
||||
creation = provider.$creation;
|
||||
if (creation == 'eager') {
|
||||
forEach(providers, function(provider, name){
|
||||
if (provider.$eager)
|
||||
inject(name);
|
||||
}
|
||||
if (creation == 'eager-published') {
|
||||
setter(value, name, inject(name));
|
||||
}
|
||||
|
||||
if (provider.$creation)
|
||||
throw new Error("Failed to register service '" + name +
|
||||
"': $creation property is unsupported. Use $eager:true or see release notes.");
|
||||
});
|
||||
} else {
|
||||
returnValue = inject(providerScope);
|
||||
}
|
||||
return returnValue;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function injectService(services, fn) {
|
||||
return extend(fn, {$inject:services});;
|
||||
}
|
||||
|
||||
function injectUpdateView(fn) {
|
||||
return injectService(['$updateView'], fn);
|
||||
}
|
||||
|
||||
+25
-3
@@ -29,19 +29,41 @@ function toJson(obj, pretty) {
|
||||
* Deserializes a string in the JSON format.
|
||||
*
|
||||
* @param {string} json JSON string to deserialize.
|
||||
* @param {boolean} [useNative=false] Use native JSON parser if available
|
||||
* @returns {Object|Array|Date|string|number} Deserialized thingy.
|
||||
*/
|
||||
function fromJson(json) {
|
||||
function fromJson(json, useNative) {
|
||||
if (!json) return json;
|
||||
|
||||
var obj, p, expression;
|
||||
|
||||
try {
|
||||
var p = parser(json, true);
|
||||
var expression = p.primary();
|
||||
if (useNative && JSON && JSON.parse) {
|
||||
obj = JSON.parse(json);
|
||||
return transformDates(obj);
|
||||
}
|
||||
|
||||
p = parser(json, true);
|
||||
expression = p.primary();
|
||||
p.assertAllConsumed();
|
||||
return expression();
|
||||
|
||||
} catch (e) {
|
||||
error("fromJson error: ", json, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// TODO make forEach optionally recursive and remove this function
|
||||
function transformDates(obj) {
|
||||
if (isString(obj) && obj.length === DATE_ISOSTRING_LN) {
|
||||
return angularString.toDate(obj);
|
||||
} else if (isArray(obj) || isObject(obj)) {
|
||||
forEach(obj, function(val, name) {
|
||||
obj[name] = transformDates(val);
|
||||
});
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
angular['toJson'] = toJson;
|
||||
|
||||
+7
-6
@@ -4,7 +4,7 @@ function Route(template, defaults) {
|
||||
this.template = template = template + '#';
|
||||
this.defaults = defaults || {};
|
||||
var urlParams = this.urlParams = {};
|
||||
foreach(template.split(/\W/), function(param){
|
||||
forEach(template.split(/\W/), function(param){
|
||||
if (param && template.match(new RegExp(":" + param + "\\W"))) {
|
||||
urlParams[param] = true;
|
||||
}
|
||||
@@ -17,13 +17,13 @@ Route.prototype = {
|
||||
var self = this;
|
||||
var url = this.template;
|
||||
params = params || {};
|
||||
foreach(this.urlParams, function(_, urlParam){
|
||||
forEach(this.urlParams, function(_, urlParam){
|
||||
var value = params[urlParam] || self.defaults[urlParam] || "";
|
||||
url = url.replace(new RegExp(":" + urlParam + "(\\W)"), value + "$1");
|
||||
});
|
||||
url = url.replace(/\/?#$/, '');
|
||||
var query = [];
|
||||
foreachSorted(params, function(value, key){
|
||||
forEachSorted(params, function(value, key){
|
||||
if (!self.urlParams[key]) {
|
||||
query.push(encodeURI(key) + '=' + encodeURI(value));
|
||||
}
|
||||
@@ -52,7 +52,7 @@ ResourceFactory.prototype = {
|
||||
actions = extend({}, ResourceFactory.DEFAULT_ACTIONS, actions);
|
||||
function extractParams(data){
|
||||
var ids = {};
|
||||
foreach(paramDefaults || {}, function(value, key){
|
||||
forEach(paramDefaults || {}, function(value, key){
|
||||
ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
|
||||
});
|
||||
return ids;
|
||||
@@ -62,7 +62,7 @@ ResourceFactory.prototype = {
|
||||
copy(value || {}, this);
|
||||
}
|
||||
|
||||
foreach(actions, function(action, name){
|
||||
forEach(actions, function(action, name){
|
||||
var isPostOrPut = action.method == 'POST' || action.method == 'PUT';
|
||||
Resource[name] = function (a1, a2, a3) {
|
||||
var params = {};
|
||||
@@ -73,6 +73,7 @@ ResourceFactory.prototype = {
|
||||
case 2:
|
||||
if (isFunction(a2)) {
|
||||
callback = a2;
|
||||
//fallthrough
|
||||
} else {
|
||||
params = a1;
|
||||
data = a2;
|
||||
@@ -97,7 +98,7 @@ ResourceFactory.prototype = {
|
||||
if (status == 200) {
|
||||
if (action.isArray) {
|
||||
value.length = 0;
|
||||
foreach(response, function(item){
|
||||
forEach(response, function(item){
|
||||
value.push(new Resource(item));
|
||||
});
|
||||
} else {
|
||||
|
||||
+21
-5
@@ -48,7 +48,7 @@ var scopeId = 0,
|
||||
getterFnCache = {},
|
||||
compileCache = {},
|
||||
JS_KEYWORDS = {};
|
||||
foreach(
|
||||
forEach(
|
||||
("abstract,boolean,break,byte,case,catch,char,class,const,continue,debugger,default," +
|
||||
"delete,do,double,else,enum,export,extends,false,final,finally,float,for,function,goto," +
|
||||
"if,implements,import,ininstanceof,intinterface,long,native,new,null,package,private," +
|
||||
@@ -61,7 +61,7 @@ function getterFn(path){
|
||||
if (fn) return fn;
|
||||
|
||||
var code = 'var l, fn, t;\n';
|
||||
foreach(path.split('.'), function(key) {
|
||||
forEach(path.split('.'), function(key) {
|
||||
key = (JS_KEYWORDS[key]) ? '["' + key + '"]' : '.' + key;
|
||||
code += 'if(!s) return s;\n' +
|
||||
'l=s;\n' +
|
||||
@@ -575,10 +575,10 @@ function createScope(parent, providers, instanceCache) {
|
||||
$become: function(Class) {
|
||||
if (isFunction(Class)) {
|
||||
instance.constructor = Class;
|
||||
foreach(Class.prototype, function(fn, name){
|
||||
forEach(Class.prototype, function(fn, name){
|
||||
instance[name] = bind(instance, fn);
|
||||
});
|
||||
instance.$inject.apply(instance, concat([Class, instance], arguments, 1));
|
||||
instance.$service.apply(instance, concat([Class, instance], arguments, 1));
|
||||
|
||||
//TODO: backwards compatibility hack, remove when we don't depend on init methods
|
||||
if (isFunction(Class.prototype.init)) {
|
||||
@@ -615,7 +615,23 @@ function createScope(parent, providers, instanceCache) {
|
||||
if (!parent.$root) {
|
||||
instance.$root = instance;
|
||||
instance.$parent = instance;
|
||||
(instance.$inject = createInjector(instance, providers, instanceCache))();
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.scope.$service
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Provides access to angular's dependency injector and
|
||||
* {@link angular.service registered services}. In general the use of this api is discouraged,
|
||||
* except for tests and components that currently don't support dependency injection (widgets,
|
||||
* filters, etc).
|
||||
*
|
||||
* @param {string} serviceId String ID of the service to return.
|
||||
* @returns {*} Value, object or function returned by the service factory function if any.
|
||||
*/
|
||||
(instance.$service = createInjector(instance, providers, instanceCache))();
|
||||
}
|
||||
|
||||
return instance;
|
||||
|
||||
Vendored
+95
-33
@@ -21,11 +21,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
(function(previousOnLoad){
|
||||
var filename = /^(.*)\/angular-bootstrap.js(#.*)?$/,
|
||||
(function(window) {
|
||||
|
||||
var filename = /^(.*\/)angular-bootstrap.js(#.*)?$/,
|
||||
scripts = document.getElementsByTagName("SCRIPT"),
|
||||
serverPath,
|
||||
match;
|
||||
match,
|
||||
globalVars = {};
|
||||
|
||||
for(var j = 0; j < scripts.length; j++) {
|
||||
match = (scripts[j].src || "").match(filename);
|
||||
if (match) {
|
||||
@@ -33,50 +36,109 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addScript(file){
|
||||
document.write('<script type="text/javascript" src="' + serverPath + file +'"></script>');
|
||||
function key(prop) {
|
||||
return "ng-clobber_" + prop;
|
||||
}
|
||||
|
||||
window.angularClobberTest = function(file) {
|
||||
var varKey, prop,
|
||||
clobbered = [];
|
||||
|
||||
for (prop in window) {
|
||||
varKey = key(prop);
|
||||
|
||||
if (prop === 'event') { //skip special variables which keep on changing
|
||||
continue;
|
||||
}
|
||||
else if (!globalVars.hasOwnProperty(varKey)) {
|
||||
//console.log('new global variable found: ', prop);
|
||||
globalVars[varKey] = window[prop];
|
||||
} else if (globalVars[varKey] !== window[prop] && !isActuallyNaN(window[prop])) {
|
||||
clobbered.push(prop);
|
||||
console.error("Global variable clobbered by script " + file + "! Variable name: " + prop);
|
||||
globalVars[varKey] = window[prop];
|
||||
}
|
||||
}
|
||||
|
||||
for (varKey in globalVars) {
|
||||
prop = varKey.substr(11);
|
||||
if (clobbered.indexOf(prop) == -1 &&
|
||||
prop != 'event' &&
|
||||
!isActuallyNaN(globalVars[varKey]) &&
|
||||
globalVars[varKey] !== window[prop]) {
|
||||
|
||||
delete globalVars[varKey];
|
||||
console.warn("Global variable unexpectedly deleted in script " + file + "! " +
|
||||
"Variable name: " + prop);
|
||||
}
|
||||
}
|
||||
|
||||
function isActuallyNaN(val) {
|
||||
return isNaN(val) && (typeof val === 'number');
|
||||
}
|
||||
}
|
||||
|
||||
function addScripts(){
|
||||
var prop, i;
|
||||
|
||||
// initialize the window property cache
|
||||
for (prop in window) {
|
||||
globalVars[key(prop)] = window[prop];
|
||||
}
|
||||
|
||||
// load the js scripts
|
||||
for (i in arguments) {
|
||||
file = arguments[i];
|
||||
document.write('<script type="text/javascript" src="' + serverPath + file + '" ' +
|
||||
'onload="angularClobberTest(\'' + file + '\')"></script>');
|
||||
}
|
||||
}
|
||||
|
||||
function addCss(file) {
|
||||
document.write('<link rel="stylesheet" type="text/css" href="' +
|
||||
serverPath + '/../css' + file + '"/>');
|
||||
serverPath + '../css/' + file + '"/>');
|
||||
}
|
||||
|
||||
addCss("/angular.css");
|
||||
addCss('angular.css');
|
||||
|
||||
addScript("/Angular.js");
|
||||
addScript("/JSON.js");
|
||||
addScript("/Compiler.js");
|
||||
addScript("/Scope.js");
|
||||
addScript("/Injector.js");
|
||||
addScript("/jqLite.js");
|
||||
addScript("/parser.js");
|
||||
addScript("/Resource.js");
|
||||
addScript("/Browser.js");
|
||||
addScript("/sanitizer.js");
|
||||
addScript("/AngularPublic.js");
|
||||
addScripts('Angular.js',
|
||||
'JSON.js',
|
||||
'Compiler.js',
|
||||
'Scope.js',
|
||||
'Injector.js',
|
||||
'jqLite.js',
|
||||
'parser.js',
|
||||
'Resource.js',
|
||||
'Browser.js',
|
||||
'sanitizer.js',
|
||||
'AngularPublic.js',
|
||||
|
||||
// Extension points
|
||||
addScript("/services.js");
|
||||
addScript("/apis.js");
|
||||
addScript("/filters.js");
|
||||
addScript("/formatters.js");
|
||||
addScript("/validators.js");
|
||||
addScript("/directives.js");
|
||||
addScript("/markups.js");
|
||||
addScript("/widgets.js");
|
||||
// Extension points
|
||||
'services.js',
|
||||
'apis.js',
|
||||
'filters.js',
|
||||
'formatters.js',
|
||||
'validators.js',
|
||||
'directives.js',
|
||||
'markups.js',
|
||||
'widgets.js');
|
||||
|
||||
|
||||
window.onload = function(){
|
||||
try {
|
||||
if (previousOnLoad) previousOnLoad();
|
||||
} catch(e) {}
|
||||
function onLoadListener(){
|
||||
// empty the cache to prevent mem leaks
|
||||
globalVars = {};
|
||||
|
||||
//angular-ie-compat.js needs to be pregenerated for development with IE<8
|
||||
if (msie<8) addScript('../angular-ie-compat.js');
|
||||
|
||||
angularInit(angularJsConfig(document));
|
||||
};
|
||||
}
|
||||
|
||||
})(window.onload);
|
||||
if (window.addEventListener){
|
||||
window.addEventListener('load', onLoadListener, false);
|
||||
} else if (window.attachEvent){
|
||||
window.attachEvent('onload', onLoadListener);
|
||||
}
|
||||
|
||||
})(window);
|
||||
|
||||
|
||||
+1
-1
@@ -21,4 +21,4 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
(function(window, document, previousOnLoad){
|
||||
(function(window, document){
|
||||
|
||||
+3
-6
@@ -1,9 +1,6 @@
|
||||
|
||||
window.onload = function(){
|
||||
try {
|
||||
if (previousOnLoad) previousOnLoad();
|
||||
} catch(e) {}
|
||||
jqLite(document).ready(function(){
|
||||
angularInit(angularJsConfig(document));
|
||||
};
|
||||
});
|
||||
|
||||
})(window, document, window.onload);
|
||||
})(window, document);
|
||||
|
||||
+2
-2
@@ -499,7 +499,7 @@ var angularArray = {
|
||||
'count':function(array, condition) {
|
||||
if (!condition) return array.length;
|
||||
var fn = angular['Function']['compile'](condition), count = 0;
|
||||
foreach(array, function(value){
|
||||
forEach(array, function(value){
|
||||
if (fn(value)) {
|
||||
count ++;
|
||||
}
|
||||
@@ -747,7 +747,7 @@ var angularFunction = {
|
||||
|
||||
function defineApi(dst, chain){
|
||||
angular[dst] = angular[dst] || {};
|
||||
foreach(chain, function(parent){
|
||||
forEach(chain, function(parent){
|
||||
extend(angular[dst], parent);
|
||||
});
|
||||
}
|
||||
|
||||
+7
-7
@@ -222,7 +222,7 @@ function compileBindTemplate(template){
|
||||
var fn = bindTemplateCache[template];
|
||||
if (!fn) {
|
||||
var bindings = [];
|
||||
foreach(parseBindings(template), function(text){
|
||||
forEach(parseBindings(template), function(text){
|
||||
var exp = binding(text);
|
||||
bindings.push(exp ? function(element){
|
||||
var error, value = this.$tryEval(exp, function(e){
|
||||
@@ -423,14 +423,14 @@ angularDirective("ng:bind-attr", function(expression){
|
||||
* TODO: maybe we should consider allowing users to control event propagation in the future.
|
||||
*/
|
||||
angularDirective("ng:click", function(expression, element){
|
||||
return function(element){
|
||||
return injectUpdateView(function($updateView, element){
|
||||
var self = this;
|
||||
element.bind('click', function(event){
|
||||
self.$tryEval(expression, element);
|
||||
self.$root.$eval();
|
||||
$updateView();
|
||||
event.stopPropagation();
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -471,14 +471,14 @@ angularDirective("ng:click", function(expression, element){
|
||||
* server and reloading the current page).
|
||||
*/
|
||||
angularDirective("ng:submit", function(expression, element) {
|
||||
return function(element) {
|
||||
return injectUpdateView(function($updateView, element) {
|
||||
var self = this;
|
||||
element.bind('submit', function(event) {
|
||||
self.$tryEval(expression, element);
|
||||
self.$root.$eval();
|
||||
$updateView();
|
||||
event.preventDefault();
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
+10
-4
@@ -217,12 +217,18 @@ angularFilter.date = function(date, format) {
|
||||
var text = date.toLocaleDateString(), fn;
|
||||
if (format && isString(format)) {
|
||||
text = '';
|
||||
var parts = [];
|
||||
var parts = [], match;
|
||||
while(format) {
|
||||
parts = concat(parts, DATE_FORMATS_SPLIT.exec(format), 1);
|
||||
format = parts.pop();
|
||||
match = DATE_FORMATS_SPLIT.exec(format);
|
||||
if (match) {
|
||||
parts = concat(parts, match, 1);
|
||||
format = parts.pop();
|
||||
} else {
|
||||
parts.push(format);
|
||||
format = null;
|
||||
}
|
||||
}
|
||||
foreach(parts, function(value){
|
||||
forEach(parts, function(value){
|
||||
fn = DATE_FORMATS[value];
|
||||
text += fn ? fn(date) : value;
|
||||
});
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ angularFormatter.list = formatter(
|
||||
function(obj) { return obj ? obj.join(", ") : obj; },
|
||||
function(value) {
|
||||
var list = [];
|
||||
foreach((value || '').split(','), function(item){
|
||||
forEach((value || '').split(','), function(item){
|
||||
item = trim(item);
|
||||
if (item) list.push(item);
|
||||
});
|
||||
|
||||
+31
-18
@@ -5,12 +5,12 @@
|
||||
var jqCache = {},
|
||||
jqName = 'ng-' + new Date().getTime(),
|
||||
jqId = 1,
|
||||
addEventListener = (window.document.attachEvent ?
|
||||
function(element, type, fn) {element.attachEvent('on' + type, fn);} :
|
||||
function(element, type, fn) {element.addEventListener(type, fn, false);}),
|
||||
removeEventListener = (window.document.detachEvent ?
|
||||
function(element, type, fn) {element.detachEvent('on' + type, fn); } :
|
||||
function(element, type, fn) { element.removeEventListener(type, fn, false); });
|
||||
addEventListenerFn = (window.document.addEventListener ?
|
||||
function(element, type, fn) {element.addEventListener(type, fn, false);} :
|
||||
function(element, type, fn) {element.attachEvent('on' + type, fn);}),
|
||||
removeEventListenerFn = (window.document.removeEventListener ?
|
||||
function(element, type, fn) {element.removeEventListener(type, fn, false); } :
|
||||
function(element, type, fn) {element.detachEvent('on' + type, fn); });
|
||||
|
||||
function jqNextId() { return (jqId++); }
|
||||
|
||||
@@ -18,8 +18,8 @@ function jqClearData(element) {
|
||||
var cacheId = element[jqName],
|
||||
cache = jqCache[cacheId];
|
||||
if (cache) {
|
||||
foreach(cache.bind || {}, function(fn, type){
|
||||
removeEventListener(element, type, fn);
|
||||
forEach(cache.bind || {}, function(fn, type){
|
||||
removeEventListenerFn(element, type, fn);
|
||||
});
|
||||
delete jqCache[cacheId];
|
||||
if (msie)
|
||||
@@ -47,14 +47,14 @@ function getStyle(element) {
|
||||
}
|
||||
|
||||
function JQLite(element) {
|
||||
if (isElement(element)) {
|
||||
this[0] = element;
|
||||
this.length = 1;
|
||||
} else if (isDefined(element.length) && element.item) {
|
||||
if (!isElement(element) && isDefined(element.length) && element.item && !isWindow(element)) {
|
||||
for(var i=0; i < element.length; i++) {
|
||||
this[i] = element[i];
|
||||
}
|
||||
this.length = element.length;
|
||||
} else {
|
||||
this[0] = element;
|
||||
this.length = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,19 +81,32 @@ JQLite.prototype = {
|
||||
dealoc: function(){
|
||||
(function dealoc(element){
|
||||
jqClearData(element);
|
||||
for ( var i = 0, children = element.childNodes; i < children.length; i++) {
|
||||
for ( var i = 0, children = element.childNodes || []; i < children.length; i++) {
|
||||
dealoc(children[i]);
|
||||
}
|
||||
})(this[0]);
|
||||
},
|
||||
|
||||
ready: function(fn) {
|
||||
var fired = false;
|
||||
|
||||
function trigger() {
|
||||
if (fired) return;
|
||||
fired = true;
|
||||
fn();
|
||||
}
|
||||
|
||||
this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9
|
||||
jqLite(window).bind('load', trigger); // fallback to window.onload for others
|
||||
},
|
||||
|
||||
bind: function(type, fn){
|
||||
var self = this,
|
||||
element = self[0],
|
||||
bind = self.data('bind'),
|
||||
eventHandler;
|
||||
if (!bind) this.data('bind', bind = {});
|
||||
foreach(type.split(' '), function(type){
|
||||
forEach(type.split(' '), function(type){
|
||||
eventHandler = bind[type];
|
||||
if (!eventHandler) {
|
||||
bind[type] = eventHandler = function(event) {
|
||||
@@ -107,12 +120,12 @@ JQLite.prototype = {
|
||||
event.cancelBubble = true; //ie
|
||||
};
|
||||
}
|
||||
foreach(eventHandler.fns, function(fn){
|
||||
forEach(eventHandler.fns, function(fn){
|
||||
fn.call(self, event);
|
||||
});
|
||||
};
|
||||
eventHandler.fns = [];
|
||||
addEventListener(element, type, eventHandler);
|
||||
addEventListenerFn(element, type, eventHandler);
|
||||
}
|
||||
eventHandler.fns.push(fn);
|
||||
});
|
||||
@@ -129,7 +142,7 @@ JQLite.prototype = {
|
||||
append: function(node) {
|
||||
var self = this[0];
|
||||
node = jqLite(node);
|
||||
foreach(node, function(child){
|
||||
forEach(node, function(child){
|
||||
self.appendChild(child);
|
||||
});
|
||||
},
|
||||
@@ -187,7 +200,7 @@ JQLite.prototype = {
|
||||
attr: function(name, value){
|
||||
var e = this[0];
|
||||
if (isObject(name)) {
|
||||
foreach(name, function(value, name){
|
||||
forEach(name, function(value, name){
|
||||
e.setAttribute(name, value);
|
||||
});
|
||||
} else if (isDefined(value)) {
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ angularTextMarkup('{{}}', function(text, textNode, parentElement) {
|
||||
parentElement.attr('ng:bind-template', text);
|
||||
} else {
|
||||
var cursor = textNode, newElement;
|
||||
foreach(parseBindings(text), function(text){
|
||||
forEach(parseBindings(text), function(text){
|
||||
var exp = binding(text);
|
||||
if (exp) {
|
||||
newElement = self.element('span');
|
||||
@@ -59,7 +59,7 @@ angularTextMarkup('{{}}', function(text, textNode, parentElement) {
|
||||
|
||||
// TODO: this should be widget not a markup
|
||||
angularTextMarkup('OPTION', function(text, textNode, parentElement){
|
||||
if (nodeName(parentElement) == "OPTION") {
|
||||
if (nodeName_(parentElement) == "OPTION") {
|
||||
var select = document.createElement('select');
|
||||
select.insertBefore(parentElement[0].cloneNode(true), _null);
|
||||
if (!select.innerHTML.match(/<option(\s.*\s|\s)value\s*=\s*.*>.*<\/\s*option\s*>/gi)) {
|
||||
|
||||
+126
-59
@@ -26,13 +26,13 @@ var OPERATORS = {
|
||||
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
|
||||
|
||||
function lex(text, parseStringsForObjects){
|
||||
var dateParseLength = parseStringsForObjects ? 24 : -1,
|
||||
var dateParseLength = parseStringsForObjects ? DATE_ISOSTRING_LN : -1,
|
||||
tokens = [],
|
||||
token,
|
||||
index = 0,
|
||||
json = [],
|
||||
ch,
|
||||
lastCh = ':';
|
||||
lastCh = ':'; // can start regexp
|
||||
|
||||
while (index < text.length) {
|
||||
ch = text.charAt(index);
|
||||
@@ -42,12 +42,17 @@ function lex(text, parseStringsForObjects){
|
||||
readNumber();
|
||||
} else if (isIdent(ch)) {
|
||||
readIdent();
|
||||
// identifiers can only be if the preceding char was a { or ,
|
||||
if (was('{,') && json[0]=='{' &&
|
||||
(token=tokens[tokens.length-1])) {
|
||||
token.json = token.text.indexOf('.') == -1;
|
||||
}
|
||||
} else if (is('(){}[].,;:')) {
|
||||
tokens.push({index:index, text:ch, json:is('{}[]:,')});
|
||||
tokens.push({
|
||||
index:index,
|
||||
text:ch,
|
||||
json:(was(':[,') && is('{[')) || is('}]:,')
|
||||
});
|
||||
if (is('{[')) json.unshift(ch);
|
||||
if (is('}]')) json.shift();
|
||||
index++;
|
||||
@@ -71,9 +76,6 @@ function lex(text, parseStringsForObjects){
|
||||
lastCh = ch;
|
||||
}
|
||||
return tokens;
|
||||
|
||||
|
||||
//////////////////////////////////////////////
|
||||
|
||||
function is(chars) {
|
||||
return chars.indexOf(ch) != -1;
|
||||
@@ -98,6 +100,10 @@ function lex(text, parseStringsForObjects){
|
||||
'A' <= ch && ch <= 'Z' ||
|
||||
'_' == ch || ch == '$';
|
||||
}
|
||||
function isExpOperator(ch) {
|
||||
return ch == '-' || ch == '+' || isNumber(ch);
|
||||
}
|
||||
|
||||
function throwError(error, start, end) {
|
||||
end = end || index;
|
||||
throw Error("Lexer Error: " + error + " at column" +
|
||||
@@ -106,61 +112,103 @@ function lex(text, parseStringsForObjects){
|
||||
" " + end) +
|
||||
" in expression [" + text + "].");
|
||||
}
|
||||
|
||||
function consume(regexp, processToken, errorMsg) {
|
||||
var match = text.substr(index).match(regexp);
|
||||
var token = {index: index};
|
||||
var start = index;
|
||||
if (!match) throwError(errorMsg);
|
||||
index += match[0].length;
|
||||
processToken(token, token.text = match[0], start);
|
||||
tokens.push(token);
|
||||
}
|
||||
|
||||
function readNumber() {
|
||||
consume(/^(\d+)?(\.\d+)?([eE][+-]?\d+)?/, function(token, number){
|
||||
token.text = number = 1 * number;
|
||||
token.json = true;
|
||||
token.fn = valueFn(number);
|
||||
}, "Not a valid number");
|
||||
}
|
||||
|
||||
function readIdent() {
|
||||
consume(/^[\w_\$][\w_\$\d]*(\.[\w_\$][\w_\$\d]*)*/, function(token, ident){
|
||||
fn = OPERATORS[ident];
|
||||
if (!fn) {
|
||||
fn = getterFn(ident);
|
||||
fn.isAssignable = ident;
|
||||
var number = "";
|
||||
var start = index;
|
||||
while (index < text.length) {
|
||||
var ch = lowercase(text.charAt(index));
|
||||
if (ch == '.' || isNumber(ch)) {
|
||||
number += ch;
|
||||
} else {
|
||||
var peekCh = peek();
|
||||
if (ch == 'e' && isExpOperator(peekCh)) {
|
||||
number += ch;
|
||||
} else if (isExpOperator(ch) &&
|
||||
peekCh && isNumber(peekCh) &&
|
||||
number.charAt(number.length - 1) == 'e') {
|
||||
number += ch;
|
||||
} else if (isExpOperator(ch) &&
|
||||
(!peekCh || !isNumber(peekCh)) &&
|
||||
number.charAt(number.length - 1) == 'e') {
|
||||
throwError('Invalid exponent');
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
token.fn = OPERATORS[ident]||extend(getterFn(ident), {
|
||||
index++;
|
||||
}
|
||||
number = 1 * number;
|
||||
tokens.push({index:start, text:number, json:true,
|
||||
fn:function(){return number;}});
|
||||
}
|
||||
function readIdent() {
|
||||
var ident = "";
|
||||
var start = index;
|
||||
var fn;
|
||||
while (index < text.length) {
|
||||
var ch = text.charAt(index);
|
||||
if (ch == '.' || isIdent(ch) || isNumber(ch)) {
|
||||
ident += ch;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
fn = OPERATORS[ident];
|
||||
tokens.push({
|
||||
index:start,
|
||||
text:ident,
|
||||
json: fn,
|
||||
fn:fn||extend(getterFn(ident), {
|
||||
assign:function(self, value){
|
||||
return setter(self, ident, value);
|
||||
}
|
||||
});
|
||||
token.json = OPERATORS[ident];
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function readString(quote) {
|
||||
consume(/^(('(\\'|[^'])*')|("(\\"|[^"])*"))/, function(token, rawString, start){
|
||||
var hasError;
|
||||
var string = token.string = rawString.substr(1, rawString.length - 2).
|
||||
replace(/(\\u(.?.?.?.?))|(\\(.))/g,
|
||||
function(match, wholeUnicode, unicode, wholeEscape, escape){
|
||||
if (unicode && !unicode.match(/[\da-fA-F]{4}/))
|
||||
hasError = hasError || bind(null, throwError, "Invalid unicode escape [\\u" + unicode + "]", start);
|
||||
return unicode ?
|
||||
String.fromCharCode(parseInt(unicode, 16)) :
|
||||
ESCAPE[escape] || escape;
|
||||
});
|
||||
(hasError||noop)();
|
||||
token.json = true;
|
||||
token.fn = function(){
|
||||
return (string.length == dateParseLength) ?
|
||||
angular['String']['toDate'](string) :
|
||||
string;
|
||||
};
|
||||
}, "Unterminated string");
|
||||
var start = index;
|
||||
index++;
|
||||
var string = "";
|
||||
var rawString = quote;
|
||||
var escape = false;
|
||||
while (index < text.length) {
|
||||
var ch = text.charAt(index);
|
||||
rawString += ch;
|
||||
if (escape) {
|
||||
if (ch == 'u') {
|
||||
var hex = text.substring(index + 1, index + 5);
|
||||
if (!hex.match(/[\da-f]{4}/i))
|
||||
throwError( "Invalid unicode escape [\\u" + hex + "]");
|
||||
index += 4;
|
||||
string += String.fromCharCode(parseInt(hex, 16));
|
||||
} else {
|
||||
var rep = ESCAPE[ch];
|
||||
if (rep) {
|
||||
string += rep;
|
||||
} else {
|
||||
string += ch;
|
||||
}
|
||||
}
|
||||
escape = false;
|
||||
} else if (ch == '\\') {
|
||||
escape = true;
|
||||
} else if (ch == quote) {
|
||||
index++;
|
||||
tokens.push({index:start, text:rawString, string:string, json:true,
|
||||
fn:function(){
|
||||
return (string.length == dateParseLength) ?
|
||||
angular['String']['toDate'](string) : string;
|
||||
}});
|
||||
return;
|
||||
} else {
|
||||
string += ch;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
throwError("Unterminated quote", start);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +216,26 @@ function lex(text, parseStringsForObjects){
|
||||
|
||||
function parser(text, json){
|
||||
var ZERO = valueFn(0),
|
||||
tokens = lex(text, json);
|
||||
tokens = lex(text, json),
|
||||
assignment = _assignment,
|
||||
functionCall = _functionCall,
|
||||
fieldAccess = _fieldAccess,
|
||||
objectIndex = _objectIndex,
|
||||
filterChain = _filterChain,
|
||||
functionIdent = _functionIdent,
|
||||
pipeFunction = _pipeFunction;
|
||||
if(json){
|
||||
// The extra level of aliasing is here, just in case the lexer misses something, so that
|
||||
// we prevent any accidental execution in JSON.
|
||||
assignment = logicalOR;
|
||||
functionCall =
|
||||
fieldAccess =
|
||||
objectIndex =
|
||||
filterChain =
|
||||
functionIdent =
|
||||
pipeFunction =
|
||||
function (){ throwError("is not valid json", {text:text, index:0}); };
|
||||
}
|
||||
return {
|
||||
assertAllConsumed: assertAllConsumed,
|
||||
primary: primary,
|
||||
@@ -266,7 +333,7 @@ function parser(text, json){
|
||||
}
|
||||
}
|
||||
|
||||
function filterChain(){
|
||||
function _filterChain(){
|
||||
var left = expression();
|
||||
var token;
|
||||
while(true) {
|
||||
@@ -286,7 +353,7 @@ function parser(text, json){
|
||||
return pipeFunction(angularValidator);
|
||||
}
|
||||
|
||||
function pipeFunction(fnScope){
|
||||
function _pipeFunction(fnScope){
|
||||
var fn = functionIdent(fnScope);
|
||||
var argsFn = [];
|
||||
var token;
|
||||
@@ -312,7 +379,7 @@ function parser(text, json){
|
||||
return assignment();
|
||||
}
|
||||
|
||||
function assignment(){
|
||||
function _assignment(){
|
||||
var left = logicalOR();
|
||||
var right;
|
||||
var token;
|
||||
@@ -400,7 +467,7 @@ function parser(text, json){
|
||||
}
|
||||
}
|
||||
|
||||
function functionIdent(fnScope) {
|
||||
function _functionIdent(fnScope) {
|
||||
var token = expect();
|
||||
var element = token.text.split('.');
|
||||
var instance = fnScope;
|
||||
@@ -448,7 +515,7 @@ function parser(text, json){
|
||||
return primary;
|
||||
}
|
||||
|
||||
function fieldAccess(object) {
|
||||
function _fieldAccess(object) {
|
||||
var field = expect().text;
|
||||
var getter = getterFn(field);
|
||||
return extend(function (self){
|
||||
@@ -460,7 +527,7 @@ function parser(text, json){
|
||||
});
|
||||
}
|
||||
|
||||
function objectIndex(obj) {
|
||||
function _objectIndex(obj) {
|
||||
var indexFn = expression();
|
||||
consume(']');
|
||||
return extend(
|
||||
@@ -475,7 +542,7 @@ function parser(text, json){
|
||||
});
|
||||
}
|
||||
|
||||
function functionCall(fn) {
|
||||
function _functionCall(fn) {
|
||||
var argsFn = [];
|
||||
if (peekToken().text != ')') {
|
||||
do {
|
||||
|
||||
+1
-1
@@ -252,7 +252,7 @@ function htmlSanitizeWriter(buf){
|
||||
if (!ignore && validElements[tag] == true) {
|
||||
out('<');
|
||||
out(tag);
|
||||
foreach(attrs, function(value, key){
|
||||
forEach(attrs, function(value, key){
|
||||
var lkey=lowercase(key);
|
||||
if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
|
||||
out(' ');
|
||||
|
||||
@@ -21,7 +21,7 @@ angular.scenario.Describe = function(descName, parent) {
|
||||
var beforeEachFns = this.beforeEachFns;
|
||||
this.setupBefore = function() {
|
||||
if (parent) parent.setupBefore.call(this);
|
||||
angular.foreach(beforeEachFns, function(fn) { fn.call(this); }, this);
|
||||
angular.forEach(beforeEachFns, function(fn) { fn.call(this); }, this);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -29,7 +29,7 @@ angular.scenario.Describe = function(descName, parent) {
|
||||
*/
|
||||
var afterEachFns = this.afterEachFns;
|
||||
this.setupAfter = function() {
|
||||
angular.foreach(afterEachFns, function(fn) { fn.call(this); }, this);
|
||||
angular.forEach(afterEachFns, function(fn) { fn.call(this); }, this);
|
||||
if (parent) parent.setupAfter.call(this);
|
||||
};
|
||||
};
|
||||
@@ -133,14 +133,14 @@ angular.scenario.Describe.prototype.xit = angular.noop;
|
||||
*/
|
||||
angular.scenario.Describe.prototype.getSpecs = function() {
|
||||
var specs = arguments[0] || [];
|
||||
angular.foreach(this.children, function(child) {
|
||||
angular.forEach(this.children, function(child) {
|
||||
child.getSpecs(specs);
|
||||
});
|
||||
angular.foreach(this.its, function(it) {
|
||||
angular.forEach(this.its, function(it) {
|
||||
specs.push(it);
|
||||
});
|
||||
var only = [];
|
||||
angular.foreach(specs, function(it) {
|
||||
angular.forEach(specs, function(it) {
|
||||
if (it.only) {
|
||||
only.push(it);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ angular.scenario.ObjectModel = function(runner) {
|
||||
|
||||
runner.on('SpecBegin', function(spec) {
|
||||
var block = self.value;
|
||||
angular.foreach(self.getDefinitionPath(spec), function(def) {
|
||||
angular.forEach(self.getDefinitionPath(spec), function(def) {
|
||||
if (!block.children[def.name]) {
|
||||
block.children[def.name] = {
|
||||
id: def.id,
|
||||
|
||||
@@ -16,7 +16,7 @@ angular.scenario.Runner = function($window) {
|
||||
beforeEach: this.beforeEach,
|
||||
afterEach: this.afterEach
|
||||
};
|
||||
angular.foreach(this.api, angular.bind(this, function(fn, key) {
|
||||
angular.forEach(this.api, angular.bind(this, function(fn, key) {
|
||||
this.$window[key] = angular.bind(this, fn);
|
||||
}));
|
||||
};
|
||||
@@ -33,7 +33,7 @@ angular.scenario.Runner.prototype.emit = function(eventName) {
|
||||
eventName = eventName.toLowerCase();
|
||||
if (!this.listeners[eventName])
|
||||
return;
|
||||
angular.foreach(this.listeners[eventName], function(listener) {
|
||||
angular.forEach(this.listeners[eventName], function(listener) {
|
||||
listener.apply(self, args);
|
||||
});
|
||||
};
|
||||
@@ -164,17 +164,17 @@ angular.scenario.Runner.prototype.run = function(application) {
|
||||
asyncForEach(this.rootDescribe.getSpecs(), function(spec, specDone) {
|
||||
var dslCache = {};
|
||||
var runner = self.createSpecRunner_($root);
|
||||
angular.foreach(angular.scenario.dsl, function(fn, key) {
|
||||
angular.forEach(angular.scenario.dsl, function(fn, key) {
|
||||
dslCache[key] = fn.call($root);
|
||||
});
|
||||
angular.foreach(angular.scenario.dsl, function(fn, key) {
|
||||
angular.forEach(angular.scenario.dsl, function(fn, key) {
|
||||
self.$window[key] = function() {
|
||||
var line = callerFile(3);
|
||||
var scope = angular.scope(runner);
|
||||
|
||||
// Make the dsl accessible on the current chain
|
||||
scope.dsl = {};
|
||||
angular.foreach(dslCache, function(fn, key) {
|
||||
angular.forEach(dslCache, function(fn, key) {
|
||||
scope.dsl[key] = function() {
|
||||
return dslCache[key].apply(scope, arguments);
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ angular.scenario.dsl = angular.scenario.dsl || function(name, fn) {
|
||||
return result;
|
||||
var self = this;
|
||||
var chain = angular.extend({}, result);
|
||||
angular.foreach(chain, function(value, name) {
|
||||
angular.forEach(chain, function(value, name) {
|
||||
if (angular.isFunction(value)) {
|
||||
chain[name] = function() {
|
||||
return executeStatement.call(self, value, arguments);
|
||||
@@ -101,7 +101,7 @@ function angularScenarioInit($scenario, config) {
|
||||
output = config.scenario_output.split(',');
|
||||
}
|
||||
|
||||
angular.foreach(angular.scenario.output, function(fn, name) {
|
||||
angular.forEach(angular.scenario.output, function(fn, name) {
|
||||
if (!output.length || indexOf(output,name) != -1) {
|
||||
var context = body.append('<div></div>').find('div:last');
|
||||
context.attr('id', name);
|
||||
@@ -244,7 +244,7 @@ function browserTrigger(element, type) {
|
||||
'select-multiple': 'change'
|
||||
}[element.type] || 'click';
|
||||
}
|
||||
if (lowercase(nodeName(element)) == 'option') {
|
||||
if (lowercase(nodeName_(element)) == 'option') {
|
||||
element.parentNode.value = element.value;
|
||||
element = element.parentNode;
|
||||
type = 'change';
|
||||
@@ -285,7 +285,7 @@ function browserTrigger(element, type) {
|
||||
(function(fn){
|
||||
var parentTrigger = fn.trigger;
|
||||
fn.trigger = function(type) {
|
||||
if (/(click|change|keyup)/.test(type)) {
|
||||
if (/(click|change|keydown)/.test(type)) {
|
||||
return this.each(function(index, node) {
|
||||
browserTrigger(node, type);
|
||||
});
|
||||
|
||||
@@ -112,7 +112,7 @@ angular.scenario.SpecRunner.prototype.addFutureAction = function(name, behavior,
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
selector = (self.selector || '') + ' ' + (selector || '');
|
||||
selector = _jQuery.trim(selector) || '*';
|
||||
angular.foreach(args, function(value, index) {
|
||||
angular.forEach(args, function(value, index) {
|
||||
selector = selector.replace('$' + (index + 1), value);
|
||||
});
|
||||
var result = $document.find(selector);
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
(function(window, document, previousOnLoad){
|
||||
(function(window, document){
|
||||
var _jQuery = window.jQuery.noConflict(true);
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
var $scenario = new angular.scenario.Runner(window);
|
||||
|
||||
window.onload = function() {
|
||||
try {
|
||||
if (previousOnLoad) previousOnLoad();
|
||||
} catch(e) {}
|
||||
jqLite(document).ready(function() {
|
||||
angularScenarioInit($scenario, angularJsConfig(document));
|
||||
};
|
||||
});
|
||||
|
||||
})(window, document, window.onload);
|
||||
})(window, document);
|
||||
|
||||
+2
-2
@@ -331,7 +331,7 @@ angular.scenario.dsl('element', function() {
|
||||
});
|
||||
};
|
||||
|
||||
angular.foreach(KEY_VALUE_METHODS, function(methodName) {
|
||||
angular.forEach(KEY_VALUE_METHODS, function(methodName) {
|
||||
chain[methodName] = function(name, value) {
|
||||
var futureName = "element '" + this.label + "' get " + methodName + " '" + name + "'";
|
||||
if (angular.isDefined(value)) {
|
||||
@@ -344,7 +344,7 @@ angular.scenario.dsl('element', function() {
|
||||
};
|
||||
});
|
||||
|
||||
angular.foreach(VALUE_METHODS, function(methodName) {
|
||||
angular.forEach(VALUE_METHODS, function(methodName) {
|
||||
chain[methodName] = function(value) {
|
||||
var futureName = "element '" + this.label + "' " + methodName;
|
||||
if (angular.isDefined(value)) {
|
||||
|
||||
@@ -121,7 +121,7 @@ angular.scenario.output('html', function(context, runner) {
|
||||
*/
|
||||
function findContext(spec) {
|
||||
var currentContext = context.find('#specs');
|
||||
angular.foreach(model.getDefinitionPath(spec), function(defn) {
|
||||
angular.forEach(model.getDefinitionPath(spec), function(defn) {
|
||||
var id = 'describe-' + defn.id;
|
||||
if (!context.find('#' + id).length) {
|
||||
currentContext.find('> .test-children').append(
|
||||
|
||||
@@ -17,7 +17,7 @@ angular.scenario.output('xml', function(context, runner) {
|
||||
* @param {Object} tree node to serialize
|
||||
*/
|
||||
function serializeXml(context, tree) {
|
||||
angular.foreach(tree.children, function(child) {
|
||||
angular.forEach(tree.children, function(child) {
|
||||
var describeContext = $('<describe></describe>');
|
||||
describeContext.attr('id', child.id);
|
||||
describeContext.attr('name', child.name);
|
||||
@@ -26,14 +26,14 @@ angular.scenario.output('xml', function(context, runner) {
|
||||
});
|
||||
var its = $('<its></its>');
|
||||
context.append(its);
|
||||
angular.foreach(tree.specs, function(spec) {
|
||||
angular.forEach(tree.specs, function(spec) {
|
||||
var it = $('<it></it>');
|
||||
it.attr('id', spec.id);
|
||||
it.attr('name', spec.name);
|
||||
it.attr('duration', spec.duration);
|
||||
it.attr('status', spec.status);
|
||||
its.append(it);
|
||||
angular.foreach(spec.steps, function(step) {
|
||||
angular.forEach(spec.steps, function(step) {
|
||||
var stepContext = $('<step></step>');
|
||||
stepContext.attr('name', step.name);
|
||||
stepContext.attr('duration', step.duration);
|
||||
|
||||
+121
-76
@@ -1,11 +1,10 @@
|
||||
var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
|
||||
HASH_MATCH = /^([^\?]*)?(\?([^\?]*))?$/,
|
||||
DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21},
|
||||
EAGER = 'eager',
|
||||
EAGER_PUBLISHED = EAGER + '-published';
|
||||
EAGER = true;
|
||||
|
||||
function angularServiceInject(name, fn, inject, eager) {
|
||||
angularService(name, fn, {$inject:inject, $creation:eager});
|
||||
angularService(name, fn, {$inject:inject, $eager:eager});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,7 +25,7 @@ function angularServiceInject(name, fn, inject, eager) {
|
||||
<input ng:init="greeting='Hello World!'" type="text" name="greeting" />
|
||||
<button ng:click="$window.alert(greeting)">ALERT</button>
|
||||
*/
|
||||
angularServiceInject("$window", bind(window, identity, window), [], EAGER_PUBLISHED);
|
||||
angularServiceInject("$window", bind(window, identity, window), [], EAGER);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
@@ -39,7 +38,7 @@ angularServiceInject("$window", bind(window, identity, window), [], EAGER_PUBLIS
|
||||
*/
|
||||
angularServiceInject("$document", function(window){
|
||||
return jqLite(window.document);
|
||||
}, ['$window'], EAGER_PUBLISHED);
|
||||
}, ['$window'], EAGER);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
@@ -69,27 +68,20 @@ angularServiceInject("$document", function(window){
|
||||
<input type='text' name="$location.hash"/>
|
||||
<pre>$location = {{$location}}</pre>
|
||||
*/
|
||||
angularServiceInject("$location", function(browser) {
|
||||
angularServiceInject("$location", function($browser) {
|
||||
var scope = this,
|
||||
location = {toString:toString, update:update, updateHash: updateHash},
|
||||
lastBrowserUrl = browser.getUrl(),
|
||||
lastLocationHref,
|
||||
lastLocationHash;
|
||||
location = {update:update, updateHash: updateHash},
|
||||
lastLocation = {};
|
||||
|
||||
browser.addPollFn(function() {
|
||||
if (lastBrowserUrl != browser.getUrl()) {
|
||||
update(lastBrowserUrl = browser.getUrl());
|
||||
updateLastLocation();
|
||||
scope.$eval();
|
||||
}
|
||||
});
|
||||
$browser.onHashChange(function() { //register
|
||||
update($browser.getUrl());
|
||||
copy(location, lastLocation);
|
||||
scope.$eval();
|
||||
})(); //initialize
|
||||
|
||||
this.$onEval(PRIORITY_FIRST, updateBrowser);
|
||||
this.$onEval(PRIORITY_FIRST, sync);
|
||||
this.$onEval(PRIORITY_LAST, updateBrowser);
|
||||
|
||||
update(lastBrowserUrl);
|
||||
updateLastLocation();
|
||||
|
||||
return location;
|
||||
|
||||
// PUBLIC METHODS
|
||||
@@ -110,7 +102,7 @@ angularServiceInject("$location", function(browser) {
|
||||
* scope.$location.update({host: 'www.google.com', protocol: 'https'});
|
||||
* scope.$location.update({hashPath: '/path', hashSearch: {a: 'b', x: true}});
|
||||
*
|
||||
* @param {(string|Object)} href Full href as a string or hash object with properties
|
||||
* @param {(string|Object)} href Full href as a string or object with properties
|
||||
*/
|
||||
function update(href) {
|
||||
if (isString(href)) {
|
||||
@@ -166,62 +158,55 @@ angularServiceInject("$location", function(browser) {
|
||||
update(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#toString
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* Returns string representation - href
|
||||
*/
|
||||
function toString() {
|
||||
updateLocation();
|
||||
return location.href;
|
||||
}
|
||||
|
||||
// INNER METHODS
|
||||
|
||||
/**
|
||||
* Update location object
|
||||
* Synchronizes all location object properties.
|
||||
*
|
||||
* User is allowed to change properties, so after property change,
|
||||
* location object is not in consistent state.
|
||||
*
|
||||
* Properties are synced with the following precedence order:
|
||||
*
|
||||
* - `$location.href`
|
||||
* - `$location.hash`
|
||||
* - everything else
|
||||
*
|
||||
* @example
|
||||
* scope.$location.href = 'http://www.angularjs.org/path#a/b'
|
||||
* immediately after this call, other properties are still the old ones...
|
||||
*
|
||||
* This method checks the changes and update location to the consistent state
|
||||
*/
|
||||
function updateLocation() {
|
||||
if (location.href == lastLocationHref) {
|
||||
if (location.hash == lastLocationHash) {
|
||||
location.hash = composeHash(location);
|
||||
function sync() {
|
||||
if (!equals(location, lastLocation)) {
|
||||
if (location.href != lastLocation.href) {
|
||||
update(location.href);
|
||||
return;
|
||||
}
|
||||
location.href = composeHref(location);
|
||||
if (location.hash != lastLocation.hash) {
|
||||
var hash = parseHash(location.hash);
|
||||
updateHash(hash.path, hash.search);
|
||||
} else {
|
||||
location.hash = composeHash(location);
|
||||
location.href = composeHref(location);
|
||||
}
|
||||
update(location.href);
|
||||
}
|
||||
update(location.href);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update information about last location
|
||||
*/
|
||||
function updateLastLocation() {
|
||||
lastLocationHref = location.href;
|
||||
lastLocationHash = location.hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* If location has changed, update the browser
|
||||
* This method is called at the end of $eval() phase
|
||||
*/
|
||||
function updateBrowser() {
|
||||
updateLocation();
|
||||
sync();
|
||||
|
||||
if (location.href != lastLocationHref) {
|
||||
browser.setUrl(lastBrowserUrl = location.href);
|
||||
updateLastLocation();
|
||||
if ($browser.getUrl() != location.href) {
|
||||
$browser.setUrl(location.href);
|
||||
copy(location, lastLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +280,7 @@ angularServiceInject("$location", function(browser) {
|
||||
|
||||
return h;
|
||||
}
|
||||
}, ['$browser'], EAGER_PUBLISHED);
|
||||
}, ['$browser']);
|
||||
|
||||
|
||||
/**
|
||||
@@ -372,7 +357,7 @@ angularServiceInject("$log", function($window){
|
||||
if (logFn.apply) {
|
||||
return function(){
|
||||
var args = [];
|
||||
foreach(arguments, function(arg){
|
||||
forEach(arguments, function(arg){
|
||||
args.push(formatError(arg));
|
||||
});
|
||||
return logFn.apply(console, args);
|
||||
@@ -382,7 +367,7 @@ angularServiceInject("$log", function($window){
|
||||
return logFn;
|
||||
}
|
||||
}
|
||||
}, ['$window'], EAGER_PUBLISHED);
|
||||
}, ['$window'], EAGER);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
@@ -406,7 +391,67 @@ angularServiceInject('$exceptionHandler', function($log){
|
||||
return function(e) {
|
||||
$log.error(e);
|
||||
};
|
||||
}, ['$log'], EAGER_PUBLISHED);
|
||||
}, ['$log'], EAGER);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$updateView
|
||||
* @requires $browser
|
||||
*
|
||||
* @description
|
||||
* Calling `$updateView` enqueues the eventual update of the view. (Update the DOM to reflect the
|
||||
* model). The update is eventual, since there are often multiple updates to the model which may
|
||||
* be deferred. The default update delayed is 25 ms. This means that the view lags the model by
|
||||
* that time. (25ms is small enough that it is perceived as instantaneous by the user). The delay
|
||||
* can be adjusted by setting the delay property of the service.
|
||||
*
|
||||
* <pre>angular.service('$updateView').delay = 10</pre>
|
||||
*
|
||||
* The delay is there so that multiple updates to the model which occur sufficiently close
|
||||
* together can be merged into a single update.
|
||||
*
|
||||
* You don't usually call '$updateView' directly since angular does it for you in most cases,
|
||||
* but there are some cases when you need to call it.
|
||||
*
|
||||
* - `$updateView()` called automatically by angular:
|
||||
* - Your Application Controllers: Your controller code is called by angular and hence
|
||||
* angular is aware that you may have changed the model.
|
||||
* - Your Services: Your service is usually called by your controller code, hence same rules
|
||||
* apply.
|
||||
* - May need to call `$updateView()` manually:
|
||||
* - Widgets / Directives: If you listen to any DOM events or events on any third party
|
||||
* libraries, then angular is not aware that you may have changed state state of the
|
||||
* model, and hence you need to call '$updateView()' manually.
|
||||
* - 'setTimeout'/'XHR': If you call 'setTimeout' (instead of {@link angular.service.$defer})
|
||||
* 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
|
||||
* {@link scope.$eval} at any time from your code:
|
||||
* <pre>scope.$root.$eval()</pre>
|
||||
*
|
||||
* In unit-test mode the update is instantaneous and synchronous to simplify writing tests.
|
||||
*
|
||||
*/
|
||||
|
||||
function serviceUpdateViewFactory($browser){
|
||||
var rootScope = this;
|
||||
var scheduled;
|
||||
function update(){
|
||||
scheduled = false;
|
||||
rootScope.$eval();
|
||||
}
|
||||
return $browser.isMock ? update : function(){
|
||||
if (!scheduled) {
|
||||
scheduled = true;
|
||||
$browser.defer(update, serviceUpdateViewFactory.delay);
|
||||
}
|
||||
};
|
||||
}
|
||||
serviceUpdateViewFactory.delay = 25;
|
||||
|
||||
angularServiceInject('$updateView', serviceUpdateViewFactory, ['$browser']);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
@@ -499,7 +544,7 @@ angularServiceInject("$invalidWidgets", function(){
|
||||
/** Return count of all invalid widgets that are currently visible */
|
||||
invalidWidgets.visible = function() {
|
||||
var count = 0;
|
||||
foreach(invalidWidgets, function(widget){
|
||||
forEach(invalidWidgets, function(widget){
|
||||
count = count + (isVisible(widget) ? 1 : 0);
|
||||
});
|
||||
return count;
|
||||
@@ -531,7 +576,7 @@ angularServiceInject("$invalidWidgets", function(){
|
||||
}
|
||||
|
||||
return invalidWidgets;
|
||||
}, [], EAGER_PUBLISHED);
|
||||
}, [], EAGER);
|
||||
|
||||
|
||||
|
||||
@@ -539,7 +584,7 @@ function switchRouteMatcher(on, when, dstName) {
|
||||
var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$',
|
||||
params = [],
|
||||
dst = {};
|
||||
foreach(when.split(/\W/), function(param){
|
||||
forEach(when.split(/\W/), function(param){
|
||||
if (param) {
|
||||
var paramRegExp = new RegExp(":" + param + "([\\W])");
|
||||
if (regex.match(paramRegExp)) {
|
||||
@@ -550,7 +595,7 @@ function switchRouteMatcher(on, when, dstName) {
|
||||
});
|
||||
var match = on.match(new RegExp(regex));
|
||||
if (match) {
|
||||
foreach(params, function(name, index){
|
||||
forEach(params, function(name, index){
|
||||
dst[name] = match[index + 1];
|
||||
});
|
||||
if (dstName) this.$set(dstName, dst);
|
||||
@@ -659,7 +704,7 @@ angularServiceInject('$route', function(location) {
|
||||
function updateRoute(){
|
||||
var childScope;
|
||||
$route.current = _null;
|
||||
angular.foreach(routes, function(routeParams, route) {
|
||||
angular.forEach(routes, function(routeParams, route) {
|
||||
if (!childScope) {
|
||||
var pathParams = matcher(location.hashPath, route);
|
||||
if (pathParams) {
|
||||
@@ -671,14 +716,14 @@ angularServiceInject('$route', function(location) {
|
||||
}
|
||||
}
|
||||
});
|
||||
angular.foreach(onChange, parentScope.$tryEval);
|
||||
angular.forEach(onChange, parentScope.$tryEval);
|
||||
if (childScope) {
|
||||
childScope.$become($route.current.controller);
|
||||
}
|
||||
}
|
||||
this.$watch(function(){return dirty + location.hash;}, updateRoute);
|
||||
return $route;
|
||||
}, ['$location'], EAGER_PUBLISHED);
|
||||
}, ['$location']);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
@@ -705,7 +750,7 @@ angularServiceInject('$xhr', function($browser, $error, $log){
|
||||
$browser.xhr(method, url, post, function(code, response){
|
||||
try {
|
||||
if (isString(response) && /^\s*[\[\{]/.exec(response) && /[\}\]]\s*$/.exec(response)) {
|
||||
response = fromJson(response);
|
||||
response = fromJson(response, true);
|
||||
}
|
||||
if (code == 200) {
|
||||
callback(code, response);
|
||||
@@ -760,7 +805,7 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
|
||||
post = _null;
|
||||
}
|
||||
var currentQueue;
|
||||
foreach(bulkXHR.urls, function(queue){
|
||||
forEach(bulkXHR.urls, function(queue){
|
||||
if (isFunction(queue.match) ? queue.match(url) : queue.match.exec(url)) {
|
||||
currentQueue = queue;
|
||||
}
|
||||
@@ -774,13 +819,13 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
|
||||
}
|
||||
bulkXHR.urls = {};
|
||||
bulkXHR.flush = function(callback){
|
||||
foreach(bulkXHR.urls, function(queue, url){
|
||||
forEach(bulkXHR.urls, function(queue, url){
|
||||
var currentRequests = queue.requests;
|
||||
if (currentRequests && currentRequests.length) {
|
||||
queue.requests = [];
|
||||
queue.callbacks = [];
|
||||
$xhr('POST', url, {requests:currentRequests}, function(code, response){
|
||||
foreach(response, function(response, i){
|
||||
forEach(response, function(response, i){
|
||||
try {
|
||||
if (response.status == 200) {
|
||||
(currentRequests[i].callback || noop)(response.status, response.response);
|
||||
@@ -818,7 +863,7 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
|
||||
*
|
||||
* @param {function()} fn A function, who's execution should be deferred.
|
||||
*/
|
||||
angularServiceInject('$defer', function($browser, $exceptionHandler) {
|
||||
angularServiceInject('$defer', function($browser, $exceptionHandler, $updateView) {
|
||||
var scope = this;
|
||||
|
||||
return function(fn) {
|
||||
@@ -828,11 +873,11 @@ angularServiceInject('$defer', function($browser, $exceptionHandler) {
|
||||
} catch(e) {
|
||||
$exceptionHandler(e);
|
||||
} finally {
|
||||
scope.$eval();
|
||||
$updateView();
|
||||
}
|
||||
});
|
||||
};
|
||||
}, ['$browser', '$exceptionHandler']);
|
||||
}, ['$browser', '$exceptionHandler', '$updateView']);
|
||||
|
||||
|
||||
/**
|
||||
@@ -845,7 +890,7 @@ angularServiceInject('$defer', function($browser, $exceptionHandler) {
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject('$xhr.cache', function($xhr, $defer){
|
||||
angularServiceInject('$xhr.cache', function($xhr, $defer, $log){
|
||||
var inflight = {}, self = this;
|
||||
function cache(method, url, post, callback, verifyCache){
|
||||
if (isFunction(post)) {
|
||||
@@ -869,11 +914,11 @@ angularServiceInject('$xhr.cache', function($xhr, $defer){
|
||||
cache.data[url] = { value: response };
|
||||
var callbacks = inflight[url].callbacks;
|
||||
delete inflight[url];
|
||||
foreach(callbacks, function(callback){
|
||||
forEach(callbacks, function(callback){
|
||||
try {
|
||||
(callback||noop)(status, copy(response));
|
||||
} catch(e) {
|
||||
self.$log.error(e);
|
||||
$log.error(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -887,7 +932,7 @@ angularServiceInject('$xhr.cache', function($xhr, $defer){
|
||||
cache.data = {};
|
||||
cache.delegate = $xhr;
|
||||
return cache;
|
||||
}, ['$xhr.bulk', '$defer']);
|
||||
}, ['$xhr.bulk', '$defer', '$log']);
|
||||
|
||||
|
||||
/**
|
||||
@@ -1124,7 +1169,7 @@ angularServiceInject('$cookies', function($browser) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}, ['$browser'], EAGER_PUBLISHED);
|
||||
}, ['$browser']);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
|
||||
+25
-13
@@ -9,13 +9,19 @@ extend(angularValidator, {
|
||||
* Use regexp validator to restrict the input to any Regular Expression.
|
||||
*
|
||||
* @param {string} value value to validate
|
||||
* @param {regexp} expression regular expression.
|
||||
* @param {string|regexp} expression regular expression.
|
||||
* @param {string=} msg error message to display.
|
||||
* @css ng-validation-error
|
||||
*
|
||||
* @example
|
||||
* <script> var ssn = /^\d\d\d-\d\d-\d\d\d\d$/; </script>
|
||||
* <script> function Cntl(){
|
||||
* this.ssnRegExp = /^\d\d\d-\d\d-\d\d\d\d$/;
|
||||
* }
|
||||
* </script>
|
||||
* Enter valid SSN:
|
||||
* <input name="ssn" value="123-45-6789" ng:validate="regexp:$window.ssn" >
|
||||
* <div ng:controller="Cntl">
|
||||
* <input name="ssn" value="123-45-6789" ng:validate="regexp:ssnRegExp" >
|
||||
* </div>
|
||||
*
|
||||
* @scenario
|
||||
* it('should invalidate non ssn', function(){
|
||||
@@ -333,14 +339,18 @@ extend(angularValidator, {
|
||||
*
|
||||
* @example
|
||||
* <script>
|
||||
* function myValidator(inputToValidate, validationDone) {
|
||||
* setTimeout(function(){
|
||||
* validationDone(inputToValidate.length % 2);
|
||||
* }, 500);
|
||||
* function MyCntl(){
|
||||
* this.myValidator = function (inputToValidate, validationDone) {
|
||||
* setTimeout(function(){
|
||||
* validationDone(inputToValidate.length % 2);
|
||||
* }, 500);
|
||||
* }
|
||||
* }
|
||||
* </script>
|
||||
* This input is validated asynchronously:
|
||||
* <input name="text" ng:validate="asynchronous:$window.myValidator">
|
||||
* <div ng:controller="MyCntl">
|
||||
* <input name="text" ng:validate="asynchronous:myValidator">
|
||||
* </div>
|
||||
*
|
||||
* @scenario
|
||||
* it('should change color in delayed way', function(){
|
||||
@@ -382,10 +392,12 @@ extend(angularValidator, {
|
||||
|
||||
cache.current = input;
|
||||
|
||||
var inputState = cache.inputs[input];
|
||||
var inputState = cache.inputs[input],
|
||||
$invalidWidgets = scope.$service('$invalidWidgets');
|
||||
|
||||
if (!inputState) {
|
||||
cache.inputs[input] = inputState = { inFlight: true };
|
||||
scope.$invalidWidgets.markInvalid(scope.$element);
|
||||
$invalidWidgets.markInvalid(scope.$element);
|
||||
element.addClass('ng-input-indicator-wait');
|
||||
asynchronousFn(input, function(error, data) {
|
||||
inputState.response = data;
|
||||
@@ -393,14 +405,14 @@ extend(angularValidator, {
|
||||
inputState.inFlight = false;
|
||||
if (cache.current == input) {
|
||||
element.removeClass('ng-input-indicator-wait');
|
||||
scope.$invalidWidgets.markValid(element);
|
||||
$invalidWidgets.markValid(element);
|
||||
}
|
||||
element.data($$validate)();
|
||||
scope.$root.$eval();
|
||||
scope.$service('$updateView')();
|
||||
});
|
||||
} else if (inputState.inFlight) {
|
||||
// request in flight, mark widget invalid, but don't show it to user
|
||||
scope.$invalidWidgets.markInvalid(scope.$element);
|
||||
$invalidWidgets.markInvalid(scope.$element);
|
||||
} else {
|
||||
(updateFn||noop)(inputState.response);
|
||||
}
|
||||
|
||||
+53
-44
@@ -134,17 +134,18 @@
|
||||
|
||||
function modelAccessor(scope, element) {
|
||||
var expr = element.attr('name');
|
||||
if (!expr) throw "Required field 'name' not found.";
|
||||
return {
|
||||
get: function() {
|
||||
return scope.$eval(expr);
|
||||
},
|
||||
set: function(value) {
|
||||
if (value !== _undefined) {
|
||||
return scope.$tryEval(expr + '=' + toJson(value), element);
|
||||
if (expr) {
|
||||
return {
|
||||
get: function() {
|
||||
return scope.$eval(expr);
|
||||
},
|
||||
set: function(value) {
|
||||
if (value !== _undefined) {
|
||||
return scope.$tryEval(expr + '=' + toJson(value), element);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function modelFormattedAccessor(scope, element) {
|
||||
@@ -152,14 +153,16 @@ function modelFormattedAccessor(scope, element) {
|
||||
formatterName = element.attr('ng:format') || NOOP,
|
||||
formatter = angularFormatter(formatterName);
|
||||
if (!formatter) throw "Formatter named '" + formatterName + "' not found.";
|
||||
return {
|
||||
get: function() {
|
||||
return formatter.format(accessor.get());
|
||||
},
|
||||
set: function(value) {
|
||||
return accessor.set(formatter.parse(value));
|
||||
}
|
||||
};
|
||||
if (accessor) {
|
||||
return {
|
||||
get: function() {
|
||||
return formatter.format(accessor.get());
|
||||
},
|
||||
set: function(value) {
|
||||
return accessor.set(formatter.parse(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function compileValidator(expr) {
|
||||
@@ -268,7 +271,7 @@ function valueAccessor(scope, element) {
|
||||
formatterName = element.attr('ng:format') || NOOP,
|
||||
formatter = angularFormatter(formatterName),
|
||||
format, parse, lastError, required,
|
||||
invalidWidgets = scope.$invalidWidgets || {markValid:noop, markInvalid:noop};
|
||||
invalidWidgets = scope.$service('$invalidWidgets') || {markValid:noop, markInvalid:noop};
|
||||
if (!validator) throw "Validator named '" + validatorName + "' not found.";
|
||||
if (!formatter) throw "Formatter named '" + formatterName + "' not found.";
|
||||
format = formatter.format;
|
||||
@@ -356,15 +359,15 @@ function optionsAccessor(scope, element) {
|
||||
return {
|
||||
get: function(){
|
||||
var values = [];
|
||||
foreach(options, function(option){
|
||||
forEach(options, function(option){
|
||||
if (option.selected) values.push(option.value);
|
||||
});
|
||||
return values;
|
||||
},
|
||||
set: function(values){
|
||||
var keys = {};
|
||||
foreach(values, function(value){ keys[value] = true; });
|
||||
foreach(options, function(option){
|
||||
forEach(values, function(value){ keys[value] = true; });
|
||||
forEach(options, function(option){
|
||||
option.selected = keys[option.value];
|
||||
});
|
||||
}
|
||||
@@ -373,7 +376,7 @@ function optionsAccessor(scope, element) {
|
||||
|
||||
function noopAccessor() { return { get: noop, set: noop }; }
|
||||
|
||||
var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initWidgetValue()),
|
||||
var textWidget = inputWidget('keydown change', modelAccessor, valueAccessor, initWidgetValue(), true),
|
||||
buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop),
|
||||
INPUT_TYPE = {
|
||||
'text': textWidget,
|
||||
@@ -451,30 +454,35 @@ function radioInit(model, view, element) {
|
||||
expect(binding('checkboxCount')).toBe('1');
|
||||
});
|
||||
*/
|
||||
function inputWidget(events, modelAccessor, viewAccessor, initFn) {
|
||||
return function(element) {
|
||||
function inputWidget(events, modelAccessor, viewAccessor, initFn, textBox) {
|
||||
return injectService(['$updateView', '$defer'], function($updateView, $defer, element) {
|
||||
var scope = this,
|
||||
model = modelAccessor(scope, element),
|
||||
view = viewAccessor(scope, element),
|
||||
action = element.attr('ng:change') || '',
|
||||
lastValue;
|
||||
initFn.call(scope, model, view, element);
|
||||
this.$eval(element.attr('ng:init')||'');
|
||||
// Don't register a handler if we are a button (noopAccessor) and there is no action
|
||||
if (action || modelAccessor !== noopAccessor) {
|
||||
element.bind(events, function (){
|
||||
model.set(view.get());
|
||||
lastValue = model.get();
|
||||
scope.$tryEval(action, element);
|
||||
scope.$root.$eval();
|
||||
if (model) {
|
||||
initFn.call(scope, model, view, element);
|
||||
this.$eval(element.attr('ng:init')||'');
|
||||
element.bind(events, function(event){
|
||||
function handler(){
|
||||
var value = view.get();
|
||||
if (!textBox || value != lastValue) {
|
||||
model.set(value);
|
||||
lastValue = model.get();
|
||||
scope.$tryEval(action, element);
|
||||
$updateView();
|
||||
}
|
||||
}
|
||||
event.type == 'keydown' ? $defer(handler) : handler();
|
||||
});
|
||||
scope.$watch(model.get, function(value){
|
||||
if (lastValue !== value) {
|
||||
view.set(lastValue = value);
|
||||
}
|
||||
});
|
||||
}
|
||||
scope.$watch(model.get, function(value){
|
||||
if (lastValue !== value) {
|
||||
view.set(lastValue = value);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function inputWidgetSelector(element){
|
||||
@@ -546,7 +554,8 @@ angularWidget('option', function(){
|
||||
* (e.g. ng:include won't work for file:// access).
|
||||
*
|
||||
* @param {string} src expression evaluating to URL.
|
||||
* @param {Scope=} [scope=new_child_scope] expression evaluating to angular.scope
|
||||
* @param {Scope=} [scope=new_child_scope] optional expression which evaluates to an
|
||||
* instance of angular.scope to set the HTML fragment to.
|
||||
* @param {string=} onload Expression to evaluate when a new partial is loaded.
|
||||
*
|
||||
* @example
|
||||
@@ -690,7 +699,7 @@ var ngSwitch = angularWidget('ng:switch', function (element){
|
||||
if (isString(when)) {
|
||||
switchCase.when = function(scope, value){
|
||||
var args = [value, when];
|
||||
foreach(usingExprParams, function(arg){
|
||||
forEach(usingExprParams, function(arg){
|
||||
args.push(arg);
|
||||
});
|
||||
return usingFn.apply(scope, args);
|
||||
@@ -703,7 +712,7 @@ var ngSwitch = angularWidget('ng:switch', function (element){
|
||||
});
|
||||
|
||||
// this needs to be here for IE
|
||||
foreach(cases, function(_case){
|
||||
forEach(cases, function(_case){
|
||||
_case.element.remove();
|
||||
});
|
||||
|
||||
@@ -714,7 +723,7 @@ var ngSwitch = angularWidget('ng:switch', function (element){
|
||||
var found = false;
|
||||
element.html('');
|
||||
childScope = createScope(scope);
|
||||
foreach(cases, function(switchCase){
|
||||
forEach(cases, function(switchCase){
|
||||
if (!found && switchCase.when(childScope, value)) {
|
||||
found = true;
|
||||
var caseElement = quickClone(switchCase.element);
|
||||
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
tests=$1
|
||||
norecompile=$2
|
||||
|
||||
if [[ $tests = "" ]]; then
|
||||
tests="all"
|
||||
fi
|
||||
|
||||
if [[ $norecompile = "" ]]; then
|
||||
rake compile
|
||||
fi
|
||||
|
||||
java -jar lib/jstestdriver/JsTestDriver.jar --tests "$tests" --config jsTestDriver-perf.conf
|
||||
+30
-38
@@ -2,18 +2,6 @@ beforeEach(function(){
|
||||
compileCache = {};
|
||||
});
|
||||
|
||||
describe('Angular', function(){
|
||||
xit('should fire on updateEvents', function(){
|
||||
var onUpdateView = jasmine.createSpy();
|
||||
var scope = angular.compile("<div></div>", { onUpdateView: onUpdateView });
|
||||
expect(onUpdateView).wasNotCalled();
|
||||
scope.$init();
|
||||
scope.$eval();
|
||||
expect(onUpdateView).wasCalled();
|
||||
dealoc(scope);
|
||||
});
|
||||
});
|
||||
|
||||
describe('case', function(){
|
||||
it('should change case', function(){
|
||||
expect(lowercase('ABC90')).toEqual('abc90');
|
||||
@@ -101,6 +89,28 @@ describe('equals', function(){
|
||||
it('should ignore functions', function(){
|
||||
expect(equals({func: function() {}}, {bar: function() {}})).toEqual(true);
|
||||
});
|
||||
|
||||
it('should work well with nulls', function() {
|
||||
expect(equals(null, '123')).toBe(false);
|
||||
expect(equals('123', null)).toBe(false);
|
||||
|
||||
var obj = {foo:'bar'};
|
||||
expect(equals(null, obj)).toBe(false);
|
||||
expect(equals(obj, null)).toBe(false);
|
||||
|
||||
expect(equals(null, null)).toBe(true);
|
||||
});
|
||||
|
||||
it('should work well with undefined', function() {
|
||||
expect(equals(undefined, '123')).toBe(false);
|
||||
expect(equals('123', undefined)).toBe(false);
|
||||
|
||||
var obj = {foo:'bar'};
|
||||
expect(equals(undefined, obj)).toBe(false);
|
||||
expect(equals(obj, undefined)).toBe(false);
|
||||
|
||||
expect(equals(undefined, undefined)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseKeyValue', function() {
|
||||
@@ -296,25 +306,6 @@ describe('angularJsConfig', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('extensionMap', function() {
|
||||
it('should preserve $ properties on override', function() {
|
||||
var extension = extensionMap({}, 'fake');
|
||||
extension('first', {$one: true, $two: true});
|
||||
var result = extension('first', {$one: false, $three: true});
|
||||
|
||||
expect(result.$one).toBeFalsy();
|
||||
expect(result.$two).toBeTruthy();
|
||||
expect(result.$three).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not preserve non-angular properties', function() {
|
||||
var extension = extensionMap({}, 'fake');
|
||||
extension('first', {two: true});
|
||||
var result = extension('first', {$one: false, $three: true});
|
||||
|
||||
expect(result.two).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('angular service', function() {
|
||||
it('should override services', function() {
|
||||
@@ -322,16 +313,17 @@ describe('angular service', function() {
|
||||
angular.service('fake', function() { return 'old'; });
|
||||
angular.service('fake', function() { return 'new'; });
|
||||
|
||||
expect(scope.$inject('fake')).toEqual('new');
|
||||
expect(scope.$service('fake')).toEqual('new');
|
||||
});
|
||||
|
||||
it('should preserve $ properties on override', function() {
|
||||
angular.service('fake', {$one: true}, {$two: true});
|
||||
var result = angular.service('fake', {$third: true});
|
||||
it('should not preserve properties on override', function() {
|
||||
angular.service('fake', {$one: true}, {$two: true}, {three: true});
|
||||
var result = angular.service('fake', {$four: true});
|
||||
|
||||
expect(result.$one).toBeTruthy();
|
||||
expect(result.$two).toBeTruthy();
|
||||
expect(result.$third).toBeTruthy();
|
||||
expect(result.$one).toBeUndefined();
|
||||
expect(result.$two).toBeUndefined();
|
||||
expect(result.three).toBeUndefined();
|
||||
expect(result.$four).toBe(true);
|
||||
});
|
||||
|
||||
it('should not preserve non-angular properties on override', function() {
|
||||
|
||||
@@ -0,0 +1,668 @@
|
||||
describe('Binder', function(){
|
||||
|
||||
beforeEach(function(){
|
||||
var self = this;
|
||||
|
||||
this.compile = function(html, initialScope, parent) {
|
||||
var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
|
||||
if (self.element) dealoc(self.element);
|
||||
var element = self.element = jqLite(html);
|
||||
var scope = compiler.compile(element)(element);
|
||||
|
||||
if (parent) parent.append(element);
|
||||
|
||||
extend(scope, initialScope);
|
||||
scope.$init();
|
||||
return {node:element, scope:scope};
|
||||
};
|
||||
this.compileToHtml = function (content) {
|
||||
return sortedHtml(this.compile(content).node);
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
if (this.element && this.element.dealoc) {
|
||||
this.element.dealoc();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('ChangingTextfieldUpdatesModel', function(){
|
||||
var state = this.compile('<input type="text" name="model.price" value="abc">', {model:{}});
|
||||
state.scope.$eval();
|
||||
assertEquals('abc', state.scope.model.price);
|
||||
});
|
||||
|
||||
it('ChangingTextareaUpdatesModel', function(){
|
||||
var c = this.compile('<textarea name="model.note">abc</textarea>');
|
||||
c.scope.$eval();
|
||||
assertEquals(c.scope.model.note, 'abc');
|
||||
});
|
||||
|
||||
it('ChangingRadioUpdatesModel', function(){
|
||||
var c = this.compile('<input type="radio" name="model.price" value="A" checked>' +
|
||||
'<input type="radio" name="model.price" value="B">');
|
||||
c.scope.$eval();
|
||||
assertEquals(c.scope.model.price, 'A');
|
||||
});
|
||||
|
||||
it('ChangingCheckboxUpdatesModel', function(){
|
||||
var form = this.compile('<input type="checkbox" name="model.price" value="true" checked ng:format="boolean"/>');
|
||||
assertEquals(true, form.scope.model.price);
|
||||
});
|
||||
|
||||
it('BindUpdate', function(){
|
||||
var c = this.compile('<div ng:eval="a=123"/>');
|
||||
assertEquals(123, c.scope.$get('a'));
|
||||
});
|
||||
|
||||
it('ChangingSelectNonSelectedUpdatesModel', function(){
|
||||
var form = this.compile('<select name="model.price"><option value="A">A</option><option value="B">B</option></select>');
|
||||
assertEquals('A', form.scope.model.price);
|
||||
});
|
||||
|
||||
it('ChangingMultiselectUpdatesModel', function(){
|
||||
var form = this.compile('<select name="Invoice.options" multiple="multiple">' +
|
||||
'<option value="A" selected>Gift wrap</option>' +
|
||||
'<option value="B" selected>Extra padding</option>' +
|
||||
'<option value="C">Expedite</option>' +
|
||||
'</select>');
|
||||
assertJsonEquals(["A", "B"], form.scope.$get('Invoice').options);
|
||||
});
|
||||
|
||||
it('ChangingSelectSelectedUpdatesModel', function(){
|
||||
var form = this.compile('<select name="model.price"><option>A</option><option selected value="b">B</option></select>');
|
||||
assertEquals(form.scope.model.price, 'b');
|
||||
});
|
||||
|
||||
it('ExecuteInitialization', function(){
|
||||
var c = this.compile('<div ng:init="a=123">');
|
||||
assertEquals(c.scope.$get('a'), 123);
|
||||
});
|
||||
|
||||
it('ExecuteInitializationStatements', function(){
|
||||
var c = this.compile('<div ng:init="a=123;b=345">');
|
||||
assertEquals(c.scope.$get('a'), 123);
|
||||
assertEquals(c.scope.$get('b'), 345);
|
||||
});
|
||||
|
||||
it('ApplyTextBindings', function(){
|
||||
var form = this.compile('<div ng:bind="model.a">x</div>');
|
||||
form.scope.$set('model', {a:123});
|
||||
form.scope.$eval();
|
||||
assertEquals('123', form.node.text());
|
||||
});
|
||||
|
||||
it('ReplaceBindingInTextWithSpan', function(){
|
||||
assertEquals(this.compileToHtml("<b>a{{b}}c</b>"), '<b>a<span ng:bind="b"></span>c</b>');
|
||||
assertEquals(this.compileToHtml("<b>{{b}}</b>"), '<b><span ng:bind="b"></span></b>');
|
||||
});
|
||||
|
||||
it('BindingSpaceConfusesIE', function(){
|
||||
if (!msie) return;
|
||||
var span = document.createElement("span");
|
||||
span.innerHTML = ' ';
|
||||
var nbsp = span.firstChild.nodeValue;
|
||||
assertEquals(
|
||||
'<b><span ng:bind="a"></span><span>'+nbsp+'</span><span ng:bind="b"></span></b>',
|
||||
this.compileToHtml("<b>{{a}} {{b}}</b>"));
|
||||
assertEquals(
|
||||
'<b><span ng:bind="A"></span><span>'+nbsp+'x </span><span ng:bind="B"></span><span>'+nbsp+'(</span><span ng:bind="C"></span>)</b>',
|
||||
this.compileToHtml("<b>{{A}} x {{B}} ({{C}})</b>"));
|
||||
});
|
||||
|
||||
it('BindingOfAttributes', function(){
|
||||
var c = this.compile("<a href='http://s/a{{b}}c' foo='x'></a>");
|
||||
var attrbinding = c.node.attr("ng:bind-attr");
|
||||
var bindings = fromJson(attrbinding);
|
||||
assertEquals("http://s/a{{b}}c", decodeURI(bindings.href));
|
||||
assertTrue(!bindings.foo);
|
||||
});
|
||||
|
||||
it('MarkMultipleAttributes', function(){
|
||||
var c = this.compile('<a href="http://s/a{{b}}c" foo="{{d}}"></a>');
|
||||
var attrbinding = c.node.attr("ng:bind-attr");
|
||||
var bindings = fromJson(attrbinding);
|
||||
assertEquals(bindings.foo, "{{d}}");
|
||||
assertEquals(decodeURI(bindings.href), "http://s/a{{b}}c");
|
||||
});
|
||||
|
||||
it('AttributesNoneBound', function(){
|
||||
var c = this.compile("<a href='abc' foo='def'></a>");
|
||||
var a = c.node;
|
||||
assertEquals(a[0].nodeName, "A");
|
||||
assertTrue(!a.attr("ng:bind-attr"));
|
||||
});
|
||||
|
||||
it('ExistingAttrbindingIsAppended', function(){
|
||||
var c = this.compile("<a href='http://s/{{abc}}' ng:bind-attr='{\"b\":\"{{def}}\"}'></a>");
|
||||
var a = c.node;
|
||||
assertEquals('{"b":"{{def}}","href":"http://s/{{abc}}"}', a.attr('ng:bind-attr'));
|
||||
});
|
||||
|
||||
it('AttributesAreEvaluated', function(){
|
||||
var c = this.compile('<a ng:bind-attr=\'{"a":"a", "b":"a+b={{a+b}}"}\'></a>');
|
||||
var binder = c.binder, form = c.node;
|
||||
c.scope.$eval('a=1;b=2');
|
||||
c.scope.$eval();
|
||||
var a = c.node;
|
||||
assertEquals(a.attr('a'), 'a');
|
||||
assertEquals(a.attr('b'), 'a+b=3');
|
||||
});
|
||||
|
||||
it('InputTypeButtonActionExecutesInScope', function(){
|
||||
var savedCalled = false;
|
||||
var c = this.compile('<input type="button" ng:click="person.save()" value="Apply">');
|
||||
c.scope.$set("person.save", function(){
|
||||
savedCalled = true;
|
||||
});
|
||||
browserTrigger(c.node, 'click');
|
||||
assertTrue(savedCalled);
|
||||
});
|
||||
|
||||
it('InputTypeButtonActionExecutesInScope2', function(){
|
||||
var log = "";
|
||||
var c = this.compile('<input type="image" ng:click="action()">');
|
||||
c.scope.$set("action", function(){
|
||||
log += 'click;';
|
||||
});
|
||||
expect(log).toEqual('');
|
||||
browserTrigger(c.node, 'click');
|
||||
expect(log).toEqual('click;');
|
||||
});
|
||||
|
||||
it('ButtonElementActionExecutesInScope', function(){
|
||||
var savedCalled = false;
|
||||
var c = this.compile('<button ng:click="person.save()">Apply</button>');
|
||||
c.scope.$set("person.save", function(){
|
||||
savedCalled = true;
|
||||
});
|
||||
browserTrigger(c.node, 'click');
|
||||
assertTrue(savedCalled);
|
||||
});
|
||||
|
||||
it('RepeaterUpdateBindings', function(){
|
||||
var a = this.compile('<ul><LI ng:repeat="item in model.items" ng:bind="item.a"/></ul>');
|
||||
var form = a.node;
|
||||
var items = [{a:"A"}, {a:"B"}];
|
||||
a.scope.$set('model', {items:items});
|
||||
|
||||
a.scope.$eval();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="0">A</li>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="1">B</li>' +
|
||||
'</ul>', sortedHtml(form));
|
||||
|
||||
items.unshift({a:'C'});
|
||||
a.scope.$eval();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="0">C</li>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="1">A</li>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="2">B</li>' +
|
||||
'</ul>', sortedHtml(form));
|
||||
|
||||
items.shift();
|
||||
a.scope.$eval();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="0">A</li>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="1">B</li>' +
|
||||
'</ul>', sortedHtml(form));
|
||||
|
||||
items.shift();
|
||||
items.shift();
|
||||
a.scope.$eval();
|
||||
});
|
||||
|
||||
it('RepeaterContentDoesNotBind', function(){
|
||||
var a = this.compile('<ul><LI ng:repeat="item in model.items"><span ng:bind="item.a"></span></li></ul>');
|
||||
a.scope.$set('model', {items:[{a:"A"}]});
|
||||
a.scope.$eval();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:repeat-index="0"><span ng:bind="item.a">A</span></li>' +
|
||||
'</ul>', sortedHtml(a.node));
|
||||
});
|
||||
|
||||
it('ExpandEntityTag', function(){
|
||||
assertEquals(
|
||||
'<div ng-entity="Person" ng:watch="$anchor.a:1"></div>',
|
||||
this.compileToHtml('<div ng-entity="Person" ng:watch="$anchor.a:1"/>'));
|
||||
});
|
||||
|
||||
it('DoNotOverwriteCustomAction', function(){
|
||||
var html = this.compileToHtml('<input type="submit" value="Save" action="foo();">');
|
||||
assertTrue(html.indexOf('action="foo();"') > 0 );
|
||||
});
|
||||
|
||||
it('RepeaterAdd', function(){
|
||||
var c = this.compile('<div><input type="text" name="item.x" ng:repeat="item in items"></div>');
|
||||
var doc = c.node;
|
||||
c.scope.$set('items', [{x:'a'}, {x:'b'}]);
|
||||
c.scope.$eval();
|
||||
var first = childNode(c.node, 1);
|
||||
var second = childNode(c.node, 2);
|
||||
assertEquals('a', first.val());
|
||||
assertEquals('b', second.val());
|
||||
|
||||
first.val('ABC');
|
||||
browserTrigger(first, 'keydown');
|
||||
c.scope.$service('$browser').defer.flush();
|
||||
assertEquals(c.scope.items[0].x, 'ABC');
|
||||
});
|
||||
|
||||
it('ItShouldRemoveExtraChildrenWhenIteratingOverHash', function(){
|
||||
var c = this.compile('<div><div ng:repeat="i in items">{{i}}</div></div>');
|
||||
var items = {};
|
||||
c.scope.$set("items", items);
|
||||
|
||||
c.scope.$eval();
|
||||
expect(c.node[0].childNodes.length - 1).toEqual(0);
|
||||
|
||||
items.name = "misko";
|
||||
c.scope.$eval();
|
||||
expect(c.node[0].childNodes.length - 1).toEqual(1);
|
||||
|
||||
delete items.name;
|
||||
c.scope.$eval();
|
||||
expect(c.node[0].childNodes.length - 1).toEqual(0);
|
||||
});
|
||||
|
||||
it('IfTextBindingThrowsErrorDecorateTheSpan', function(){
|
||||
var a = this.compile('<div>{{error.throw()}}</div>');
|
||||
var doc = a.node;
|
||||
|
||||
a.scope.$set('error.throw', function(){throw "ErrorMsg1";});
|
||||
a.scope.$eval();
|
||||
var span = childNode(doc, 0);
|
||||
assertTrue(span.hasClass('ng-exception'));
|
||||
assertTrue(!!span.text().match(/ErrorMsg1/));
|
||||
assertTrue(!!span.attr('ng-exception').match(/ErrorMsg1/));
|
||||
|
||||
a.scope.$set('error.throw', function(){throw "MyError";});
|
||||
a.scope.$eval();
|
||||
span = childNode(doc, 0);
|
||||
assertTrue(span.hasClass('ng-exception'));
|
||||
assertTrue(span.text(), span.text().match('MyError') !== null);
|
||||
assertEquals('MyError', span.attr('ng-exception'));
|
||||
|
||||
a.scope.$set('error.throw', function(){return "ok";});
|
||||
a.scope.$eval();
|
||||
assertFalse(span.hasClass('ng-exception'));
|
||||
assertEquals('ok', span.text());
|
||||
assertEquals(null, span.attr('ng-exception'));
|
||||
});
|
||||
|
||||
it('IfAttrBindingThrowsErrorDecorateTheAttribute', function(){
|
||||
var a = this.compile('<div attr="before {{error.throw()}} after"></div>');
|
||||
var doc = a.node;
|
||||
|
||||
a.scope.$set('error.throw', function(){throw "ErrorMsg";});
|
||||
a.scope.$eval();
|
||||
assertTrue('ng-exception', doc.hasClass('ng-exception'));
|
||||
assertEquals('"ErrorMsg"', doc.attr('ng-exception'));
|
||||
assertEquals('before "ErrorMsg" after', doc.attr('attr'));
|
||||
|
||||
a.scope.$set('error.throw', function(){ return 'X';});
|
||||
a.scope.$eval();
|
||||
assertFalse('!ng-exception', doc.hasClass('ng-exception'));
|
||||
assertEquals('before X after', doc.attr('attr'));
|
||||
assertEquals(null, doc.attr('ng-exception'));
|
||||
|
||||
});
|
||||
|
||||
it('NestedRepeater', function(){
|
||||
var a = this.compile('<div><div ng:repeat="m in model" name="{{m.name}}">' +
|
||||
'<ul name="{{i}}" ng:repeat="i in m.item"></ul>' +
|
||||
'</div></div>');
|
||||
|
||||
a.scope.$set('model', [{name:'a', item:['a1', 'a2']}, {name:'b', item:['b1', 'b2']}]);
|
||||
a.scope.$eval();
|
||||
|
||||
assertEquals('<div>'+
|
||||
'<#comment></#comment>'+
|
||||
'<div name="a" ng:bind-attr="{"name":"{{m.name}}"}" ng:repeat-index="0">'+
|
||||
'<#comment></#comment>'+
|
||||
'<ul name="a1" ng:bind-attr="{"name":"{{i}}"}" ng:repeat-index="0"></ul>'+
|
||||
'<ul name="a2" ng:bind-attr="{"name":"{{i}}"}" ng:repeat-index="1"></ul>'+
|
||||
'</div>'+
|
||||
'<div name="b" ng:bind-attr="{"name":"{{m.name}}"}" ng:repeat-index="1">'+
|
||||
'<#comment></#comment>'+
|
||||
'<ul name="b1" ng:bind-attr="{"name":"{{i}}"}" ng:repeat-index="0"></ul>'+
|
||||
'<ul name="b2" ng:bind-attr="{"name":"{{i}}"}" ng:repeat-index="1"></ul>'+
|
||||
'</div></div>', sortedHtml(a.node));
|
||||
});
|
||||
|
||||
it('HideBindingExpression', function(){
|
||||
var a = this.compile('<div ng:hide="hidden == 3"/>');
|
||||
|
||||
a.scope.$set('hidden', 3);
|
||||
a.scope.$eval();
|
||||
|
||||
assertHidden(a.node);
|
||||
|
||||
a.scope.$set('hidden', 2);
|
||||
a.scope.$eval();
|
||||
|
||||
assertVisible(a.node);
|
||||
});
|
||||
|
||||
it('HideBinding', function(){
|
||||
var c = this.compile('<div ng:hide="hidden"/>');
|
||||
|
||||
c.scope.$set('hidden', 'true');
|
||||
c.scope.$eval();
|
||||
|
||||
assertHidden(c.node);
|
||||
|
||||
c.scope.$set('hidden', 'false');
|
||||
c.scope.$eval();
|
||||
|
||||
assertVisible(c.node);
|
||||
|
||||
c.scope.$set('hidden', '');
|
||||
c.scope.$eval();
|
||||
|
||||
assertVisible(c.node);
|
||||
});
|
||||
|
||||
it('ShowBinding', function(){
|
||||
var c = this.compile('<div ng:show="show"/>');
|
||||
|
||||
c.scope.$set('show', 'true');
|
||||
c.scope.$eval();
|
||||
|
||||
assertVisible(c.node);
|
||||
|
||||
c.scope.$set('show', 'false');
|
||||
c.scope.$eval();
|
||||
|
||||
assertHidden(c.node);
|
||||
|
||||
c.scope.$set('show', '');
|
||||
c.scope.$eval();
|
||||
|
||||
assertHidden(c.node);
|
||||
});
|
||||
|
||||
it('BindClassUndefined', function(){
|
||||
var doc = this.compile('<div ng:class="undefined"/>');
|
||||
doc.scope.$eval();
|
||||
|
||||
assertEquals(
|
||||
'<div class="undefined" ng:class="undefined"></div>',
|
||||
sortedHtml(doc.node));
|
||||
});
|
||||
|
||||
it('BindClass', function(){
|
||||
var c = this.compile('<div ng:class="class"/>');
|
||||
|
||||
c.scope.$set('class', 'testClass');
|
||||
c.scope.$eval();
|
||||
|
||||
assertEquals('<div class="testClass" ng:class="class"></div>', sortedHtml(c.node));
|
||||
|
||||
c.scope.$set('class', ['a', 'b']);
|
||||
c.scope.$eval();
|
||||
|
||||
assertEquals('<div class="a b" ng:class="class"></div>', sortedHtml(c.node));
|
||||
});
|
||||
|
||||
it('BindClassEvenOdd', function(){
|
||||
var x = this.compile('<div><div ng:repeat="i in [0,1]" ng:class-even="\'e\'" ng:class-odd="\'o\'"/></div>');
|
||||
x.scope.$eval();
|
||||
var d1 = jqLite(x.node[0].childNodes[1]);
|
||||
var d2 = jqLite(x.node[0].childNodes[2]);
|
||||
expect(d1.hasClass('o')).toBeTruthy();
|
||||
expect(d2.hasClass('e')).toBeTruthy();
|
||||
assertEquals(
|
||||
'<div><#comment></#comment>' +
|
||||
'<div class="o" ng:class-even="\'e\'" ng:class-odd="\'o\'" ng:repeat-index="0"></div>' +
|
||||
'<div class="e" ng:class-even="\'e\'" ng:class-odd="\'o\'" ng:repeat-index="1"></div></div>',
|
||||
sortedHtml(x.node));
|
||||
});
|
||||
|
||||
it('BindStyle', function(){
|
||||
var c = this.compile('<div ng:style="style"/>');
|
||||
|
||||
c.scope.$eval('style={color:"red"}');
|
||||
c.scope.$eval();
|
||||
|
||||
assertEquals("red", c.node.css('color'));
|
||||
|
||||
c.scope.$eval('style={}');
|
||||
c.scope.$eval();
|
||||
});
|
||||
|
||||
it('ActionOnAHrefThrowsError', function(){
|
||||
var model = {books:[]};
|
||||
var c = this.compile('<a ng:click="action()">Add Phone</a>', model);
|
||||
c.scope.action = function(){
|
||||
throw new Error('MyError');
|
||||
};
|
||||
var input = c.node;
|
||||
browserTrigger(input, 'click');
|
||||
var error = input.attr('ng-exception');
|
||||
assertTrue(!!error.match(/MyError/));
|
||||
assertTrue("should have an error class", input.hasClass('ng-exception'));
|
||||
|
||||
// TODO: I think that exception should never get cleared so this portion of test makes no sense
|
||||
//c.scope.action = noop;
|
||||
//browserTrigger(input, 'click');
|
||||
//dump(input.attr('ng-error'));
|
||||
//assertFalse('error class should be cleared', input.hasClass('ng-exception'));
|
||||
});
|
||||
|
||||
it('ShoulIgnoreVbNonBindable', function(){
|
||||
var c = this.compile("<div>{{a}}" +
|
||||
"<div ng:non-bindable>{{a}}</div>" +
|
||||
"<div ng:non-bindable=''>{{b}}</div>" +
|
||||
"<div ng:non-bindable='true'>{{c}}</div></div>");
|
||||
c.scope.$set('a', 123);
|
||||
c.scope.$eval();
|
||||
assertEquals('123{{a}}{{b}}{{c}}', c.node.text());
|
||||
});
|
||||
|
||||
it('OptionShouldUpdateParentToGetProperBinding', function(){
|
||||
var c = this.compile('<select name="s"><option ng:repeat="i in [0,1]" value="{{i}}" ng:bind="i"></option></select>');
|
||||
c.scope.$set('s', 1);
|
||||
c.scope.$eval();
|
||||
assertEquals(1, c.node[0].selectedIndex);
|
||||
});
|
||||
|
||||
it('RepeaterShouldBindInputsDefaults', function () {
|
||||
var c = this.compile('<div><input value="123" name="item.name" ng:repeat="item in items"></div>');
|
||||
c.scope.$set('items', [{}, {name:'misko'}]);
|
||||
c.scope.$eval();
|
||||
|
||||
assertEquals("123", c.scope.$eval('items[0].name'));
|
||||
assertEquals("misko", c.scope.$eval('items[1].name'));
|
||||
});
|
||||
|
||||
it('ShouldTemplateBindPreElements', function () {
|
||||
var c = this.compile('<pre>Hello {{name}}!</pre>');
|
||||
c.scope.$set("name", "World");
|
||||
c.scope.$eval();
|
||||
|
||||
assertEquals('<pre ng:bind-template="Hello {{name}}!">Hello World!</pre>', sortedHtml(c.node));
|
||||
});
|
||||
|
||||
it('FillInOptionValueWhenMissing', function(){
|
||||
var c = this.compile(
|
||||
'<select><option selected="true">{{a}}</option><option value="">{{b}}</option><option>C</option></select>');
|
||||
c.scope.$set('a', 'A');
|
||||
c.scope.$set('b', 'B');
|
||||
c.scope.$eval();
|
||||
var optionA = childNode(c.node, 0);
|
||||
var optionB = childNode(c.node, 1);
|
||||
var optionC = childNode(c.node, 2);
|
||||
|
||||
expect(optionA.attr('value')).toEqual('A');
|
||||
expect(optionA.text()).toEqual('A');
|
||||
|
||||
expect(optionB.attr('value')).toEqual('');
|
||||
expect(optionB.text()).toEqual('B');
|
||||
|
||||
expect(optionC.attr('value')).toEqual('C');
|
||||
expect(optionC.text()).toEqual('C');
|
||||
});
|
||||
|
||||
it('ValidateForm', function(){
|
||||
var c = this.compile('<div><input name="name" ng:required>' +
|
||||
'<div ng:repeat="item in items"><input name="item.name" ng:required/></div></div>',
|
||||
undefined, jqLite(document.body));
|
||||
var items = [{}, {}];
|
||||
c.scope.$set("items", items);
|
||||
c.scope.$eval();
|
||||
assertEquals(3, c.scope.$service('$invalidWidgets').length);
|
||||
|
||||
c.scope.$set('name', '');
|
||||
c.scope.$eval();
|
||||
assertEquals(3, c.scope.$service('$invalidWidgets').length);
|
||||
|
||||
c.scope.$set('name', ' ');
|
||||
c.scope.$eval();
|
||||
assertEquals(3, c.scope.$service('$invalidWidgets').length);
|
||||
|
||||
c.scope.$set('name', 'abc');
|
||||
c.scope.$eval();
|
||||
assertEquals(2, c.scope.$service('$invalidWidgets').length);
|
||||
|
||||
items[0].name = 'abc';
|
||||
c.scope.$eval();
|
||||
assertEquals(1, c.scope.$service('$invalidWidgets').length);
|
||||
|
||||
items[1].name = 'abc';
|
||||
c.scope.$eval();
|
||||
assertEquals(0, c.scope.$service('$invalidWidgets').length);
|
||||
});
|
||||
|
||||
it('ValidateOnlyVisibleItems', function(){
|
||||
var c = this.compile('<div><input name="name" ng:required><input ng:show="show" name="name" ng:required></div>', undefined, jqLite(document.body));
|
||||
c.scope.$set("show", true);
|
||||
c.scope.$eval();
|
||||
assertEquals(2, c.scope.$service('$invalidWidgets').length);
|
||||
|
||||
c.scope.$set("show", false);
|
||||
c.scope.$eval();
|
||||
assertEquals(1, c.scope.$service('$invalidWidgets').visible());
|
||||
});
|
||||
|
||||
it('DeleteAttributeIfEvaluatesFalse', function(){
|
||||
var c = this.compile('<div>' +
|
||||
'<input name="a0" ng:bind-attr="{disabled:\'{{true}}\'}"><input name="a1" ng:bind-attr="{disabled:\'{{false}}\'}">' +
|
||||
'<input name="b0" ng:bind-attr="{disabled:\'{{1}}\'}"><input name="b1" ng:bind-attr="{disabled:\'{{0}}\'}">' +
|
||||
'<input name="c0" ng:bind-attr="{disabled:\'{{[0]}}\'}"><input name="c1" ng:bind-attr="{disabled:\'{{[]}}\'}"></div>');
|
||||
c.scope.$eval();
|
||||
function assertChild(index, disabled) {
|
||||
var child = childNode(c.node, index);
|
||||
assertEquals(sortedHtml(child), disabled, !!child.attr('disabled'));
|
||||
}
|
||||
|
||||
assertChild(0, true);
|
||||
assertChild(1, false);
|
||||
assertChild(2, true);
|
||||
assertChild(3, false);
|
||||
assertChild(4, true);
|
||||
assertChild(5, false);
|
||||
});
|
||||
|
||||
it('ItShouldDisplayErrorWhenActionIsSyntacticlyIncorect', function(){
|
||||
var c = this.compile('<div>' +
|
||||
'<input type="button" ng:click="greeting=\'ABC\'"/>' +
|
||||
'<input type="button" ng:click=":garbage:"/></div>');
|
||||
var first = jqLite(c.node[0].childNodes[0]);
|
||||
var second = jqLite(c.node[0].childNodes[1]);
|
||||
|
||||
browserTrigger(first, 'click');
|
||||
assertEquals("ABC", c.scope.greeting);
|
||||
|
||||
browserTrigger(second, 'click');
|
||||
assertTrue(second.hasClass("ng-exception"));
|
||||
});
|
||||
|
||||
it('ItShouldSelectTheCorrectRadioBox', function(){
|
||||
var c = this.compile('<div>' +
|
||||
'<input type="radio" name="sex" value="female"/>' +
|
||||
'<input type="radio" name="sex" value="male"/></div>');
|
||||
var female = jqLite(c.node[0].childNodes[0]);
|
||||
var male = jqLite(c.node[0].childNodes[1]);
|
||||
|
||||
browserTrigger(female);
|
||||
assertEquals("female", c.scope.sex);
|
||||
assertEquals(true, female[0].checked);
|
||||
assertEquals(false, male[0].checked);
|
||||
assertEquals("female", female.val());
|
||||
|
||||
browserTrigger(male);
|
||||
assertEquals("male", c.scope.sex);
|
||||
assertEquals(false, female[0].checked);
|
||||
assertEquals(true, male[0].checked);
|
||||
assertEquals("male", male.val());
|
||||
});
|
||||
|
||||
it('ItShouldListenOnRightScope', function(){
|
||||
var c = this.compile(
|
||||
'<ul ng:init="counter=0; gCounter=0" ng:watch="w:counter=counter+1">' +
|
||||
'<li ng:repeat="n in [1,2,4]" ng:watch="w:counter=counter+1;w:$root.gCounter=$root.gCounter+n"/></ul>');
|
||||
c.scope.$eval();
|
||||
assertEquals(1, c.scope.$get("counter"));
|
||||
assertEquals(7, c.scope.$get("gCounter"));
|
||||
|
||||
c.scope.$set("w", "something");
|
||||
c.scope.$eval();
|
||||
assertEquals(2, c.scope.$get("counter"));
|
||||
assertEquals(14, c.scope.$get("gCounter"));
|
||||
});
|
||||
|
||||
it('ItShouldRepeatOnHashes', function(){
|
||||
var x = this.compile('<ul><li ng:repeat="(k,v) in {a:0,b:1}" ng:bind=\"k + v\"></li></ul>');
|
||||
x.scope.$eval();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind=\"k + v\" ng:repeat-index="0">a0</li>' +
|
||||
'<li ng:bind=\"k + v\" ng:repeat-index="1">b1</li>' +
|
||||
'</ul>',
|
||||
sortedHtml(x.node));
|
||||
});
|
||||
|
||||
it('ItShouldFireChangeListenersBeforeUpdate', function(){
|
||||
var x = this.compile('<div ng:bind="name"></div>');
|
||||
x.scope.$set("name", "");
|
||||
x.scope.$watch("watched", "name=123");
|
||||
x.scope.$set("watched", "change");
|
||||
x.scope.$eval();
|
||||
assertEquals(123, x.scope.$get("name"));
|
||||
assertEquals(
|
||||
'<div ng:bind="name">123</div>',
|
||||
sortedHtml(x.node));
|
||||
});
|
||||
|
||||
it('ItShouldHandleMultilineBindings', function(){
|
||||
var x = this.compile('<div>{{\n 1 \n + \n 2 \n}}</div>');
|
||||
x.scope.$eval();
|
||||
assertEquals("3", x.node.text());
|
||||
});
|
||||
|
||||
it('ItBindHiddenInputFields', function(){
|
||||
var x = this.compile('<input type="hidden" name="myName" value="abc" />');
|
||||
x.scope.$eval();
|
||||
assertEquals("abc", x.scope.$get("myName"));
|
||||
});
|
||||
|
||||
it('ItShouldUseFormaterForText', function(){
|
||||
var x = this.compile('<input name="a" ng:format="list" value="a,b">');
|
||||
x.scope.$eval();
|
||||
assertEquals(['a','b'], x.scope.$get('a'));
|
||||
var input = x.node;
|
||||
input[0].value = ' x,,yz';
|
||||
browserTrigger(input, 'change');
|
||||
assertEquals(['x','yz'], x.scope.$get('a'));
|
||||
x.scope.$set('a', [1 ,2, 3]);
|
||||
x.scope.$eval();
|
||||
assertEquals('1, 2, 3', input[0].value);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,675 +0,0 @@
|
||||
BinderTest = TestCase('BinderTest');
|
||||
|
||||
BinderTest.prototype.setUp = function(){
|
||||
var self = this;
|
||||
|
||||
this.compile = function(html, initialScope, parent) {
|
||||
var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
|
||||
if (self.element) dealoc(self.element);
|
||||
var element = self.element = jqLite(html);
|
||||
var scope = compiler.compile(element)(element);
|
||||
|
||||
if (parent) parent.append(element);
|
||||
|
||||
extend(scope, initialScope);
|
||||
scope.$init();
|
||||
return {node:element, scope:scope};
|
||||
};
|
||||
this.compileToHtml = function (content) {
|
||||
return sortedHtml(this.compile(content).node);
|
||||
};
|
||||
};
|
||||
|
||||
BinderTest.prototype.tearDown = function(){
|
||||
if (this.element && this.element.dealoc) {
|
||||
this.element.dealoc();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
BinderTest.prototype.testChangingTextfieldUpdatesModel = function(){
|
||||
var state = this.compile('<input type="text" name="model.price" value="abc">', {model:{}});
|
||||
state.scope.$eval();
|
||||
assertEquals('abc', state.scope.model.price);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testChangingTextareaUpdatesModel = function(){
|
||||
var c = this.compile('<textarea name="model.note">abc</textarea>');
|
||||
c.scope.$eval();
|
||||
assertEquals(c.scope.model.note, 'abc');
|
||||
};
|
||||
|
||||
BinderTest.prototype.testChangingRadioUpdatesModel = function(){
|
||||
var c = this.compile('<input type="radio" name="model.price" value="A" checked>' +
|
||||
'<input type="radio" name="model.price" value="B">');
|
||||
c.scope.$eval();
|
||||
assertEquals(c.scope.model.price, 'A');
|
||||
};
|
||||
|
||||
BinderTest.prototype.testChangingCheckboxUpdatesModel = function(){
|
||||
var form = this.compile('<input type="checkbox" name="model.price" value="true" checked ng:format="boolean"/>');
|
||||
assertEquals(true, form.scope.model.price);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testBindUpdate = function() {
|
||||
var c = this.compile('<div ng:eval="a=123"/>');
|
||||
assertEquals(123, c.scope.$get('a'));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testChangingSelectNonSelectedUpdatesModel = function(){
|
||||
var form = this.compile('<select name="model.price"><option value="A">A</option><option value="B">B</option></select>');
|
||||
assertEquals('A', form.scope.model.price);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testChangingMultiselectUpdatesModel = function(){
|
||||
var form = this.compile('<select name="Invoice.options" multiple="multiple">' +
|
||||
'<option value="A" selected>Gift wrap</option>' +
|
||||
'<option value="B" selected>Extra padding</option>' +
|
||||
'<option value="C">Expedite</option>' +
|
||||
'</select>');
|
||||
assertJsonEquals(["A", "B"], form.scope.$get('Invoice').options);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testChangingSelectSelectedUpdatesModel = function(){
|
||||
var form = this.compile('<select name="model.price"><option>A</option><option selected value="b">B</option></select>');
|
||||
assertEquals(form.scope.model.price, 'b');
|
||||
};
|
||||
|
||||
BinderTest.prototype.testExecuteInitialization = function() {
|
||||
var c = this.compile('<div ng:init="a=123">');
|
||||
assertEquals(c.scope.$get('a'), 123);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testExecuteInitializationStatements = function() {
|
||||
var c = this.compile('<div ng:init="a=123;b=345">');
|
||||
assertEquals(c.scope.$get('a'), 123);
|
||||
assertEquals(c.scope.$get('b'), 345);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testApplyTextBindings = function(){
|
||||
var form = this.compile('<div ng:bind="model.a">x</div>');
|
||||
form.scope.$set('model', {a:123});
|
||||
form.scope.$eval();
|
||||
assertEquals('123', form.node.text());
|
||||
};
|
||||
|
||||
BinderTest.prototype.testReplaceBindingInTextWithSpan = function() {
|
||||
assertEquals(this.compileToHtml("<b>a{{b}}c</b>"), '<b>a<span ng:bind="b"></span>c</b>');
|
||||
assertEquals(this.compileToHtml("<b>{{b}}</b>"), '<b><span ng:bind="b"></span></b>');
|
||||
};
|
||||
|
||||
BinderTest.prototype.testBindingSpaceConfusesIE = function() {
|
||||
if (!msie) return;
|
||||
var span = document.createElement("span");
|
||||
span.innerHTML = ' ';
|
||||
var nbsp = span.firstChild.nodeValue;
|
||||
assertEquals(
|
||||
'<b><span ng:bind="a"></span><span>'+nbsp+'</span><span ng:bind="b"></span></b>',
|
||||
this.compileToHtml("<b>{{a}} {{b}}</b>"));
|
||||
assertEquals(
|
||||
'<b><span ng:bind="A"></span><span>'+nbsp+'x </span><span ng:bind="B"></span><span>'+nbsp+'(</span><span ng:bind="C"></span>)</b>',
|
||||
this.compileToHtml("<b>{{A}} x {{B}} ({{C}})</b>"));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testBindingOfAttributes = function() {
|
||||
var c = this.compile("<a href='http://s/a{{b}}c' foo='x'></a>");
|
||||
var attrbinding = c.node.attr("ng:bind-attr");
|
||||
var bindings = fromJson(attrbinding);
|
||||
assertEquals("http://s/a{{b}}c", decodeURI(bindings.href));
|
||||
assertTrue(!bindings.foo);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testMarkMultipleAttributes = function() {
|
||||
var c = this.compile('<a href="http://s/a{{b}}c" foo="{{d}}"></a>');
|
||||
var attrbinding = c.node.attr("ng:bind-attr");
|
||||
var bindings = fromJson(attrbinding);
|
||||
assertEquals(bindings.foo, "{{d}}");
|
||||
assertEquals(decodeURI(bindings.href), "http://s/a{{b}}c");
|
||||
};
|
||||
|
||||
BinderTest.prototype.testAttributesNoneBound = function() {
|
||||
var c = this.compile("<a href='abc' foo='def'></a>");
|
||||
var a = c.node;
|
||||
assertEquals(a[0].nodeName, "A");
|
||||
assertTrue(!a.attr("ng:bind-attr"));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testExistingAttrbindingIsAppended = function() {
|
||||
var c = this.compile("<a href='http://s/{{abc}}' ng:bind-attr='{\"b\":\"{{def}}\"}'></a>");
|
||||
var a = c.node;
|
||||
assertEquals('{"b":"{{def}}","href":"http://s/{{abc}}"}', a.attr('ng:bind-attr'));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testAttributesAreEvaluated = function(){
|
||||
var c = this.compile('<a ng:bind-attr=\'{"a":"a", "b":"a+b={{a+b}}"}\'></a>');
|
||||
var binder = c.binder, form = c.node;
|
||||
c.scope.$eval('a=1;b=2');
|
||||
c.scope.$eval();
|
||||
var a = c.node;
|
||||
assertEquals(a.attr('a'), 'a');
|
||||
assertEquals(a.attr('b'), 'a+b=3');
|
||||
};
|
||||
|
||||
BinderTest.prototype.testInputTypeButtonActionExecutesInScope = function(){
|
||||
var savedCalled = false;
|
||||
var c = this.compile('<input type="button" ng:click="person.save()" value="Apply">');
|
||||
c.scope.$set("person.save", function(){
|
||||
savedCalled = true;
|
||||
});
|
||||
browserTrigger(c.node, 'click');
|
||||
assertTrue(savedCalled);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testInputTypeButtonActionExecutesInScope2 = function(){
|
||||
var log = "";
|
||||
var c = this.compile('<input type="image" ng:click="action()">');
|
||||
c.scope.$set("action", function(){
|
||||
log += 'click;';
|
||||
});
|
||||
expect(log).toEqual('');
|
||||
browserTrigger(c.node, 'click');
|
||||
expect(log).toEqual('click;');
|
||||
};
|
||||
|
||||
BinderTest.prototype.testButtonElementActionExecutesInScope = function(){
|
||||
var savedCalled = false;
|
||||
var c = this.compile('<button ng:click="person.save()">Apply</button>');
|
||||
c.scope.$set("person.save", function(){
|
||||
savedCalled = true;
|
||||
});
|
||||
browserTrigger(c.node, 'click');
|
||||
assertTrue(savedCalled);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testRepeaterUpdateBindings = function(){
|
||||
var a = this.compile('<ul><LI ng:repeat="item in model.items" ng:bind="item.a"/></ul>');
|
||||
var form = a.node;
|
||||
var items = [{a:"A"}, {a:"B"}];
|
||||
a.scope.$set('model', {items:items});
|
||||
|
||||
a.scope.$eval();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="0">A</li>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="1">B</li>' +
|
||||
'</ul>', sortedHtml(form));
|
||||
|
||||
items.unshift({a:'C'});
|
||||
a.scope.$eval();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="0">C</li>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="1">A</li>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="2">B</li>' +
|
||||
'</ul>', sortedHtml(form));
|
||||
|
||||
items.shift();
|
||||
a.scope.$eval();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="0">A</li>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="1">B</li>' +
|
||||
'</ul>', sortedHtml(form));
|
||||
|
||||
items.shift();
|
||||
items.shift();
|
||||
a.scope.$eval();
|
||||
};
|
||||
|
||||
BinderTest.prototype.testRepeaterContentDoesNotBind = function(){
|
||||
var a = this.compile('<ul><LI ng:repeat="item in model.items"><span ng:bind="item.a"></span></li></ul>');
|
||||
a.scope.$set('model', {items:[{a:"A"}]});
|
||||
a.scope.$eval();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:repeat-index="0"><span ng:bind="item.a">A</span></li>' +
|
||||
'</ul>', sortedHtml(a.node));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testExpandEntityTag = function(){
|
||||
assertEquals(
|
||||
'<div ng-entity="Person" ng:watch="$anchor.a:1"></div>',
|
||||
this.compileToHtml('<div ng-entity="Person" ng:watch="$anchor.a:1"/>'));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testDoNotOverwriteCustomAction = function(){
|
||||
var html = this.compileToHtml('<input type="submit" value="Save" action="foo();">');
|
||||
assertTrue(html.indexOf('action="foo();"') > 0 );
|
||||
};
|
||||
|
||||
BinderTest.prototype.testRepeaterAdd = function(){
|
||||
var c = this.compile('<div><input type="text" name="item.x" ng:repeat="item in items"></div>');
|
||||
var doc = c.node;
|
||||
c.scope.$set('items', [{x:'a'}, {x:'b'}]);
|
||||
c.scope.$eval();
|
||||
var first = childNode(c.node, 1);
|
||||
var second = childNode(c.node, 2);
|
||||
assertEquals('a', first.val());
|
||||
assertEquals('b', second.val());
|
||||
|
||||
first.val('ABC');
|
||||
browserTrigger(first, 'keyup');
|
||||
assertEquals(c.scope.items[0].x, 'ABC');
|
||||
};
|
||||
|
||||
BinderTest.prototype.testItShouldRemoveExtraChildrenWhenIteratingOverHash = function(){
|
||||
var c = this.compile('<div><div ng:repeat="i in items">{{i}}</div></div>');
|
||||
var items = {};
|
||||
c.scope.$set("items", items);
|
||||
|
||||
c.scope.$eval();
|
||||
expect(c.node[0].childNodes.length - 1).toEqual(0);
|
||||
|
||||
items.name = "misko";
|
||||
c.scope.$eval();
|
||||
expect(c.node[0].childNodes.length - 1).toEqual(1);
|
||||
|
||||
delete items.name;
|
||||
c.scope.$eval();
|
||||
expect(c.node[0].childNodes.length - 1).toEqual(0);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testIfTextBindingThrowsErrorDecorateTheSpan = function(){
|
||||
var a = this.compile('<div>{{error.throw()}}</div>');
|
||||
var doc = a.node;
|
||||
|
||||
a.scope.$set('error.throw', function(){throw "ErrorMsg1";});
|
||||
a.scope.$eval();
|
||||
var span = childNode(doc, 0);
|
||||
assertTrue(span.hasClass('ng-exception'));
|
||||
assertTrue(!!span.text().match(/ErrorMsg1/));
|
||||
assertTrue(!!span.attr('ng-exception').match(/ErrorMsg1/));
|
||||
|
||||
a.scope.$set('error.throw', function(){throw "MyError";});
|
||||
a.scope.$eval();
|
||||
span = childNode(doc, 0);
|
||||
assertTrue(span.hasClass('ng-exception'));
|
||||
assertTrue(span.text(), span.text().match('MyError') !== null);
|
||||
assertEquals('MyError', span.attr('ng-exception'));
|
||||
|
||||
a.scope.$set('error.throw', function(){return "ok";});
|
||||
a.scope.$eval();
|
||||
assertFalse(span.hasClass('ng-exception'));
|
||||
assertEquals('ok', span.text());
|
||||
assertEquals(null, span.attr('ng-exception'));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testIfAttrBindingThrowsErrorDecorateTheAttribute = function(){
|
||||
var a = this.compile('<div attr="before {{error.throw()}} after"></div>');
|
||||
var doc = a.node;
|
||||
|
||||
a.scope.$set('error.throw', function(){throw "ErrorMsg";});
|
||||
a.scope.$eval();
|
||||
assertTrue('ng-exception', doc.hasClass('ng-exception'));
|
||||
assertEquals('"ErrorMsg"', doc.attr('ng-exception'));
|
||||
assertEquals('before "ErrorMsg" after', doc.attr('attr'));
|
||||
|
||||
a.scope.$set('error.throw', function(){ return 'X';});
|
||||
a.scope.$eval();
|
||||
assertFalse('!ng-exception', doc.hasClass('ng-exception'));
|
||||
assertEquals('before X after', doc.attr('attr'));
|
||||
assertEquals(null, doc.attr('ng-exception'));
|
||||
|
||||
};
|
||||
|
||||
BinderTest.prototype.testNestedRepeater = function() {
|
||||
var a = this.compile('<div><div ng:repeat="m in model" name="{{m.name}}">' +
|
||||
'<ul name="{{i}}" ng:repeat="i in m.item"></ul>' +
|
||||
'</div></div>');
|
||||
|
||||
a.scope.$set('model', [{name:'a', item:['a1', 'a2']}, {name:'b', item:['b1', 'b2']}]);
|
||||
a.scope.$eval();
|
||||
|
||||
assertEquals('<div>'+
|
||||
'<#comment></#comment>'+
|
||||
'<div name="a" ng:bind-attr="{"name":"{{m.name}}"}" ng:repeat-index="0">'+
|
||||
'<#comment></#comment>'+
|
||||
'<ul name="a1" ng:bind-attr="{"name":"{{i}}"}" ng:repeat-index="0"></ul>'+
|
||||
'<ul name="a2" ng:bind-attr="{"name":"{{i}}"}" ng:repeat-index="1"></ul>'+
|
||||
'</div>'+
|
||||
'<div name="b" ng:bind-attr="{"name":"{{m.name}}"}" ng:repeat-index="1">'+
|
||||
'<#comment></#comment>'+
|
||||
'<ul name="b1" ng:bind-attr="{"name":"{{i}}"}" ng:repeat-index="0"></ul>'+
|
||||
'<ul name="b2" ng:bind-attr="{"name":"{{i}}"}" ng:repeat-index="1"></ul>'+
|
||||
'</div></div>', sortedHtml(a.node));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testHideBindingExpression = function() {
|
||||
var a = this.compile('<div ng:hide="hidden == 3"/>');
|
||||
|
||||
a.scope.$set('hidden', 3);
|
||||
a.scope.$eval();
|
||||
|
||||
assertHidden(a.node);
|
||||
|
||||
a.scope.$set('hidden', 2);
|
||||
a.scope.$eval();
|
||||
|
||||
assertVisible(a.node);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testHideBinding = function() {
|
||||
var c = this.compile('<div ng:hide="hidden"/>');
|
||||
|
||||
c.scope.$set('hidden', 'true');
|
||||
c.scope.$eval();
|
||||
|
||||
assertHidden(c.node);
|
||||
|
||||
c.scope.$set('hidden', 'false');
|
||||
c.scope.$eval();
|
||||
|
||||
assertVisible(c.node);
|
||||
|
||||
c.scope.$set('hidden', '');
|
||||
c.scope.$eval();
|
||||
|
||||
assertVisible(c.node);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testShowBinding = function() {
|
||||
var c = this.compile('<div ng:show="show"/>');
|
||||
|
||||
c.scope.$set('show', 'true');
|
||||
c.scope.$eval();
|
||||
|
||||
assertVisible(c.node);
|
||||
|
||||
c.scope.$set('show', 'false');
|
||||
c.scope.$eval();
|
||||
|
||||
assertHidden(c.node);
|
||||
|
||||
c.scope.$set('show', '');
|
||||
c.scope.$eval();
|
||||
|
||||
assertHidden(c.node);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testBindClassUndefined = function() {
|
||||
var doc = this.compile('<div ng:class="undefined"/>');
|
||||
doc.scope.$eval();
|
||||
|
||||
assertEquals(
|
||||
'<div class="undefined" ng:class="undefined"></div>',
|
||||
sortedHtml(doc.node));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testBindClass = function() {
|
||||
var c = this.compile('<div ng:class="class"/>');
|
||||
|
||||
c.scope.$set('class', 'testClass');
|
||||
c.scope.$eval();
|
||||
|
||||
assertEquals('<div class="testClass" ng:class="class"></div>', sortedHtml(c.node));
|
||||
|
||||
c.scope.$set('class', ['a', 'b']);
|
||||
c.scope.$eval();
|
||||
|
||||
assertEquals('<div class="a b" ng:class="class"></div>', sortedHtml(c.node));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testBindClassEvenOdd = function() {
|
||||
var x = this.compile('<div><div ng:repeat="i in [0,1]" ng:class-even="\'e\'" ng:class-odd="\'o\'"/></div>');
|
||||
x.scope.$eval();
|
||||
var d1 = jqLite(x.node[0].childNodes[1]);
|
||||
var d2 = jqLite(x.node[0].childNodes[2]);
|
||||
expect(d1.hasClass('o')).toBeTruthy();
|
||||
expect(d2.hasClass('e')).toBeTruthy();
|
||||
assertEquals(
|
||||
'<div><#comment></#comment>' +
|
||||
'<div class="o" ng:class-even="\'e\'" ng:class-odd="\'o\'" ng:repeat-index="0"></div>' +
|
||||
'<div class="e" ng:class-even="\'e\'" ng:class-odd="\'o\'" ng:repeat-index="1"></div></div>',
|
||||
sortedHtml(x.node));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testBindStyle = function() {
|
||||
var c = this.compile('<div ng:style="style"/>');
|
||||
|
||||
c.scope.$eval('style={color:"red"}');
|
||||
c.scope.$eval();
|
||||
|
||||
assertEquals("red", c.node.css('color'));
|
||||
|
||||
c.scope.$eval('style={}');
|
||||
c.scope.$eval();
|
||||
};
|
||||
|
||||
BinderTest.prototype.testActionOnAHrefThrowsError = function(){
|
||||
var model = {books:[]};
|
||||
var c = this.compile('<a ng:click="action()">Add Phone</a>', model);
|
||||
c.scope.action = function(){
|
||||
throw new Error('MyError');
|
||||
};
|
||||
var input = c.node;
|
||||
browserTrigger(input, 'click');
|
||||
var error = input.attr('ng-exception');
|
||||
assertTrue(!!error.match(/MyError/));
|
||||
assertTrue("should have an error class", input.hasClass('ng-exception'));
|
||||
|
||||
// TODO: I think that exception should never get cleared so this portion of test makes no sense
|
||||
//c.scope.action = noop;
|
||||
//browserTrigger(input, 'click');
|
||||
//dump(input.attr('ng-error'));
|
||||
//assertFalse('error class should be cleared', input.hasClass('ng-exception'));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testShoulIgnoreVbNonBindable = function(){
|
||||
var c = this.compile("<div>{{a}}" +
|
||||
"<div ng:non-bindable>{{a}}</div>" +
|
||||
"<div ng:non-bindable=''>{{b}}</div>" +
|
||||
"<div ng:non-bindable='true'>{{c}}</div></div>");
|
||||
c.scope.$set('a', 123);
|
||||
c.scope.$eval();
|
||||
assertEquals('123{{a}}{{b}}{{c}}', c.node.text());
|
||||
};
|
||||
|
||||
BinderTest.prototype.testOptionShouldUpdateParentToGetProperBinding = function() {
|
||||
var c = this.compile('<select name="s"><option ng:repeat="i in [0,1]" value="{{i}}" ng:bind="i"></option></select>');
|
||||
c.scope.$set('s', 1);
|
||||
c.scope.$eval();
|
||||
assertEquals(1, c.node[0].selectedIndex);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testRepeaterShouldBindInputsDefaults = function () {
|
||||
var c = this.compile('<div><input value="123" name="item.name" ng:repeat="item in items"></div>');
|
||||
c.scope.$set('items', [{}, {name:'misko'}]);
|
||||
c.scope.$eval();
|
||||
|
||||
assertEquals("123", c.scope.$eval('items[0].name'));
|
||||
assertEquals("misko", c.scope.$eval('items[1].name'));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testShouldTemplateBindPreElements = function () {
|
||||
var c = this.compile('<pre>Hello {{name}}!</pre>');
|
||||
c.scope.$set("name", "World");
|
||||
c.scope.$eval();
|
||||
|
||||
assertEquals('<pre ng:bind-template="Hello {{name}}!">Hello World!</pre>', sortedHtml(c.node));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testFillInOptionValueWhenMissing = function() {
|
||||
var c = this.compile(
|
||||
'<select><option selected="true">{{a}}</option><option value="">{{b}}</option><option>C</option></select>');
|
||||
c.scope.$set('a', 'A');
|
||||
c.scope.$set('b', 'B');
|
||||
c.scope.$eval();
|
||||
var optionA = childNode(c.node, 0);
|
||||
var optionB = childNode(c.node, 1);
|
||||
var optionC = childNode(c.node, 2);
|
||||
|
||||
expect(optionA.attr('value')).toEqual('A');
|
||||
expect(optionA.text()).toEqual('A');
|
||||
|
||||
expect(optionB.attr('value')).toEqual('');
|
||||
expect(optionB.text()).toEqual('B');
|
||||
|
||||
expect(optionC.attr('value')).toEqual('C');
|
||||
expect(optionC.text()).toEqual('C');
|
||||
};
|
||||
|
||||
BinderTest.prototype.testValidateForm = function() {
|
||||
var c = this.compile('<div><input name="name" ng:required>' +
|
||||
'<div ng:repeat="item in items"><input name="item.name" ng:required/></div></div>',
|
||||
undefined, jqLite(document.body));
|
||||
var items = [{}, {}];
|
||||
c.scope.$set("items", items);
|
||||
c.scope.$eval();
|
||||
assertEquals(3, c.scope.$get("$invalidWidgets.length"));
|
||||
|
||||
c.scope.$set('name', '');
|
||||
c.scope.$eval();
|
||||
assertEquals(3, c.scope.$get("$invalidWidgets.length"));
|
||||
|
||||
c.scope.$set('name', ' ');
|
||||
c.scope.$eval();
|
||||
assertEquals(3, c.scope.$get("$invalidWidgets.length"));
|
||||
|
||||
c.scope.$set('name', 'abc');
|
||||
c.scope.$eval();
|
||||
assertEquals(2, c.scope.$get("$invalidWidgets.length"));
|
||||
|
||||
items[0].name = 'abc';
|
||||
c.scope.$eval();
|
||||
assertEquals(1, c.scope.$get("$invalidWidgets.length"));
|
||||
|
||||
items[1].name = 'abc';
|
||||
c.scope.$eval();
|
||||
assertEquals(0, c.scope.$get("$invalidWidgets.length"));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testValidateOnlyVisibleItems = function(){
|
||||
var c = this.compile('<div><input name="name" ng:required><input ng:show="show" name="name" ng:required></div>', undefined, jqLite(document.body));
|
||||
c.scope.$set("show", true);
|
||||
c.scope.$eval();
|
||||
assertEquals(2, c.scope.$get("$invalidWidgets.length"));
|
||||
|
||||
c.scope.$set("show", false);
|
||||
c.scope.$eval();
|
||||
assertEquals(1, c.scope.$invalidWidgets.visible());
|
||||
};
|
||||
|
||||
BinderTest.prototype.testDeleteAttributeIfEvaluatesFalse = function() {
|
||||
var c = this.compile('<div>' +
|
||||
'<input name="a0" ng:bind-attr="{disabled:\'{{true}}\'}"><input name="a1" ng:bind-attr="{disabled:\'{{false}}\'}">' +
|
||||
'<input name="b0" ng:bind-attr="{disabled:\'{{1}}\'}"><input name="b1" ng:bind-attr="{disabled:\'{{0}}\'}">' +
|
||||
'<input name="c0" ng:bind-attr="{disabled:\'{{[0]}}\'}"><input name="c1" ng:bind-attr="{disabled:\'{{[]}}\'}"></div>');
|
||||
c.scope.$eval();
|
||||
function assertChild(index, disabled) {
|
||||
var child = childNode(c.node, index);
|
||||
assertEquals(sortedHtml(child), disabled, !!child.attr('disabled'));
|
||||
}
|
||||
|
||||
assertChild(0, true);
|
||||
assertChild(1, false);
|
||||
assertChild(2, true);
|
||||
assertChild(3, false);
|
||||
assertChild(4, true);
|
||||
assertChild(5, false);
|
||||
};
|
||||
|
||||
BinderTest.prototype.testItShouldDisplayErrorWhenActionIsSyntacticlyIncorect = function(){
|
||||
var c = this.compile('<div>' +
|
||||
'<input type="button" ng:click="greeting=\'ABC\'"/>' +
|
||||
'<input type="button" ng:click=":garbage:"/></div>');
|
||||
var first = jqLite(c.node[0].childNodes[0]);
|
||||
var second = jqLite(c.node[0].childNodes[1]);
|
||||
|
||||
browserTrigger(first, 'click');
|
||||
assertEquals("ABC", c.scope.greeting);
|
||||
|
||||
browserTrigger(second, 'click');
|
||||
assertTrue(second.hasClass("ng-exception"));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testItShouldSelectTheCorrectRadioBox = function() {
|
||||
var c = this.compile('<div>' +
|
||||
'<input type="radio" name="sex" value="female"/>' +
|
||||
'<input type="radio" name="sex" value="male"/></div>');
|
||||
var female = jqLite(c.node[0].childNodes[0]);
|
||||
var male = jqLite(c.node[0].childNodes[1]);
|
||||
|
||||
browserTrigger(female);
|
||||
assertEquals("female", c.scope.sex);
|
||||
assertEquals(true, female[0].checked);
|
||||
assertEquals(false, male[0].checked);
|
||||
assertEquals("female", female.val());
|
||||
|
||||
browserTrigger(male);
|
||||
assertEquals("male", c.scope.sex);
|
||||
assertEquals(false, female[0].checked);
|
||||
assertEquals(true, male[0].checked);
|
||||
assertEquals("male", male.val());
|
||||
};
|
||||
|
||||
BinderTest.prototype.testItShouldListenOnRightScope = function() {
|
||||
var c = this.compile(
|
||||
'<ul ng:init="counter=0; gCounter=0" ng:watch="w:counter=counter+1">' +
|
||||
'<li ng:repeat="n in [1,2,4]" ng:watch="w:counter=counter+1;w:$root.gCounter=$root.gCounter+n"/></ul>');
|
||||
c.scope.$eval();
|
||||
assertEquals(1, c.scope.$get("counter"));
|
||||
assertEquals(7, c.scope.$get("gCounter"));
|
||||
|
||||
c.scope.$set("w", "something");
|
||||
c.scope.$eval();
|
||||
assertEquals(2, c.scope.$get("counter"));
|
||||
assertEquals(14, c.scope.$get("gCounter"));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testItShouldRepeatOnHashes = function() {
|
||||
var x = this.compile('<ul><li ng:repeat="(k,v) in {a:0,b:1}" ng:bind=\"k + v\"></li></ul>');
|
||||
x.scope.$eval();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind=\"k + v\" ng:repeat-index="0">a0</li>' +
|
||||
'<li ng:bind=\"k + v\" ng:repeat-index="1">b1</li>' +
|
||||
'</ul>',
|
||||
sortedHtml(x.node));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testItShouldFireChangeListenersBeforeUpdate = function(){
|
||||
var x = this.compile('<div ng:bind="name"></div>');
|
||||
x.scope.$set("name", "");
|
||||
x.scope.$watch("watched", "name=123");
|
||||
x.scope.$set("watched", "change");
|
||||
x.scope.$eval();
|
||||
assertEquals(123, x.scope.$get("name"));
|
||||
assertEquals(
|
||||
'<div ng:bind="name">123</div>',
|
||||
sortedHtml(x.node));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testItShouldHandleMultilineBindings = function(){
|
||||
var x = this.compile('<div>{{\n 1 \n + \n 2 \n}}</div>');
|
||||
x.scope.$eval();
|
||||
assertEquals("3", x.node.text());
|
||||
};
|
||||
|
||||
BinderTest.prototype.testItBindHiddenInputFields = function(){
|
||||
var x = this.compile('<input type="hidden" name="myName" value="abc" />');
|
||||
x.scope.$eval();
|
||||
assertEquals("abc", x.scope.$get("myName"));
|
||||
};
|
||||
|
||||
BinderTest.prototype.XtestItShouldRenderMultiRootHtmlInBinding = function() {
|
||||
var x = this.compile('<div>before {{a|html}}after</div>');
|
||||
x.scope.a = "a<b>c</b>d";
|
||||
x.scope.$eval();
|
||||
assertEquals(
|
||||
'<div>before <span ng:bind="a|html">a<b>c</b>d</span>after</div>',
|
||||
sortedHtml(x.node));
|
||||
};
|
||||
|
||||
BinderTest.prototype.testItShouldUseFormaterForText = function() {
|
||||
var x = this.compile('<input name="a" ng:format="list" value="a,b">');
|
||||
x.scope.$eval();
|
||||
assertEquals(['a','b'], x.scope.$get('a'));
|
||||
var input = x.node;
|
||||
input[0].value = ' x,,yz';
|
||||
browserTrigger(input, 'change');
|
||||
assertEquals(['x','yz'], x.scope.$get('a'));
|
||||
x.scope.$set('a', [1 ,2, 3]);
|
||||
x.scope.$eval();
|
||||
assertEquals('1, 2, 3', input[0].value);
|
||||
};
|
||||
|
||||
+90
-23
@@ -1,13 +1,13 @@
|
||||
describe('browser', function(){
|
||||
|
||||
var browser, location, head, xhr, setTimeoutQueue;
|
||||
var browser, fakeWindow, xhr, logs, scripts, setTimeoutQueue;
|
||||
|
||||
function fakeSetTimeout(fn) {
|
||||
setTimeoutQueue.push(fn);
|
||||
}
|
||||
|
||||
fakeSetTimeout.flush = function() {
|
||||
foreach(setTimeoutQueue, function(fn) {
|
||||
forEach(setTimeoutQueue, function(fn) {
|
||||
fn();
|
||||
});
|
||||
};
|
||||
@@ -15,19 +15,31 @@ describe('browser', function(){
|
||||
|
||||
beforeEach(function(){
|
||||
setTimeoutQueue = [];
|
||||
|
||||
location = {href:"http://server", hash:""};
|
||||
head = {
|
||||
scripts: [],
|
||||
append: function(node){head.scripts.push(node);}
|
||||
};
|
||||
scripts = [];
|
||||
xhr = null;
|
||||
browser = new Browser(location, jqLite(window.document), head, function(){
|
||||
fakeWindow = {
|
||||
location: {href:"http://server"},
|
||||
setTimeout: fakeSetTimeout
|
||||
}
|
||||
|
||||
var fakeBody = {append: function(node){scripts.push(node)}};
|
||||
|
||||
var fakeXhr = function(){
|
||||
xhr = this;
|
||||
this.open = noop;
|
||||
this.setRequestHeader = noop;
|
||||
this.send = noop;
|
||||
}, undefined, fakeSetTimeout);
|
||||
}
|
||||
|
||||
logs = {log:[], warn:[], info:[], error:[]};
|
||||
|
||||
var fakeLog = {log: function() { logs.log.push(slice.call(arguments)); },
|
||||
warn: function() { logs.warn.push(slice.call(arguments)); },
|
||||
info: function() { logs.info.push(slice.call(arguments)); },
|
||||
error: function() { logs.error.push(slice.call(arguments)); }};
|
||||
|
||||
browser = new Browser(fakeWindow, jqLite(window.document), fakeBody, fakeXhr,
|
||||
fakeLog);
|
||||
});
|
||||
|
||||
it('should contain cookie cruncher', function() {
|
||||
@@ -60,13 +72,13 @@ describe('browser', function(){
|
||||
browser.xhr('JSON', 'http://example.org/path?cb=JSON_CALLBACK', function(code, data){
|
||||
log += code + ':' + data + ';';
|
||||
});
|
||||
expect(head.scripts.length).toEqual(1);
|
||||
var url = head.scripts[0].src.split('?cb=');
|
||||
expect(scripts.length).toEqual(1);
|
||||
var url = scripts[0].src.split('?cb=');
|
||||
expect(url[0]).toEqual('http://example.org/path');
|
||||
expect(typeof window[url[1]]).toEqual($function);
|
||||
window[url[1]]('data');
|
||||
expect(typeof fakeWindow[url[1]]).toEqual($function);
|
||||
fakeWindow[url[1]]('data');
|
||||
expect(log).toEqual('200:data;');
|
||||
expect(typeof window[url[1]]).toEqual('undefined');
|
||||
expect(typeof fakeWindow[url[1]]).toEqual('undefined');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -107,16 +119,8 @@ describe('browser', function(){
|
||||
}
|
||||
}
|
||||
|
||||
var browser, log, logs;
|
||||
|
||||
beforeEach(function() {
|
||||
deleteAllCookies();
|
||||
logs = {log:[], warn:[], info:[], error:[]};
|
||||
log = {log: function() { logs.log.push(slice.call(arguments)); },
|
||||
warn: function() { logs.warn.push(slice.call(arguments)); },
|
||||
info: function() { logs.info.push(slice.call(arguments)); },
|
||||
error: function() { logs.error.push(slice.call(arguments)); }};
|
||||
browser = new Browser({}, jqLite(document), undefined, XHR, log);
|
||||
expect(document.cookie).toEqual('');
|
||||
});
|
||||
|
||||
@@ -334,4 +338,67 @@ describe('browser', function(){
|
||||
expect(returnedFn).toBe(fn);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('url api', function() {
|
||||
it('should use $browser poller to detect url changes when onhashchange event is unsupported',
|
||||
function() {
|
||||
|
||||
fakeWindow = {location: {href:"http://server"}};
|
||||
|
||||
browser = new Browser(fakeWindow, {}, {});
|
||||
|
||||
var events = [];
|
||||
|
||||
browser.onHashChange(function() {
|
||||
events.push('x');
|
||||
});
|
||||
|
||||
fakeWindow.location.href = "http://server/#newHash";
|
||||
expect(events).toEqual([]);
|
||||
browser.poll();
|
||||
expect(events).toEqual(['x']);
|
||||
});
|
||||
|
||||
|
||||
it('should use onhashchange events to detect url changes when supported by browser',
|
||||
function() {
|
||||
|
||||
var onHashChngListener;
|
||||
|
||||
fakeWindow = {location: {href:"http://server"},
|
||||
addEventListener: function(type, listener) {
|
||||
expect(type).toEqual('hashchange');
|
||||
onHashChngListener = listener;
|
||||
},
|
||||
attachEvent: function(type, listener) {
|
||||
expect(type).toEqual('onhashchange');
|
||||
onHashChngListener = listener;
|
||||
},
|
||||
removeEventListener: angular.noop,
|
||||
detachEvent: angular.noop
|
||||
};
|
||||
fakeWindow.onhashchange = true;
|
||||
|
||||
browser = new Browser(fakeWindow, {}, {});
|
||||
|
||||
var events = [],
|
||||
event = {type: "hashchange"}
|
||||
|
||||
browser.onHashChange(function(e) {
|
||||
events.push(e);
|
||||
});
|
||||
|
||||
expect(events).toEqual([]);
|
||||
onHashChngListener(event);
|
||||
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].originalEvent || events[0]).toBe(event); // please jQuery and jqLite
|
||||
|
||||
// clean up the jqLite cache so that the global afterEach doesn't complain
|
||||
if (!jQuery) {
|
||||
jqLite(fakeWindow).dealoc();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
ConsoleTest = TestCase('ConsoleTest');
|
||||
|
||||
ConsoleTest.prototype.XtestConsoleWrite = function(){
|
||||
var consoleNode = jqLite("<div></div>")[0];
|
||||
consoleLog("error", ["Hello", "world"]);
|
||||
assertEquals(jqLite(consoleNode)[0].nodeName, 'DIV');
|
||||
assertEquals(jqLite(consoleNode).text(), 'Hello world');
|
||||
assertEquals(jqLite(consoleNode.childNodes[0])[0].className, 'error');
|
||||
consoleLog("error",["Bye"]);
|
||||
assertEquals(jqLite(consoleNode).text(), 'Hello worldBye');
|
||||
consoleNode = null;
|
||||
};
|
||||
@@ -142,5 +142,9 @@ describe('filter', function() {
|
||||
expect(filter.date(isoString)).
|
||||
toEqual(angular.String.toDate(isoString).toLocaleDateString());
|
||||
});
|
||||
|
||||
it('should parse format ending with non-replaced string', function() {
|
||||
expect(filter.date(morning, 'yy/xxx')).toEqual('10/xxx');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
TestCase("formatterTest", {
|
||||
testNoop: function(){
|
||||
describe("formatter", function(){
|
||||
it('should noop', function(){
|
||||
assertEquals("abc", angular.formatter.noop.format("abc"));
|
||||
assertEquals("xyz", angular.formatter.noop.parse("xyz"));
|
||||
assertEquals(null, angular.formatter.noop.parse(null));
|
||||
},
|
||||
});
|
||||
|
||||
testList: function() {
|
||||
it('should List', function() {
|
||||
assertEquals('a, b', angular.formatter.list.format(['a', 'b']));
|
||||
assertEquals('', angular.formatter.list.format([]));
|
||||
assertEquals(['abc', 'c'], angular.formatter.list.parse(" , abc , c ,,"));
|
||||
assertEquals([], angular.formatter.list.parse(""));
|
||||
assertEquals([], angular.formatter.list.parse(null));
|
||||
},
|
||||
});
|
||||
|
||||
testBoolean: function() {
|
||||
it('should Boolean', function() {
|
||||
assertEquals('true', angular.formatter['boolean'].format(true));
|
||||
assertEquals('false', angular.formatter['boolean'].format(false));
|
||||
assertEquals(true, angular.formatter['boolean'].parse("true"));
|
||||
assertEquals(false, angular.formatter['boolean'].parse(""));
|
||||
assertEquals(false, angular.formatter['boolean'].parse("false"));
|
||||
assertEquals(false, angular.formatter['boolean'].parse(null));
|
||||
},
|
||||
});
|
||||
|
||||
testNumber: function() {
|
||||
it('should Number', function() {
|
||||
assertEquals('1', angular.formatter.number.format(1));
|
||||
assertEquals(1, angular.formatter.number.format('1'));
|
||||
},
|
||||
});
|
||||
|
||||
testTrim: function() {
|
||||
it('should Trim', function() {
|
||||
assertEquals('', angular.formatter.trim.format(null));
|
||||
assertEquals('', angular.formatter.trim.format(""));
|
||||
assertEquals('a', angular.formatter.trim.format(" a "));
|
||||
assertEquals('a', angular.formatter.trim.parse(' a '));
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
+3
-13
@@ -53,19 +53,9 @@ describe('injector', function(){
|
||||
|
||||
it('should autostart eager services', function(){
|
||||
var log = '';
|
||||
providers('eager', function(){log += 'eager;';}, {$creation: 'eager'});
|
||||
providers('eager', function(){log += 'eager;'; return 'foo'}, {$eager: true});
|
||||
inject();
|
||||
expect(log).toEqual('eager;');
|
||||
expect(scope.eager).not.toBeDefined();
|
||||
expect(inject('eager')).toBe('foo');
|
||||
});
|
||||
|
||||
|
||||
it('should return a list of published objects', function(){
|
||||
var log = '';
|
||||
providers('eager', function(){log += 'eager;'; return 'pub'; }, {$creation: 'eager-published'});
|
||||
inject();
|
||||
expect(log).toEqual('eager;');
|
||||
expect(scope.eager).toEqual('pub');
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,6 +116,42 @@ describe('json', function(){
|
||||
expect(fromJson("{exp:1.2e-10}")).toEqual({exp:1.2E-10});
|
||||
});
|
||||
|
||||
|
||||
//run these tests only in browsers that have native JSON parser
|
||||
if (JSON && JSON.parse) {
|
||||
|
||||
describe('native parser', function() {
|
||||
|
||||
var nativeParser = JSON.parse;
|
||||
|
||||
afterEach(function() {
|
||||
JSON.parse = nativeParser;
|
||||
});
|
||||
|
||||
|
||||
it('should delegate to native parser if available and boolean flag is passed', function() {
|
||||
var spy = this.spyOn(JSON, 'parse').andCallThrough();
|
||||
|
||||
expect(fromJson('{}')).toEqual({});
|
||||
expect(spy).wasNotCalled();
|
||||
|
||||
expect(fromJson('{}', true)).toEqual({});
|
||||
expect(spy).wasCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should convert timestamp strings to Date objects', function() {
|
||||
expect(fromJson('"2010-12-22T17:23:17.974Z"', true) instanceof Date).toBe(true);
|
||||
expect(fromJson('["2010-12-22T17:23:17.974Z"]', true)[0] instanceof Date).toBe(true);
|
||||
expect(fromJson('{"t":"2010-12-22T17:23:17.974Z"}', true).t instanceof Date).toBe(true);
|
||||
expect(fromJson('{"t":["2010-12-22T17:23:17.974Z"]}', true).t[0] instanceof Date).toBe(true);
|
||||
expect(fromJson('{"t":{"t":"2010-12-22T17:23:17.974Z"}}', true).t.t instanceof Date).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
describe('security', function(){
|
||||
it('should not allow naked expressions', function(){
|
||||
expect(function(){fromJson('1+2');}).
|
||||
@@ -151,6 +187,18 @@ describe('json', function(){
|
||||
expect(function(){fromJson('[].constructor');}).
|
||||
toThrow(new Error("Parse Error: Token '.' is not valid json at column 3 of expression [[].constructor] starting at [.constructor]."));
|
||||
});
|
||||
|
||||
it('should not allow object dereference', function(){
|
||||
expect(function(){fromJson('{a:1, b: $location, c:1}');}).toThrow();
|
||||
expect(function(){fromJson("{a:1, b:[1]['__parent__']['location'], c:1}");}).toThrow();
|
||||
});
|
||||
|
||||
it('should not allow assignments', function(){
|
||||
expect(function(){fromJson("{a:1, b:[1]=1, c:1}");}).toThrow();
|
||||
expect(function(){fromJson("{a:1, b:=1, c:1}");}).toThrow();
|
||||
expect(function(){fromJson("{a:1, b:x=1, c:1}");}).toThrow();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user