mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-16 23:30:23 +00:00
feat(scope): new and improved scope implementation
- Speed improvements (about 4x on flush phase) - Memory improvements (uses no function closures) - Break $eval into $apply, $dispatch, $flush - Introduced $watch and $observe Breaks angular.equals() use === instead of == Breaks angular.scope() does not take parent as first argument Breaks scope.$watch() takes scope as first argument Breaks scope.$set(), scope.$get are removed Breaks scope.$config is removed Breaks $route.onChange callback has not "this" bounded
This commit is contained in:
parent
1f4b417184
commit
8f0dcbab80
60 changed files with 2539 additions and 1721 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -1,6 +1,22 @@
|
|||
<a name="0.9.19"><a/>
|
||||
# 0.9.19 canine-psychokinesis (in-progress) #
|
||||
|
||||
# Breaking Changes
|
||||
- Controller constructor functions are now looked up on scope first and then on window.
|
||||
- angular.equals now use === which means that things which used to be equal are no longer.
|
||||
Example '0' !== 0 and [] !== ''
|
||||
- angular.scope (http://docs.angularjs.org/#!angular.scope) now (providers, cache) instead of
|
||||
(parent, providers, cache)
|
||||
- Watch functions (see http://docs.angularjs.org/#!angular.scope.$watch) used to take
|
||||
fn(newValue, oldValue) and be bound to scope, now they take fn(scope, newValue, oldValue)
|
||||
- calling $eval() [no args] should be replaced with call to $apply()
|
||||
(http://docs.angularjs.org/#!angular.scope.$apply) ($eval(exp) should remain as is see
|
||||
http://docs.angularjs.org/#!angular.scope.$eval)
|
||||
- scope $set/$get have been removed. ($get is same as $eval; no replacement for $set)
|
||||
- $route.onChange() callback (http://docs.angularjs.org/#!angular.service.$route)
|
||||
no longer has this bound.
|
||||
- Removed undocumented $config in root scope. (You should have not been depending on this.)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ no connection between the controller and the view.
|
|||
});
|
||||
this.$location.hashSearch.board = rows.join(';') + '/' + this.nextMove;
|
||||
},
|
||||
readUrl: function(value) {
|
||||
readUrl: function(scope, value) {
|
||||
if (value) {
|
||||
value = value.split('/');
|
||||
this.nextMove = value[1];
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Scopes: Applying Controllers to Scopes
|
||||
@description
|
||||
|
||||
When a controller function is applied to a scope, the scope is augmented with the behavior defined
|
||||
in the controller. The end result is that the scope behaves as if it were the controller:
|
||||
|
||||
<pre>
|
||||
var scope = angular.scope();
|
||||
scope.salutation = 'Hello';
|
||||
scope.name = 'World';
|
||||
|
||||
expect(scope.greeting).toEqual(undefined);
|
||||
|
||||
scope.$watch('name', function(){
|
||||
this.greeting = this.salutation + ' ' + this.name + '!';
|
||||
});
|
||||
|
||||
expect(scope.greeting).toEqual('Hello World!');
|
||||
scope.name = 'Misko';
|
||||
// scope.$eval() will propagate the change to listeners
|
||||
expect(scope.greeting).toEqual('Hello World!');
|
||||
|
||||
scope.$eval();
|
||||
expect(scope.greeting).toEqual('Hello Misko!');
|
||||
</pre>
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.scopes Angular Scope Objects}
|
||||
* {@link dev_guide.scopes.understanding_scopes Understanding Angular Scopes}
|
||||
* {@link dev_guide.scopes.working_scopes Working With Angular Scopes}
|
||||
* {@link dev_guide.scopes.updating_scopes Updating Angular Scopes}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.scope Angular Scope API}
|
||||
196
docs/content/guide/dev_guide.scopes.internals.ngdoc
Normal file
196
docs/content/guide/dev_guide.scopes.internals.ngdoc
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Scopes: Scope Internals
|
||||
@description
|
||||
|
||||
## What is a scope?
|
||||
|
||||
A scope is an execution context for {@link dev_guide.expressions expressions}. You can think of a
|
||||
scope as a JavaScript object that has an extra set of APIs for registering change listeners and for
|
||||
managing its own life cycle. In Angular's implementation of the model-view-controller design
|
||||
pattern, a scope's properties comprise both the model and the controller methods.
|
||||
|
||||
|
||||
### Scope characteristics
|
||||
- Scopes provide APIs ($watch and $observe) to observe model mutations.
|
||||
- Scopes provide APIs ($apply) to propagate any model changes through the system into the view from
|
||||
outside of the "Angular realm" (controllers, services, Angular event handlers).
|
||||
- Scopes can be nested to isolate application components while providing access to shared model
|
||||
properties. A scope (prototypically) inherits properties from its parent scope.
|
||||
- In some parts of the system (such as controllers, services and directives), the scope is made
|
||||
available as `this` within the given context. (Note: This functionality will change before 1.0 is
|
||||
released.)
|
||||
|
||||
|
||||
### Root scope
|
||||
|
||||
Every application has a root scope, which is the ancestor of all other scopes. The root scope is
|
||||
responsible for creating the injector which is assigned to the {@link api/angular.scope.$service
|
||||
$service} property, and initializing the services.
|
||||
|
||||
### What is scope used for?
|
||||
|
||||
{@link dev_guide.expressions Expressions} in the view are evaluated against the current scope. When
|
||||
HTML DOM elements are attached to a scope, expressions in those elements are evaluated against the
|
||||
attached scope.
|
||||
|
||||
There are two kinds of expressions:
|
||||
- Binding expressions, which are observations of property changes. Property changes are reflected
|
||||
in the view during the {@link api/angular.scope.$flush flush cycle}.
|
||||
- Action expressions, which are expressions with side effects. Typically, the side effects cause
|
||||
execution of a method in a controller in response to a user action, such as clicking on a button.
|
||||
|
||||
|
||||
### Scope inheritance
|
||||
|
||||
A scope (prototypically) inherits properties from its parent scope. Since a given property may not
|
||||
reside on a child scope, if a property read does not find the property on a scope, the read will
|
||||
recursively check the parent scope, grandparent scope, etc. all the way to the root scope before
|
||||
defaulting to undefined.
|
||||
|
||||
{@link api/angular.directive Directives} associated with elements (ng:controller, ng:repeat,
|
||||
ng:include, etc.) create new child scopes that inherit properties from the current parent scope.
|
||||
Any code in Angular is free to create a new scope. Whether or not your code does so is an
|
||||
implementation detail of the directive, that is, you can decide when or if this happens.
|
||||
Inheritance typically mimics HTML DOM element nesting, but does not do so with the same
|
||||
granularity.
|
||||
|
||||
A property write will always write to the current scope. This means that a write can hide a parent
|
||||
property within the scope it writes to, as shown in the following example.
|
||||
|
||||
<pre>
|
||||
var root = angular.scope();
|
||||
var child = root.$new();
|
||||
|
||||
root.name = 'angular';
|
||||
expect(child.name).toEqual('angular');
|
||||
expect(root.name).toEqual('angular');
|
||||
|
||||
child.name = 'super-heroic framework';
|
||||
expect(child.name).toEqual('super-heroic framework');
|
||||
expect(root.name).toEqual('angular');
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
## Scopes in Angular applications
|
||||
To understand how Angular applications work, you need to understand how scopes work within an
|
||||
application. This section describes the typical life cycle of an application so you can see how
|
||||
scopes come into play throughout and get a sense of their interactions.
|
||||
### How scopes interact in applications
|
||||
|
||||
1. At application compile time, a root scope is created and is attached to the root `<HTML>` DOM
|
||||
element.
|
||||
1. The root scope creates an {@link api/angular.injector injector} which is assigned to the
|
||||
{@link api/angular.scope.$service $service} property of the root scope.
|
||||
2. Any eager {@link api/angular.scope.$service services} are initialized at this point.
|
||||
2. During the compilation phase, the {@link dev_guide.compiler compiler} matches {@link
|
||||
api/angular.directive directives} against the DOM template. The directives usually fall into one of
|
||||
two categories:
|
||||
- Observing {@link api/angular.directive directives}, such as double-curly expressions
|
||||
`{{expression}}`, register listeners using the {@link api/angular.scope.$observe $observe()}
|
||||
method. This type of directive needs to be notified whenever the expression changes so that it can
|
||||
update the view.
|
||||
- Listener directives, such as {@link api/angular.directive.ng:click ng:click}, register a
|
||||
listener with the DOM. When the DOM listener fires, the directive executes the associated
|
||||
expression and updates the view using the {@link api/angular.scope.$apply $apply()} method.
|
||||
3. When an external event (such as a user action, timer or XHR) is received, the associated {@link
|
||||
dev_guide.expressions expression} must be applied to the scope through the {@link
|
||||
api/angular.scope.$apply $apply()} method so that all listeners are updated correctly.
|
||||
|
||||
|
||||
### Directives that create scopes
|
||||
In most cases, {@link api/angular.directive directives} and scopes interact but do not create new
|
||||
instances of scope. However, some directives, such as {@link api/angular.directive.ng:controller
|
||||
ng:controller} and {@link api/angular.widget.@ng:repeat ng:repeat}, create new child scopes using
|
||||
the {@link api/angular.scope.$new $new()} method and then attach the child scope to the
|
||||
corresponding DOM element. You can retrieve a scope for any DOM element by using an
|
||||
`angular.element(aDomElement).scope()` method call.)
|
||||
|
||||
|
||||
### Controllers and scopes
|
||||
Scopes and controllers interact with each other in the following situations:
|
||||
- Controllers use scopes to expose controller methods to templates (see {@link
|
||||
api/angular.directive.ng:controller ng:controller}).
|
||||
- Controllers define methods (behavior) that can mutate the model (properties on the scope).
|
||||
- Controllers may register {@link api/angular.scope.$watch watches} on the model. These watches
|
||||
execute immediately after the controller behavior executes, but before the DOM gets updated.
|
||||
|
||||
See the {@link dev_guide.mvc.understanding_controller controller docs} for more information.
|
||||
|
||||
### Updating scope properties
|
||||
You can update a scope by calling its {@link api/angular.scope.$eval $eval()} method, but usually
|
||||
you do not have to do this explicitly. In most cases, angular intercepts all external events (such
|
||||
as user interactions, XHRs, and timers) and calls the `$eval()` method on the scope object for you
|
||||
at the right time. The only time you might need to call `$eval()` explicitly is when you create
|
||||
your own custom widget or service.
|
||||
|
||||
The reason it is unnecessary to call `$eval()` from within your controller functions when you use
|
||||
built-in angular widgets and services is because a change in the data model triggers a call to the
|
||||
`$eval()` method on the scope object where the data model changed.
|
||||
|
||||
When a user inputs data, angularized widgets copy the data to the appropriate scope and then call
|
||||
the `$eval()` method on the root scope to update the view. It works this way because scopes are
|
||||
inherited, and a child scope `$eval()` overrides its parent's `$eval()` method. Updating the whole
|
||||
page requires a call to `$eval()` on the root scope as `$root.$eval()`. Similarly, when a request
|
||||
to fetch data from a server is made and the response comes back, the data is written into the model
|
||||
and then `$eval()` is called to push updates through to the view and any other dependents.
|
||||
|
||||
A widget that creates scopes (such as {@link api/angular.widget.@ng:repeat ng:repeat}) is
|
||||
responsible for forwarding `$eval()` calls from the parent to those child scopes. That way, calling
|
||||
`$eval()` on the root scope will update the whole page. This creates a spreadsheet-like behavior
|
||||
for your app; the bound views update immediately as the user enters data.
|
||||
|
||||
## Scopes in unit-testing
|
||||
You can create scopes, including the root scope, in tests using the {@link api/angular.scope} API.
|
||||
This allows you to mimic the run-time environment and have full control over the life cycle of the
|
||||
scope so that you can assert correct model transitions. Since these scopes are created outside the
|
||||
normal compilation process, their life cycles must be managed by the test.
|
||||
|
||||
There is a key difference between the way scopes are called in Angular applications and in Angular
|
||||
tests. In tests, the {@link api/angular.service.$updateView $updateView} calls the {@link
|
||||
api/angular.scope.$flush $flush()} method synchronously.(This is in contrast to the asynchronous
|
||||
calls used for applications.) Because test calls to scopes are synchronous, your tests are simpler
|
||||
to write.
|
||||
|
||||
### Using scopes in unit-testing
|
||||
The following example demonstrates how the scope life cycle needs to be manually triggered from
|
||||
within the unit-tests.
|
||||
|
||||
<pre>
|
||||
// example of a test
|
||||
var scope = angular.scope();
|
||||
scope.$watch('name', function(scope, name){
|
||||
scope.greeting = 'Hello ' + name + '!';
|
||||
});
|
||||
|
||||
scope.name = 'angular';
|
||||
// The watch does not fire yet since we have to manually trigger the digest phase.
|
||||
expect(scope.greeting).toEqual(undefined);
|
||||
|
||||
// manually trigger digest phase from the test
|
||||
scope.$digest();
|
||||
expect(scope.greeting).toEqual('Hello Angular!');
|
||||
</pre>
|
||||
|
||||
|
||||
### Dependency injection in Tests
|
||||
|
||||
When you find it necessary to inject your own mocks in your tests, use a scope to override the
|
||||
service instances, as shown in the following example.
|
||||
|
||||
<pre>
|
||||
var myLocation = {};
|
||||
var scope = angular.scope(null, {$location: myLocation});
|
||||
expect(scope.$service('$location')).toEqual(myLocation);
|
||||
</pre>
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.scopes Angular Scope Objects}
|
||||
* {@link dev_guide.scopes.understanding_scopes Understanding Scopes}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.scope Angular Scope API}
|
||||
|
||||
|
|
@ -1,36 +1,34 @@
|
|||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Scopes
|
||||
@name Developer Guide: Scopes
|
||||
@description
|
||||
|
||||
|
||||
An angular scope is a JavaScript type defined by angular. Instances of this type are objects that
|
||||
serve as the context within which all model and controller methods live and get evaluated.
|
||||
An Angular scope is a JavaScript object with additional APIs useful for watching property changes,
|
||||
Angular scope is the model in Model-View-Controller paradigm. Instances of scope serve as the
|
||||
context within which all {@link dev_guide.expressions expressions} get evaluated.
|
||||
|
||||
Angular links scope objects to specific points in a compiled (processed) template. This linkage
|
||||
provides the contexts in which angular creates data-bindings between the model and the view. You
|
||||
can think of angular scope objects as the medium through which the model, view, and controller
|
||||
communicate.
|
||||
You can think of Angular scope objects as the medium through which the model, view, and controller
|
||||
communicate. Scopes are linked during the compilation process with the view. This linkage provides
|
||||
the contexts in which Angular creates data-bindings between the model and the view.
|
||||
|
||||
In addition to providing the context in which data is evaluated, angular scope objects watch for
|
||||
In addition to providing the context in which data is evaluated, Angular scope objects watch for
|
||||
model changes. The scope objects also notify all components interested in any model changes (for
|
||||
example, functions registered through {@link api/angular.scope.$watch $watch}, bindings created by
|
||||
{@link api/angular.directive.ng:bind ng:bind}, or HTML input elements).
|
||||
|
||||
Angular scope objects are responsible for:
|
||||
Angular scope objects:
|
||||
|
||||
* Gluing the model, controller and view template together.
|
||||
* Providing the mechanism to watch for model changes ({@link api/angular.scope.$watch}).
|
||||
* Notifying interested components when the model changes ({@link api/angular.scope.$eval}).
|
||||
* Providing the context in which all controller functions and angular expressions are evaluated.
|
||||
* Link the model, controller and view template together.
|
||||
* Provide the mechanism to watch for model changes ({@link api/angular.scope.$watch}).
|
||||
* Notify interested components when the model changes ({@link api/angular.scope.$eval}).
|
||||
* Provide the context in which expressions are evaluated.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.scopes.understanding_scopes Understanding Scopes}
|
||||
* {@link dev_guide.scopes.working_scopes Working With Scopes}
|
||||
* {@link dev_guide.scopes.controlling_scopes Applying Controllers to Scopes}
|
||||
* {@link dev_guide.scopes.updating_scopes Updating Scopes}
|
||||
* {@link dev_guide.scopes.internals Scopes Internals}
|
||||
|
||||
## Related API
|
||||
|
||||
|
|
|
|||
|
|
@ -60,9 +60,7 @@ The following illustration shows the DOM and angular scopes for the example abov
|
|||
## Related Topics
|
||||
|
||||
* {@link dev_guide.scopes Angular Scope Objects}
|
||||
* {@link dev_guide.scopes.working_scopes Working With Scopes}
|
||||
* {@link dev_guide.scopes.controlling_scopes Applying Controllers to Scopes}
|
||||
* {@link dev_guide.scopes.updating_scopes Updating Scopes}
|
||||
* {@link dev_guide.scopes.internals Scopes Internals}
|
||||
|
||||
## Related API
|
||||
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Scopes: Updating Scope Properties
|
||||
@description
|
||||
|
||||
You can update a scope by calling its {@link api/angular.scope.$eval $eval()} method, but usually
|
||||
you do not have to do this explicitly. In most cases, angular intercepts all external events (such
|
||||
as user interactions, XHRs, and timers) and calls the `$eval()` method on the scope object for you
|
||||
at the right time. The only time you might need to call `$eval()` explicitly is when you create
|
||||
your own custom widget or service.
|
||||
|
||||
The reason it is unnecessary to call `$eval()` from within your controller functions when you use
|
||||
built-in angular widgets and services is because a change in the data model triggers a call to the
|
||||
`$eval()` method on the scope object where the data model changed.
|
||||
|
||||
When a user inputs data, angularized widgets copy the data to the appropriate scope and then call
|
||||
the `$eval()` method on the root scope to update the view. It works this way because scopes are
|
||||
inherited, and a child scope `$eval()` overrides its parent's `$eval()` method. Updating the whole
|
||||
page requires a call to `$eval()` on the root scope as `$root.$eval()`. Similarly, when a request
|
||||
to fetch data from a server is made and the response comes back, the data is written into the model
|
||||
and then `$eval()` is called to push updates through to the view and any other dependents.
|
||||
|
||||
A widget that creates scopes (such as {@link api/angular.widget.@ng:repeat ng:repeat}) is
|
||||
responsible for forwarding `$eval()` calls from the parent to those child scopes. That way, calling
|
||||
`$eval()` on the root scope will update the whole page. This creates a spreadsheet-like behavior
|
||||
for your app; the bound views update immediately as the user enters data.
|
||||
|
||||
|
||||
## Related Documents
|
||||
|
||||
* {@link dev_guide.scopes Angular Scope Objects}
|
||||
* {@link dev_guide.scopes.understanding_scopes Understanding Angular Scope Objects}
|
||||
* {@link dev_guide.scopes.working_scopes Working With Angular Scopes}
|
||||
* {@link dev_guide.scopes.controlling_scopes Applying Controllers to Scopes}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.scope Angular Scope API}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Scopes: Working With Angular Scopes
|
||||
@description
|
||||
|
||||
When you use {@link api/angular.directive.ng:autobind ng:autobind} to bootstrap your application,
|
||||
angular creates the root scope automatically for you. If you need more control over the
|
||||
bootstrapping process, or if you need to create a root scope for a test, you can do so using the
|
||||
{@link api/angular.scope angular.scope()} API.
|
||||
|
||||
Here is a simple code snippet that demonstrates how to create a scope object, assign model
|
||||
properties to it, and register listeners to watch for changes to the model properties:
|
||||
|
||||
<pre>
|
||||
var scope = angular.scope();
|
||||
scope.salutation = 'Hello';
|
||||
scope.name = 'World';
|
||||
|
||||
// Verify that greeting is undefined
|
||||
expect(scope.greeting).toEqual(undefined);
|
||||
|
||||
// Set up the watcher...
|
||||
scope.$watch('name', function(){
|
||||
// when 'name' changes, set 'greeting'...
|
||||
this.greeting = this.salutation + ' ' + this.name + '!';
|
||||
}
|
||||
);
|
||||
|
||||
// verify that 'greeting' was set...
|
||||
expect(scope.greeting).toEqual('Hello World!');
|
||||
|
||||
// 'name' changed!
|
||||
scope.name = 'Misko';
|
||||
|
||||
// scope.$eval() will propagate the change to listeners
|
||||
expect(scope.greeting).toEqual('Hello World!');
|
||||
|
||||
scope.$eval();
|
||||
// verify that '$eval' propagated the change
|
||||
expect(scope.greeting).toEqual('Hello Misko!');
|
||||
</pre>
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.scopes Angular Scope Objects}
|
||||
* {@link dev_guide.scopes.understanding_scopes Understanding Scopes}
|
||||
* {@link dev_guide.scopes.controlling_scopes Applying Controllers to Scopes}
|
||||
* {@link dev_guide.scopes.updating_scopes Updating Scopes}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.scope Angular Scope API}
|
||||
|
|
@ -30,9 +30,7 @@ of the following documents before returning here to the Developer Guide:
|
|||
## {@link dev_guide.scopes Angular Scope Objects}
|
||||
|
||||
* {@link dev_guide.scopes.understanding_scopes Understanding Angular Scope Objects}
|
||||
* {@link dev_guide.scopes.working_scopes Working With Angular Scopes}
|
||||
* {@link dev_guide.scopes.controlling_scopes Applying Controllers to Scopes}
|
||||
* {@link dev_guide.scopes.updating_scopes Updating Scope Properties}
|
||||
* {@link dev_guide.scopes.internals Angular Scope Internals}
|
||||
|
||||
## {@link dev_guide.compiler Angular HTML Compiler}
|
||||
|
||||
|
|
|
|||
|
|
@ -398,3 +398,25 @@ li {
|
|||
margin: 0em 2em 1em 0em;
|
||||
float:right;
|
||||
}
|
||||
|
||||
.table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table th:first-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
border: 1px solid black;
|
||||
padding: .5em 1em;
|
||||
}
|
||||
.table th {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table th.section {
|
||||
text-align: left;
|
||||
background-color: lightgray;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ function DocsController($location, $browser, $window, $cookies) {
|
|||
$location.hashPath = '!/api';
|
||||
}
|
||||
|
||||
this.$watch('$location.hashPath', function(hashPath) {
|
||||
this.$watch('$location.hashPath', function(scope, hashPath) {
|
||||
if (hashPath.match(/^!/)) {
|
||||
var parts = hashPath.substring(1).split('/');
|
||||
self.sectionId = parts[1];
|
||||
|
|
@ -36,7 +36,7 @@ function DocsController($location, $browser, $window, $cookies) {
|
|||
delete self.partialId;
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
this.getUrl = function(page){
|
||||
return '#!/' + page.section + '/' + page.id;
|
||||
|
|
|
|||
21
perf/MiscPerf.js
Normal file
21
perf/MiscPerf.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
describe('perf misc', function(){
|
||||
it('operation speeds', function(){
|
||||
perf(
|
||||
function typeByTypeof(){ return typeof noop == 'function'; }, // WINNER
|
||||
function typeByProperty() { return noop.apply && noop.call; },
|
||||
function typeByConstructor() { return noop.constructor == Function; }
|
||||
);
|
||||
});
|
||||
|
||||
it('property access', function(){
|
||||
var name = 'value';
|
||||
var none = 'x';
|
||||
var scope = {};
|
||||
perf(
|
||||
function direct(){ return scope.value; }, // WINNER
|
||||
function byName() { return scope[name]; },
|
||||
function undefinedDirect(){ return scope.x; },
|
||||
function undefiendByName() { return scope[none]; }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -56,7 +56,6 @@ function fromCharCode(code) { return String.fromCharCode(code); }
|
|||
|
||||
var _undefined = undefined,
|
||||
_null = null,
|
||||
$$element = '$element',
|
||||
$$scope = '$scope',
|
||||
$$validate = '$validate',
|
||||
$angular = 'angular',
|
||||
|
|
@ -65,7 +64,6 @@ var _undefined = undefined,
|
|||
$console = 'console',
|
||||
$date = 'date',
|
||||
$display = 'display',
|
||||
$element = 'element',
|
||||
$function = 'function',
|
||||
$length = 'length',
|
||||
$name = 'name',
|
||||
|
|
@ -573,6 +571,16 @@ function isLeafNode (node) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.copy
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Alias for {@link angular.Object.copy}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.Object.copy
|
||||
|
|
@ -657,6 +665,15 @@ function copy(source, destination){
|
|||
return destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.equals
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Alias for {@link angular.Object.equals}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
|
|
@ -666,8 +683,8 @@ function copy(source, destination){
|
|||
* @description
|
||||
* Determines if two objects or value are equivalent.
|
||||
*
|
||||
* To be equivalent, they must pass `==` comparison or be of the same type and have all their
|
||||
* properties pass `==` comparison. During property comparision properties of `function` type and
|
||||
* To be equivalent, they must pass `===` comparison or be of the same type and have all their
|
||||
* properties pass `===` comparison. During property comparision properties of `function` type and
|
||||
* properties with name starting with `$` are ignored.
|
||||
*
|
||||
* Supports values types, arrays and objects.
|
||||
|
|
@ -707,7 +724,7 @@ function copy(source, destination){
|
|||
* </doc:example>
|
||||
*/
|
||||
function equals(o1, o2) {
|
||||
if (o1 == o2) return true;
|
||||
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') {
|
||||
|
|
@ -779,6 +796,10 @@ function concat(array1, array2, index) {
|
|||
return array1.concat(slice.call(array2, index, array2.length));
|
||||
}
|
||||
|
||||
function sliceArgs(args, startIndex) {
|
||||
return slice.call(args, startIndex || 0, args.length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
|
|
@ -797,9 +818,7 @@ function concat(array1, array2, index) {
|
|||
* @returns {function()} Function that wraps the `fn` with all the specified bindings.
|
||||
*/
|
||||
function bind(self, fn) {
|
||||
var curryArgs = arguments.length > 2
|
||||
? slice.call(arguments, 2, arguments.length)
|
||||
: [];
|
||||
var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
|
||||
if (typeof fn == $function && !(fn instanceof RegExp)) {
|
||||
return curryArgs.length
|
||||
? function() {
|
||||
|
|
@ -939,13 +958,14 @@ function angularInit(config, document){
|
|||
|
||||
if (autobind) {
|
||||
var element = isString(autobind) ? document.getElementById(autobind) : document,
|
||||
scope = compile(element)(createScope({'$config':config})),
|
||||
scope = compile(element)(createScope()),
|
||||
$browser = scope.$service('$browser');
|
||||
|
||||
if (config.css)
|
||||
$browser.addCss(config.base_url + config.css);
|
||||
else if(msie<8)
|
||||
$browser.addJs(config.ie_compat, config.ie_compat_id);
|
||||
scope.$apply();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1001,7 +1021,8 @@ function assertArg(arg, name, reason) {
|
|||
}
|
||||
|
||||
function assertArgFn(arg, name) {
|
||||
assertArg(isFunction(arg, name, 'not a function'));
|
||||
assertArg(isFunction(arg), name, 'not a function, got ' +
|
||||
(typeof arg == 'object' ? arg.constructor.name : typeof arg));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ function Browser(window, document, body, XHR, $log) {
|
|||
*/
|
||||
function completeOutstandingRequest(fn) {
|
||||
try {
|
||||
fn.apply(null, slice.call(arguments, 1));
|
||||
fn.apply(null, sliceArgs(arguments, 1));
|
||||
} finally {
|
||||
outstandingRequestCount--;
|
||||
if (outstandingRequestCount === 0) {
|
||||
|
|
|
|||
|
|
@ -29,15 +29,20 @@ Template.prototype = {
|
|||
inits[this.priority] = queue = [];
|
||||
}
|
||||
if (this.newScope) {
|
||||
childScope = createScope(scope);
|
||||
scope.$onEval(childScope.$eval);
|
||||
childScope = isFunction(this.newScope) ? scope.$new(this.newScope(scope)) : scope.$new();
|
||||
element.data($$scope, childScope);
|
||||
}
|
||||
// TODO(misko): refactor this!!!
|
||||
// Why are inits even here?
|
||||
forEach(this.inits, function(fn) {
|
||||
queue.push(function() {
|
||||
childScope.$tryEval(function(){
|
||||
return childScope.$service.invoke(childScope, fn, [element]);
|
||||
}, element);
|
||||
childScope.$eval(function(){
|
||||
try {
|
||||
return childScope.$service.invoke(childScope, fn, [element]);
|
||||
} catch (e) {
|
||||
childScope.$service('$exceptionHandler')(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
var i,
|
||||
|
|
@ -218,7 +223,6 @@ Compiler.prototype = {
|
|||
scope.$element = element;
|
||||
(cloneConnectFn||noop)(element, scope);
|
||||
template.attach(element, scope);
|
||||
scope.$eval();
|
||||
return scope;
|
||||
};
|
||||
},
|
||||
|
|
@ -228,6 +232,7 @@ Compiler.prototype = {
|
|||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:eval-order
|
||||
* @deprecated
|
||||
*
|
||||
* @description
|
||||
* Normally the view is updated from top to bottom. This usually is
|
||||
|
|
@ -244,9 +249,9 @@ Compiler.prototype = {
|
|||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div>TOTAL: without ng:eval-order {{ items.$sum('total') | currency }}</div>
|
||||
<div ng:eval-order='LAST'>TOTAL: with ng:eval-order {{ items.$sum('total') | currency }}</div>
|
||||
<table ng:init="items=[{qty:1, cost:9.99, desc:'gadget'}]">
|
||||
<div>TOTAL: without ng:eval-order {{ total | currency }}</div>
|
||||
<div ng:eval-order='LAST'>TOTAL: with ng:eval-order {{ total | currency }}</div>
|
||||
<table ng:init="items=[{qty:1, cost:9.99, desc:'gadget'}];total=0;">
|
||||
<tr>
|
||||
<td>QTY</td>
|
||||
<td>Description</td>
|
||||
|
|
@ -258,22 +263,22 @@ Compiler.prototype = {
|
|||
<td><input name="item.qty"/></td>
|
||||
<td><input name="item.desc"/></td>
|
||||
<td><input name="item.cost"/></td>
|
||||
<td>{{item.total = item.qty * item.cost | currency}}</td>
|
||||
<td>{{item.qty * item.cost | currency}}</td>
|
||||
<td><a href="" ng:click="items.$remove(item)">X</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><a href="" ng:click="items.$add()">add</a></td>
|
||||
<td>{{ items.$sum('total') | currency }}</td>
|
||||
<td>{{ total = items.$sum('qty*cost') | currency }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:format', function(){
|
||||
expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99');
|
||||
expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$9.99');
|
||||
expect(using('.doc-example-live div:first').binding("total")).toBe('$0.00');
|
||||
expect(using('.doc-example-live div:last').binding("total")).toBe('$9.99');
|
||||
input('item.qty').enter('2');
|
||||
expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99');
|
||||
expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$19.98');
|
||||
expect(using('.doc-example-live div:first').binding("total")).toBe('$9.99');
|
||||
expect(using('.doc-example-live div:last').binding("total")).toBe('$19.98');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
|
|
|||
|
|
@ -116,6 +116,9 @@ function toJsonArray(buf, obj, pretty, stack) {
|
|||
sep = true;
|
||||
}
|
||||
buf.push("]");
|
||||
} else if (isElement(obj)) {
|
||||
// TODO(misko): maybe in dev mode have a better error reporting?
|
||||
buf.push('DOM_ELEMENT');
|
||||
} else if (isDate(obj)) {
|
||||
buf.push(angular.String.quoteUnicode(angular.Date.toString(obj)));
|
||||
} else {
|
||||
|
|
|
|||
1241
src/Scope.js
1241
src/Scope.js
File diff suppressed because it is too large
Load diff
2
src/angular-mocks.js
vendored
2
src/angular-mocks.js
vendored
|
|
@ -374,7 +374,7 @@ angular.service('$browser', function(){
|
|||
* See {@link angular.mock} for more info on angular mocks.
|
||||
*/
|
||||
angular.service('$exceptionHandler', function() {
|
||||
return function(e) { throw e;};
|
||||
return function(e) { throw e; };
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
24
src/apis.js
24
src/apis.js
|
|
@ -7,7 +7,7 @@ var angularGlobal = {
|
|||
if (type == $object) {
|
||||
if (obj instanceof Array) return $array;
|
||||
if (isDate(obj)) return $date;
|
||||
if (obj.nodeType == 1) return $element;
|
||||
if (obj.nodeType == 1) return 'element';
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ var angularArray = {
|
|||
</doc:example>
|
||||
*/
|
||||
'sum':function(array, expression) {
|
||||
var fn = angular['Function']['compile'](expression);
|
||||
var fn = angularFunction.compile(expression);
|
||||
var sum = 0;
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
var value = 1 * fn(array[i]);
|
||||
|
|
@ -522,21 +522,21 @@ var angularArray = {
|
|||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should calculate counts', function() {
|
||||
expect(binding('items.$count(\'points==1\')')).toEqual(2);
|
||||
expect(binding('items.$count(\'points>1\')')).toEqual(1);
|
||||
expect(binding('items.$count(\'points==1\')')).toEqual('2');
|
||||
expect(binding('items.$count(\'points>1\')')).toEqual('1');
|
||||
});
|
||||
|
||||
it('should recalculate when updated', function() {
|
||||
using('.doc-example-live li:first-child').input('item.points').enter('23');
|
||||
expect(binding('items.$count(\'points==1\')')).toEqual(1);
|
||||
expect(binding('items.$count(\'points>1\')')).toEqual(2);
|
||||
expect(binding('items.$count(\'points==1\')')).toEqual('1');
|
||||
expect(binding('items.$count(\'points>1\')')).toEqual('2');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
'count':function(array, condition) {
|
||||
if (!condition) return array.length;
|
||||
var fn = angular['Function']['compile'](condition), count = 0;
|
||||
var fn = angularFunction.compile(condition), count = 0;
|
||||
forEach(array, function(value){
|
||||
if (fn(value)) {
|
||||
count ++;
|
||||
|
|
@ -635,7 +635,7 @@ var angularArray = {
|
|||
descending = predicate.charAt(0) == '-';
|
||||
predicate = predicate.substring(1);
|
||||
}
|
||||
get = expressionCompile(predicate).fnSelf;
|
||||
get = expressionCompile(predicate);
|
||||
}
|
||||
return reverseComparator(function(a,b){
|
||||
return compare(get(a),get(b));
|
||||
|
|
@ -796,14 +796,14 @@ var angularDate = {
|
|||
};
|
||||
|
||||
var angularFunction = {
|
||||
'compile':function(expression) {
|
||||
'compile': function(expression) {
|
||||
if (isFunction(expression)){
|
||||
return expression;
|
||||
} else if (expression){
|
||||
return expressionCompile(expression).fnSelf;
|
||||
return expressionCompile(expression);
|
||||
} else {
|
||||
return identity;
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@
|
|||
*/
|
||||
angularDirective("ng:init", function(expression){
|
||||
return function(element){
|
||||
this.$tryEval(expression, element);
|
||||
this.$eval(expression);
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -165,19 +165,19 @@ angularDirective("ng:init", function(expression){
|
|||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:controller", function(expression){
|
||||
this.scope(true);
|
||||
return function(element){
|
||||
var controller = getter(window, expression, true) || getter(this, expression, true);
|
||||
if (!controller)
|
||||
throw "Can not find '"+expression+"' controller.";
|
||||
if (!isFunction(controller))
|
||||
throw "Reference '"+expression+"' is not a class.";
|
||||
this.$become(controller);
|
||||
};
|
||||
this.scope(function(scope){
|
||||
var Controller =
|
||||
getter(scope, expression, true) ||
|
||||
getter(window, expression, true);
|
||||
assertArgFn(Controller, expression);
|
||||
return Controller;
|
||||
});
|
||||
return noop;
|
||||
});
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @deprecated
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:eval
|
||||
*
|
||||
|
|
@ -208,17 +208,18 @@ angularDirective("ng:controller", function(expression){
|
|||
<doc:scenario>
|
||||
it('should check eval', function(){
|
||||
expect(binding('obj.divide')).toBe('3');
|
||||
expect(binding('obj.updateCount')).toBe('2');
|
||||
expect(binding('obj.updateCount')).toBe('1');
|
||||
input('obj.a').enter('12');
|
||||
expect(binding('obj.divide')).toBe('6');
|
||||
expect(binding('obj.updateCount')).toBe('3');
|
||||
expect(binding('obj.updateCount')).toBe('2');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
// TODO(misko): remove me
|
||||
angularDirective("ng:eval", function(expression){
|
||||
return function(element){
|
||||
this.$onEval(expression, element);
|
||||
this.$observe(expression);
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -257,15 +258,26 @@ angularDirective("ng:bind", function(expression, element){
|
|||
element.addClass('ng-binding');
|
||||
return function(element) {
|
||||
var lastValue = noop, lastError = noop;
|
||||
this.$onEval(function() {
|
||||
this.$observe(function(scope) {
|
||||
// TODO(misko): remove error handling https://github.com/angular/angular.js/issues/347
|
||||
var error, value, html, isHtml, isDomElement,
|
||||
oldElement = this.hasOwnProperty($$element) ? this.$element : undefined;
|
||||
this.$element = element;
|
||||
value = this.$tryEval(expression, function(e){
|
||||
hadOwnElement = scope.hasOwnProperty('$element'),
|
||||
oldElement = scope.$element;
|
||||
// TODO(misko): get rid of $element https://github.com/angular/angular.js/issues/348
|
||||
scope.$element = element;
|
||||
try {
|
||||
value = scope.$eval(expression);
|
||||
} catch (e) {
|
||||
scope.$service('$exceptionHandler')(e);
|
||||
error = formatError(e);
|
||||
});
|
||||
this.$element = oldElement;
|
||||
// If we are HTML then save the raw HTML data so that we don't
|
||||
} finally {
|
||||
if (hadOwnElement) {
|
||||
scope.$element = oldElement;
|
||||
} else {
|
||||
delete scope.$element;
|
||||
}
|
||||
}
|
||||
// If we are HTML than save the raw HTML data so that we don't
|
||||
// recompute sanitization since it is expensive.
|
||||
// TODO: turn this into a more generic way to compute this
|
||||
if (isHtml = (value instanceof HTML))
|
||||
|
|
@ -289,7 +301,7 @@ angularDirective("ng:bind", function(expression, element){
|
|||
element.text(value == undefined ? '' : value);
|
||||
}
|
||||
}
|
||||
}, element);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -301,10 +313,14 @@ function compileBindTemplate(template){
|
|||
forEach(parseBindings(template), function(text){
|
||||
var exp = binding(text);
|
||||
bindings.push(exp
|
||||
? function(element){
|
||||
var error, value = this.$tryEval(exp, function(e){
|
||||
? function(scope, element) {
|
||||
var error, value;
|
||||
try {
|
||||
value = scope.$eval(exp);
|
||||
} catch(e) {
|
||||
scope.$service('$exceptionHandler')(e);
|
||||
error = toJson(e);
|
||||
});
|
||||
}
|
||||
elementError(element, NG_EXCEPTION, error);
|
||||
return error ? error : value;
|
||||
}
|
||||
|
|
@ -312,20 +328,30 @@ function compileBindTemplate(template){
|
|||
return text;
|
||||
});
|
||||
});
|
||||
bindTemplateCache[template] = fn = function(element, prettyPrintJson){
|
||||
var parts = [], self = this,
|
||||
oldElement = this.hasOwnProperty($$element) ? self.$element : undefined;
|
||||
self.$element = element;
|
||||
for ( var i = 0; i < bindings.length; i++) {
|
||||
var value = bindings[i].call(self, element);
|
||||
if (isElement(value))
|
||||
value = '';
|
||||
else if (isObject(value))
|
||||
value = toJson(value, prettyPrintJson);
|
||||
parts.push(value);
|
||||
bindTemplateCache[template] = fn = function(scope, element, prettyPrintJson) {
|
||||
var parts = [],
|
||||
hadOwnElement = scope.hasOwnProperty('$element'),
|
||||
oldElement = scope.$element;
|
||||
|
||||
// TODO(misko): get rid of $element
|
||||
scope.$element = element;
|
||||
try {
|
||||
for (var i = 0; i < bindings.length; i++) {
|
||||
var value = bindings[i](scope, element);
|
||||
if (isElement(value))
|
||||
value = '';
|
||||
else if (isObject(value))
|
||||
value = toJson(value, prettyPrintJson);
|
||||
parts.push(value);
|
||||
}
|
||||
return parts.join('');
|
||||
} finally {
|
||||
if (hadOwnElement) {
|
||||
scope.$element = oldElement;
|
||||
} else {
|
||||
delete scope.$element;
|
||||
}
|
||||
}
|
||||
self.$element = oldElement;
|
||||
return parts.join('');
|
||||
};
|
||||
}
|
||||
return fn;
|
||||
|
|
@ -372,13 +398,13 @@ angularDirective("ng:bind-template", function(expression, element){
|
|||
var templateFn = compileBindTemplate(expression);
|
||||
return function(element) {
|
||||
var lastValue;
|
||||
this.$onEval(function() {
|
||||
var value = templateFn.call(this, element, true);
|
||||
this.$observe(function(scope) {
|
||||
var value = templateFn(scope, element, true);
|
||||
if (value != lastValue) {
|
||||
element.text(value);
|
||||
lastValue = value;
|
||||
}
|
||||
}, element);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -446,10 +472,10 @@ var REMOVE_ATTRIBUTES = {
|
|||
angularDirective("ng:bind-attr", function(expression){
|
||||
return function(element){
|
||||
var lastValue = {};
|
||||
this.$onEval(function(){
|
||||
var values = this.$eval(expression);
|
||||
this.$observe(function(scope){
|
||||
var values = scope.$eval(expression);
|
||||
for(var key in values) {
|
||||
var value = compileBindTemplate(values[key]).call(this, element),
|
||||
var value = compileBindTemplate(values[key])(scope, element),
|
||||
specialName = REMOVE_ATTRIBUTES[lowercase(key)];
|
||||
if (lastValue[key] !== value) {
|
||||
lastValue[key] = value;
|
||||
|
|
@ -467,7 +493,7 @@ angularDirective("ng:bind-attr", function(expression){
|
|||
}
|
||||
}
|
||||
}
|
||||
}, element);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -510,14 +536,13 @@ 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 annotate('$updateView', function($updateView, element){
|
||||
return function(element){
|
||||
var self = this;
|
||||
element.bind('click', function(event){
|
||||
self.$tryEval(expression, element);
|
||||
$updateView();
|
||||
self.$apply(expression);
|
||||
event.stopPropagation();
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -555,28 +580,27 @@ angularDirective("ng:click", function(expression, element){
|
|||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:submit", function(expression, element) {
|
||||
return annotate('$updateView', function($updateView, element) {
|
||||
return function(element) {
|
||||
var self = this;
|
||||
element.bind('submit', function(event) {
|
||||
self.$tryEval(expression, element);
|
||||
$updateView();
|
||||
self.$apply(expression);
|
||||
event.preventDefault();
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
function ngClass(selector) {
|
||||
return function(expression, element){
|
||||
return function(expression, element) {
|
||||
var existing = element[0].className + ' ';
|
||||
return function(element){
|
||||
this.$onEval(function(){
|
||||
if (selector(this.$index)) {
|
||||
var value = this.$eval(expression);
|
||||
return function(element) {
|
||||
this.$observe(function(scope) {
|
||||
if (selector(scope.$index)) {
|
||||
var value = scope.$eval(expression);
|
||||
if (isArray(value)) value = value.join(' ');
|
||||
element[0].className = trim(existing + value);
|
||||
}
|
||||
}, element);
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -732,9 +756,9 @@ angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));
|
|||
*/
|
||||
angularDirective("ng:show", function(expression, element){
|
||||
return function(element){
|
||||
this.$onEval(function(){
|
||||
toBoolean(this.$eval(expression)) ? element.show() : element.hide();
|
||||
}, element);
|
||||
this.$observe(expression, function(scope, value){
|
||||
toBoolean(value) ? element.show() : element.hide();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -773,9 +797,9 @@ angularDirective("ng:show", function(expression, element){
|
|||
*/
|
||||
angularDirective("ng:hide", function(expression, element){
|
||||
return function(element){
|
||||
this.$onEval(function(){
|
||||
toBoolean(this.$eval(expression)) ? element.hide() : element.show();
|
||||
}, element);
|
||||
this.$observe(expression, function(scope, value){
|
||||
toBoolean(value) ? element.hide() : element.show();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -815,8 +839,8 @@ angularDirective("ng:hide", function(expression, element){
|
|||
angularDirective("ng:style", function(expression, element){
|
||||
return function(element){
|
||||
var resetStyle = getStyle(element);
|
||||
this.$onEval(function(){
|
||||
var style = this.$eval(expression) || {}, key, mergedStyle = {};
|
||||
this.$observe(function(scope){
|
||||
var style = scope.$eval(expression) || {}, key, mergedStyle = {};
|
||||
for(key in style) {
|
||||
if (resetStyle[key] === undefined) resetStyle[key] = '';
|
||||
mergedStyle[key] = style[key];
|
||||
|
|
@ -825,7 +849,7 @@ angularDirective("ng:style", function(expression, element){
|
|||
mergedStyle[key] = mergedStyle[key] || resetStyle[key];
|
||||
}
|
||||
element.css(mergedStyle);
|
||||
}, element);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -645,25 +645,26 @@ angularFilter.html = function(html, option){
|
|||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
//TODO: externalize all regexps
|
||||
angularFilter.linky = function(text){
|
||||
var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
|
||||
MAILTO_REGEXP = /^mailto:/;
|
||||
|
||||
angularFilter.linky = function(text) {
|
||||
if (!text) return text;
|
||||
var URL = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/;
|
||||
var match;
|
||||
var raw = text;
|
||||
var html = [];
|
||||
var writer = htmlSanitizeWriter(html);
|
||||
var url;
|
||||
var i;
|
||||
while (match=raw.match(URL)) {
|
||||
while (match = raw.match(LINKY_URL_REGEXP)) {
|
||||
// We can not end in these as they are sometimes found at the end of the sentence
|
||||
url = match[0];
|
||||
// if we did not match ftp/http/mailto then assume mailto
|
||||
if (match[2]==match[3]) url = 'mailto:' + url;
|
||||
if (match[2] == match[3]) url = 'mailto:' + url;
|
||||
i = match.index;
|
||||
writer.chars(raw.substr(0, i));
|
||||
writer.start('a', {href:url});
|
||||
writer.chars(match[0].replace(/^mailto:/, ''));
|
||||
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
|
||||
writer.end('a');
|
||||
raw = raw.substring(i + match[0].length);
|
||||
}
|
||||
|
|
|
|||
111
src/parser.js
111
src/parser.js
|
|
@ -659,5 +659,116 @@ function parser(text, json){
|
|||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Parser helper functions
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
function setter(obj, path, setValue) {
|
||||
var element = path.split('.');
|
||||
for (var i = 0; element.length > 1; i++) {
|
||||
var key = element.shift();
|
||||
var propertyObj = obj[key];
|
||||
if (!propertyObj) {
|
||||
propertyObj = {};
|
||||
obj[key] = propertyObj;
|
||||
}
|
||||
obj = propertyObj;
|
||||
}
|
||||
obj[element.shift()] = setValue;
|
||||
return setValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value accesible from the object by path. Any undefined traversals are ignored
|
||||
* @param {Object} obj starting object
|
||||
* @param {string} path path to traverse
|
||||
* @param {boolean=true} bindFnToScope
|
||||
* @returns value as accesbile by path
|
||||
*/
|
||||
function getter(obj, path, bindFnToScope) {
|
||||
if (!path) return obj;
|
||||
var keys = path.split('.');
|
||||
var key;
|
||||
var lastInstance = obj;
|
||||
var len = keys.length;
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
key = keys[i];
|
||||
if (obj) {
|
||||
obj = (lastInstance = obj)[key];
|
||||
}
|
||||
if (isUndefined(obj) && key.charAt(0) == '$') {
|
||||
var type = angularGlobal.typeOf(lastInstance);
|
||||
type = angular[type.charAt(0).toUpperCase()+type.substring(1)];
|
||||
var fn = type ? type[[key.substring(1)]] : _undefined;
|
||||
if (fn) {
|
||||
return obj = bind(lastInstance, fn, lastInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!bindFnToScope && isFunction(obj)) {
|
||||
return bind(lastInstance, obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
var getterFnCache = {},
|
||||
compileCache = {},
|
||||
JS_KEYWORDS = {};
|
||||
|
||||
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," +
|
||||
"protected,public,return,short,static,super,switch,synchronized,this,throw,throws," +
|
||||
"transient,true,try,typeof,var,volatile,void,undefined,while,with").split(/,/),
|
||||
function(key){ JS_KEYWORDS[key] = true;}
|
||||
);
|
||||
|
||||
function getterFn(path) {
|
||||
var fn = getterFnCache[path];
|
||||
if (fn) return fn;
|
||||
|
||||
var code = 'var l, fn, t;\n';
|
||||
forEach(path.split('.'), function(key) {
|
||||
key = (JS_KEYWORDS[key]) ? '["' + key + '"]' : '.' + key;
|
||||
code += 'if(!s) return s;\n' +
|
||||
'l=s;\n' +
|
||||
's=s' + key + ';\n' +
|
||||
'if(typeof s=="function" && !(s instanceof RegExp)) s = function(){ return l' +
|
||||
key + '.apply(l, arguments); };\n';
|
||||
if (key.charAt(1) == '$') {
|
||||
// special code for super-imposed functions
|
||||
var name = key.substr(2);
|
||||
code += 'if(!s) {\n' +
|
||||
' t = angular.Global.typeOf(l);\n' +
|
||||
' fn = (angular[t.charAt(0).toUpperCase() + t.substring(1)]||{})["' + name + '"];\n' +
|
||||
' if (fn) s = function(){ return fn.apply(l, ' +
|
||||
'[l].concat(Array.prototype.slice.call(arguments, 0, arguments.length))); };\n' +
|
||||
'}\n';
|
||||
}
|
||||
});
|
||||
code += 'return s;';
|
||||
fn = Function('s', code);
|
||||
fn["toString"] = function(){ return code; };
|
||||
|
||||
return getterFnCache[path] = fn;
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
|
||||
// TODO(misko): Should this function be public?
|
||||
function compileExpr(expr) {
|
||||
return parser(expr).statements();
|
||||
}
|
||||
|
||||
// TODO(misko): Deprecate? Remove!
|
||||
// I think that compilation should be a service.
|
||||
function expressionCompile(exp) {
|
||||
if (typeof exp === $function) return exp;
|
||||
var fn = compileCache[exp];
|
||||
if (!fn) {
|
||||
fn = compileCache[exp] = parser(exp).statements();
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,9 +163,13 @@ angular.scenario.Runner.prototype.createSpecRunner_ = function(scope) {
|
|||
*/
|
||||
angular.scenario.Runner.prototype.run = function(application) {
|
||||
var self = this;
|
||||
var $root = angular.scope(this);
|
||||
var $root = angular.scope();
|
||||
angular.extend($root, this);
|
||||
angular.forEach(angular.scenario.Runner.prototype, function(fn, name) {
|
||||
$root[name] = angular.bind(self, fn);
|
||||
});
|
||||
$root.application = application;
|
||||
this.emit('RunnerBegin');
|
||||
$root.emit('RunnerBegin');
|
||||
asyncForEach(this.rootDescribe.getSpecs(), function(spec, specDone) {
|
||||
var dslCache = {};
|
||||
var runner = self.createSpecRunner_($root);
|
||||
|
|
@ -175,7 +179,7 @@ angular.scenario.Runner.prototype.run = function(application) {
|
|||
angular.forEach(angular.scenario.dsl, function(fn, key) {
|
||||
self.$window[key] = function() {
|
||||
var line = callerFile(3);
|
||||
var scope = angular.scope(runner);
|
||||
var scope = runner.$new();
|
||||
|
||||
// Make the dsl accessible on the current chain
|
||||
scope.dsl = {};
|
||||
|
|
@ -200,7 +204,10 @@ angular.scenario.Runner.prototype.run = function(application) {
|
|||
return scope.dsl[key].apply(scope, arguments);
|
||||
};
|
||||
});
|
||||
runner.run(spec, specDone);
|
||||
runner.run(spec, function() {
|
||||
runner.$destroy();
|
||||
specDone.apply(this, arguments);
|
||||
});
|
||||
},
|
||||
function(error) {
|
||||
if (error) {
|
||||
|
|
|
|||
|
|
@ -245,7 +245,6 @@ angular.scenario.dsl('repeater', function() {
|
|||
|
||||
chain.row = function(index) {
|
||||
return this.addFutureAction("repeater '" + this.label + "' row '" + index + "'", function($window, $document, done) {
|
||||
var values = [];
|
||||
var matches = $document.elements().slice(index, index + 1);
|
||||
if (!matches.length)
|
||||
return done('row ' + index + ' out of bounds');
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ angularServiceInject('$cookies', function($browser) {
|
|||
lastBrowserCookies = currentCookies;
|
||||
copy(currentCookies, lastCookies);
|
||||
copy(currentCookies, cookies);
|
||||
if (runEval) rootScope.$eval();
|
||||
if (runEval) rootScope.$apply();
|
||||
}
|
||||
})();
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ angularServiceInject('$cookies', function($browser) {
|
|||
//at the end of each eval, push cookies
|
||||
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
|
||||
// strings or browser refuses to store some cookies, we update the model in the push fn.
|
||||
this.$onEval(PRIORITY_LAST, push);
|
||||
this.$observe(push);
|
||||
|
||||
return cookies;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,16 +18,11 @@
|
|||
* @param {function()} fn A function, who's execution should be deferred.
|
||||
* @param {number=} [delay=0] of milliseconds to defer the function execution.
|
||||
*/
|
||||
angularServiceInject('$defer', function($browser, $exceptionHandler, $updateView) {
|
||||
angularServiceInject('$defer', function($browser) {
|
||||
var scope = this;
|
||||
return function(fn, delay) {
|
||||
$browser.defer(function() {
|
||||
try {
|
||||
fn();
|
||||
} catch(e) {
|
||||
$exceptionHandler(e);
|
||||
} finally {
|
||||
$updateView();
|
||||
}
|
||||
scope.$apply(fn);
|
||||
}, delay);
|
||||
};
|
||||
}, ['$browser', '$exceptionHandler', '$updateView']);
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ angularServiceInject("$invalidWidgets", function(){
|
|||
|
||||
|
||||
/* At the end of each eval removes all invalid widgets that are not part of the current DOM. */
|
||||
this.$onEval(PRIORITY_LAST, function() {
|
||||
this.$watch(function() {
|
||||
for(var i = 0; i < invalidWidgets.length;) {
|
||||
var widget = invalidWidgets[i];
|
||||
if (isOrphan(widget[0])) {
|
||||
|
|
@ -56,7 +56,7 @@ angularServiceInject("$invalidWidgets", function(){
|
|||
|
||||
|
||||
/**
|
||||
* Traverses DOM element's (widget's) parents and considers the element to be an orphant if one of
|
||||
* Traverses DOM element's (widget's) parents and considers the element to be an orphan if one of
|
||||
* it's parents isn't the current window.document.
|
||||
*/
|
||||
function isOrphan(widget) {
|
||||
|
|
|
|||
|
|
@ -69,18 +69,14 @@ var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+)
|
|||
</doc:example>
|
||||
*/
|
||||
angularServiceInject("$location", function($browser) {
|
||||
var scope = this,
|
||||
location = {update:update, updateHash: updateHash},
|
||||
lastLocation = {};
|
||||
var location = {update: update, updateHash: updateHash};
|
||||
var lastLocation = {}; // last state since last update().
|
||||
|
||||
$browser.onHashChange(function() { //register
|
||||
$browser.onHashChange(bind(this, this.$apply, function() { //register
|
||||
update($browser.getUrl());
|
||||
copy(location, lastLocation);
|
||||
scope.$eval();
|
||||
})(); //initialize
|
||||
}))(); //initialize
|
||||
|
||||
this.$onEval(PRIORITY_FIRST, sync);
|
||||
this.$onEval(PRIORITY_LAST, updateBrowser);
|
||||
this.$watch(sync);
|
||||
|
||||
return location;
|
||||
|
||||
|
|
@ -94,6 +90,8 @@ angularServiceInject("$location", function($browser) {
|
|||
*
|
||||
* @description
|
||||
* Updates the location object.
|
||||
* Does not immediately update the browser
|
||||
* Browser is updated at the end of $flush()
|
||||
*
|
||||
* Does not immediately update the browser. Instead the browser is updated at the end of $eval()
|
||||
* cycle.
|
||||
|
|
@ -122,6 +120,8 @@ angularServiceInject("$location", function($browser) {
|
|||
|
||||
location.href = composeHref(location);
|
||||
}
|
||||
$browser.setUrl(location.href);
|
||||
copy(location, lastLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -188,33 +188,20 @@ angularServiceInject("$location", function($browser) {
|
|||
if (!equals(location, lastLocation)) {
|
||||
if (location.href != lastLocation.href) {
|
||||
update(location.href);
|
||||
return;
|
||||
}
|
||||
if (location.hash != lastLocation.hash) {
|
||||
var hash = parseHash(location.hash);
|
||||
updateHash(hash.hashPath, hash.hashSearch);
|
||||
} else {
|
||||
location.hash = composeHash(location);
|
||||
location.href = composeHref(location);
|
||||
if (location.hash != lastLocation.hash) {
|
||||
var hash = parseHash(location.hash);
|
||||
updateHash(hash.hashPath, hash.hashSearch);
|
||||
} else {
|
||||
location.hash = composeHash(location);
|
||||
location.href = composeHref(location);
|
||||
}
|
||||
update(location.href);
|
||||
}
|
||||
update(location.href);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If location has changed, update the browser
|
||||
* This method is called at the end of $eval() phase
|
||||
*/
|
||||
function updateBrowser() {
|
||||
sync();
|
||||
|
||||
if ($browser.getUrl() != location.href) {
|
||||
$browser.setUrl(location.href);
|
||||
copy(location, lastLocation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose href string from a location object
|
||||
*
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularServiceInject('$route', function(location, $updateView) {
|
||||
angularServiceInject('$route', function($location, $updateView) {
|
||||
var routes = {},
|
||||
onChange = [],
|
||||
matcher = switchRouteMatcher,
|
||||
|
|
@ -207,66 +207,67 @@ angularServiceInject('$route', function(location, $updateView) {
|
|||
|
||||
|
||||
function updateRoute(){
|
||||
var childScope, routeParams, pathParams, segmentMatch, key, redir;
|
||||
var selectedRoute, pathParams, segmentMatch, key, redir;
|
||||
|
||||
if ($route.current && $route.current.scope) {
|
||||
$route.current.scope.$destroy();
|
||||
}
|
||||
$route.current = null;
|
||||
// Match a route
|
||||
forEach(routes, function(rParams, rPath) {
|
||||
if (!pathParams) {
|
||||
if (pathParams = matcher(location.hashPath, rPath)) {
|
||||
routeParams = rParams;
|
||||
if (pathParams = matcher($location.hashPath, rPath)) {
|
||||
selectedRoute = rParams;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// "otherwise" fallback
|
||||
routeParams = routeParams || routes[null];
|
||||
// No route matched; fallback to "otherwise" route
|
||||
selectedRoute = selectedRoute || routes[null];
|
||||
|
||||
if(routeParams) {
|
||||
if (routeParams.redirectTo) {
|
||||
if (isString(routeParams.redirectTo)) {
|
||||
if(selectedRoute) {
|
||||
if (selectedRoute.redirectTo) {
|
||||
if (isString(selectedRoute.redirectTo)) {
|
||||
// interpolate the redirectTo string
|
||||
redir = {hashPath: '',
|
||||
hashSearch: extend({}, location.hashSearch, pathParams)};
|
||||
hashSearch: extend({}, $location.hashSearch, pathParams)};
|
||||
|
||||
forEach(routeParams.redirectTo.split(':'), function(segment, i) {
|
||||
forEach(selectedRoute.redirectTo.split(':'), function(segment, i) {
|
||||
if (i==0) {
|
||||
redir.hashPath += segment;
|
||||
} else {
|
||||
segmentMatch = segment.match(/(\w+)(.*)/);
|
||||
key = segmentMatch[1];
|
||||
redir.hashPath += pathParams[key] || location.hashSearch[key];
|
||||
redir.hashPath += pathParams[key] || $location.hashSearch[key];
|
||||
redir.hashPath += segmentMatch[2] || '';
|
||||
delete redir.hashSearch[key];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// call custom redirectTo function
|
||||
redir = {hash: routeParams.redirectTo(pathParams, location.hash, location.hashPath,
|
||||
location.hashSearch)};
|
||||
redir = {hash: selectedRoute.redirectTo(pathParams, $location.hash, $location.hashPath,
|
||||
$location.hashSearch)};
|
||||
}
|
||||
|
||||
location.update(redir);
|
||||
$updateView(); //TODO this is to work around the $location<=>$browser issues
|
||||
$location.update(redir);
|
||||
return;
|
||||
}
|
||||
|
||||
childScope = createScope(parentScope);
|
||||
$route.current = extend({}, routeParams, {
|
||||
scope: childScope,
|
||||
params: extend({}, location.hashSearch, pathParams)
|
||||
});
|
||||
$route.current = extend({}, selectedRoute);
|
||||
$route.current.params = extend({}, $location.hashSearch, pathParams);
|
||||
}
|
||||
|
||||
//fire onChange callbacks
|
||||
forEach(onChange, parentScope.$tryEval);
|
||||
forEach(onChange, parentScope.$eval, parentScope);
|
||||
|
||||
if (childScope) {
|
||||
childScope.$become($route.current.controller);
|
||||
// Create the scope if we have mtched a route
|
||||
if ($route.current) {
|
||||
$route.current.scope = parentScope.$new($route.current.controller);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.$watch(function(){return dirty + location.hash;}, updateRoute);
|
||||
this.$watch(function(){return dirty + $location.hash;}, updateRoute)();
|
||||
|
||||
return $route;
|
||||
}, ['$location', '$updateView']);
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@
|
|||
* 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 angular.scope.$eval} at any time from your code:
|
||||
* <pre>scope.$root.$eval()</pre>
|
||||
* {@link angular.scope.$apply} at any time from your code:
|
||||
* <pre>scope.$apply()</pre>
|
||||
*
|
||||
* In unit-test mode the update is instantaneous and synchronous to simplify writing tests.
|
||||
*
|
||||
|
|
@ -47,7 +47,7 @@ function serviceUpdateViewFactory($browser){
|
|||
var scheduled;
|
||||
function update(){
|
||||
scheduled = false;
|
||||
rootScope.$eval();
|
||||
rootScope.$flush();
|
||||
}
|
||||
return $browser.isMock ? update : function(){
|
||||
if (!scheduled) {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,6 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
|
|||
}
|
||||
});
|
||||
};
|
||||
this.$onEval(PRIORITY_LAST, bulkXHR.flush);
|
||||
this.$observe(bulkXHR.flush);
|
||||
return bulkXHR;
|
||||
}, ['$xhr', '$xhr.error', '$log']);
|
||||
|
|
|
|||
278
src/widgets.js
278
src/widgets.js
|
|
@ -183,9 +183,7 @@ function modelAccessor(scope, element) {
|
|||
},
|
||||
set: function(value) {
|
||||
if (value !== undefined) {
|
||||
return scope.$tryEval(function(){
|
||||
assignFn(scope, value);
|
||||
}, element);
|
||||
assignFn(scope, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -332,7 +330,7 @@ function valueAccessor(scope, element) {
|
|||
format = formatter.format;
|
||||
parse = formatter.parse;
|
||||
if (requiredExpr) {
|
||||
scope.$watch(requiredExpr, function(newValue) {
|
||||
scope.$watch(requiredExpr, function(scope, newValue) {
|
||||
required = newValue;
|
||||
validate();
|
||||
});
|
||||
|
|
@ -529,32 +527,33 @@ function radioInit(model, view, element) {
|
|||
</doc:example>
|
||||
*/
|
||||
function inputWidget(events, modelAccessor, viewAccessor, initFn, textBox) {
|
||||
return annotate('$updateView', '$defer', function($updateView, $defer, element) {
|
||||
return annotate('$defer', function($defer, element) {
|
||||
var scope = this,
|
||||
model = modelAccessor(scope, element),
|
||||
view = viewAccessor(scope, element),
|
||||
action = element.attr('ng:change') || '',
|
||||
action = element.attr('ng:change') || noop,
|
||||
lastValue;
|
||||
if (model) {
|
||||
initFn.call(scope, model, view, element);
|
||||
this.$eval(element.attr('ng:init')||'');
|
||||
scope.$eval(element.attr('ng:init') || noop);
|
||||
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();
|
||||
}
|
||||
scope.$apply(function() {
|
||||
var value = view.get();
|
||||
if (!textBox || value != lastValue) {
|
||||
model.set(value);
|
||||
lastValue = model.get();
|
||||
scope.$eval(action);
|
||||
}
|
||||
});
|
||||
}
|
||||
event.type == 'keydown' ? $defer(handler) : handler();
|
||||
});
|
||||
scope.$watch(model.get, function(value){
|
||||
if (lastValue !== value) {
|
||||
scope.$watch(model.get, function(scope, value) {
|
||||
if (!equals(lastValue, value)) {
|
||||
view.set(lastValue = value);
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -693,7 +692,7 @@ angularWidget('select', function(element){
|
|||
|
||||
var isMultiselect = element.attr('multiple'),
|
||||
expression = element.attr('ng:options'),
|
||||
onChange = expressionCompile(element.attr('ng:change') || "").fnSelf,
|
||||
onChange = expressionCompile(element.attr('ng:change') || ""),
|
||||
match;
|
||||
|
||||
if (!expression) {
|
||||
|
|
@ -705,12 +704,12 @@ angularWidget('select', function(element){
|
|||
" but got '" + expression + "'.");
|
||||
}
|
||||
|
||||
var displayFn = expressionCompile(match[2] || match[1]).fnSelf,
|
||||
var displayFn = expressionCompile(match[2] || match[1]),
|
||||
valueName = match[4] || match[6],
|
||||
keyName = match[5],
|
||||
groupByFn = expressionCompile(match[3] || '').fnSelf,
|
||||
valueFn = expressionCompile(match[2] ? match[1] : valueName).fnSelf,
|
||||
valuesFn = expressionCompile(match[7]).fnSelf,
|
||||
groupByFn = expressionCompile(match[3] || ''),
|
||||
valueFn = expressionCompile(match[2] ? match[1] : valueName),
|
||||
valuesFn = expressionCompile(match[7]),
|
||||
// we can't just jqLite('<option>') since jqLite is not smart enough
|
||||
// to create it in <select> and IE barfs otherwise.
|
||||
optionTemplate = jqLite(document.createElement('option')),
|
||||
|
|
@ -773,17 +772,14 @@ angularWidget('select', function(element){
|
|||
onChange(scope);
|
||||
model.set(value);
|
||||
}
|
||||
scope.$tryEval(function(){
|
||||
scope.$root.$eval();
|
||||
});
|
||||
scope.$root.$apply();
|
||||
} finally {
|
||||
tempScope = null; // TODO(misko): needs to be $destroy
|
||||
}
|
||||
});
|
||||
|
||||
scope.$onEval(function(){
|
||||
var scope = this,
|
||||
optionGroups = {'':[]}, // Temporary location for the option groups before we render them
|
||||
scope.$observe(function(scope) {
|
||||
var optionGroups = {'':[]}, // Temporary location for the option groups before we render them
|
||||
optionGroupNames = [''],
|
||||
optionGroupName,
|
||||
optionGroup,
|
||||
|
|
@ -934,7 +930,7 @@ angularWidget('select', function(element){
|
|||
optionGroupsCache.pop()[0].element.remove();
|
||||
}
|
||||
} finally {
|
||||
optionScope = null; // TODO(misko): needs to be $destroy()
|
||||
optionScope.$destroy();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -998,33 +994,36 @@ angularWidget('ng:include', function(element){
|
|||
} else {
|
||||
element[0]['ng:compiled'] = true;
|
||||
return extend(function(xhr, element){
|
||||
var scope = this, childScope;
|
||||
var changeCounter = 0;
|
||||
var preventRecursion = false;
|
||||
function incrementChange(){ changeCounter++;}
|
||||
this.$watch(srcExp, incrementChange);
|
||||
this.$watch(scopeExp, incrementChange);
|
||||
var scope = this,
|
||||
changeCounter = 0,
|
||||
releaseScopes = [],
|
||||
childScope,
|
||||
oldScope;
|
||||
|
||||
// note that this propagates eval to the current childScope, where childScope is dynamically
|
||||
// bound (via $route.onChange callback) to the current scope created by $route
|
||||
scope.$onEval(function(){
|
||||
if (childScope && !preventRecursion) {
|
||||
preventRecursion = true;
|
||||
try {
|
||||
childScope.$eval();
|
||||
} finally {
|
||||
preventRecursion = false;
|
||||
}
|
||||
function incrementChange(){ changeCounter++;}
|
||||
this.$observe(srcExp, incrementChange);
|
||||
this.$observe(function(scope){
|
||||
var newScope = scope.$eval(scopeExp);
|
||||
if (newScope !== oldScope) {
|
||||
oldScope = newScope;
|
||||
incrementChange();
|
||||
}
|
||||
});
|
||||
this.$watch(function(){return changeCounter;}, function(){
|
||||
var src = this.$eval(srcExp),
|
||||
useScope = this.$eval(scopeExp);
|
||||
this.$observe(function(){return changeCounter;}, function(scope) {
|
||||
var src = scope.$eval(srcExp),
|
||||
useScope = scope.$eval(scopeExp);
|
||||
|
||||
while(releaseScopes.length) {
|
||||
releaseScopes.pop().$destroy();
|
||||
}
|
||||
if (src) {
|
||||
xhr('GET', src, null, function(code, response){
|
||||
element.html(response);
|
||||
childScope = useScope || createScope(scope);
|
||||
if (useScope) {
|
||||
childScope = useScope;
|
||||
} else {
|
||||
releaseScopes.push(childScope = scope.$new());
|
||||
}
|
||||
compiler.compile(element)(childScope);
|
||||
scope.$eval(onloadExp);
|
||||
}, false, true);
|
||||
|
|
@ -1091,69 +1090,56 @@ angularWidget('ng:include', function(element){
|
|||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
//TODO(im): remove all the code related to using and inline equals
|
||||
var ngSwitch = angularWidget('ng:switch', function (element){
|
||||
angularWidget('ng:switch', function (element) {
|
||||
var compiler = this,
|
||||
watchExpr = element.attr("on"),
|
||||
usingExpr = (element.attr("using") || 'equals'),
|
||||
usingExprParams = usingExpr.split(":"),
|
||||
usingFn = ngSwitch[usingExprParams.shift()],
|
||||
changeExpr = element.attr('change') || '',
|
||||
cases = [];
|
||||
if (!usingFn) throw "Using expression '" + usingExpr + "' unknown.";
|
||||
if (!watchExpr) throw "Missing 'on' attribute.";
|
||||
eachNode(element, function(caseElement){
|
||||
var when = caseElement.attr('ng:switch-when');
|
||||
var switchCase = {
|
||||
change: changeExpr,
|
||||
element: caseElement,
|
||||
template: compiler.compile(caseElement)
|
||||
};
|
||||
changeExpr = element.attr('change'),
|
||||
casesTemplate = {},
|
||||
defaultCaseTemplate,
|
||||
children = element.children(),
|
||||
length = children.length,
|
||||
child,
|
||||
when;
|
||||
|
||||
if (!watchExpr) throw new Error("Missing 'on' attribute.");
|
||||
while(length--) {
|
||||
child = jqLite(children[length]);
|
||||
// this needs to be here for IE
|
||||
child.remove();
|
||||
when = child.attr('ng:switch-when');
|
||||
if (isString(when)) {
|
||||
switchCase.when = function(scope, value){
|
||||
var args = [value, when];
|
||||
forEach(usingExprParams, function(arg){
|
||||
args.push(arg);
|
||||
});
|
||||
return usingFn.apply(scope, args);
|
||||
};
|
||||
cases.unshift(switchCase);
|
||||
} else if (isString(caseElement.attr('ng:switch-default'))) {
|
||||
switchCase.when = valueFn(true);
|
||||
cases.push(switchCase);
|
||||
casesTemplate[when] = compiler.compile(child);
|
||||
} else if (isString(child.attr('ng:switch-default'))) {
|
||||
defaultCaseTemplate = compiler.compile(child);
|
||||
}
|
||||
});
|
||||
|
||||
// this needs to be here for IE
|
||||
forEach(cases, function(_case){
|
||||
_case.element.remove();
|
||||
});
|
||||
|
||||
}
|
||||
children = null; // release memory;
|
||||
element.html('');
|
||||
|
||||
return function(element){
|
||||
var scope = this, childScope;
|
||||
this.$watch(watchExpr, function(value){
|
||||
var found = false;
|
||||
var changeCounter = 0;
|
||||
var childScope;
|
||||
var selectedTemplate;
|
||||
|
||||
this.$watch(watchExpr, function(scope, value) {
|
||||
element.html('');
|
||||
childScope = createScope(scope);
|
||||
forEach(cases, function(switchCase){
|
||||
if (!found && switchCase.when(childScope, value)) {
|
||||
found = true;
|
||||
childScope.$tryEval(switchCase.change, element);
|
||||
switchCase.template(childScope, function(caseElement){
|
||||
element.append(caseElement);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
scope.$onEval(function(){
|
||||
if (childScope) childScope.$eval();
|
||||
if (selectedTemplate = casesTemplate[value] || defaultCaseTemplate) {
|
||||
changeCounter++;
|
||||
if (childScope) childScope.$destroy();
|
||||
childScope = scope.$new();
|
||||
childScope.$eval(changeExpr);
|
||||
}
|
||||
})();
|
||||
|
||||
this.$observe(function(){return changeCounter;}, function() {
|
||||
element.html('');
|
||||
if (selectedTemplate) {
|
||||
selectedTemplate(childScope, function(caseElement) {
|
||||
element.append(caseElement);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}, {
|
||||
equals: function(on, when) {
|
||||
return ''+on == when;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -1267,15 +1253,16 @@ angularWidget('@ng:repeat', function(expression, element){
|
|||
valueIdent = match[3] || match[1];
|
||||
keyIdent = match[2];
|
||||
|
||||
var children = [], currentScope = this;
|
||||
this.$onEval(function(){
|
||||
var childScopes = [];
|
||||
var childElements = [iterStartElement];
|
||||
var parentScope = this;
|
||||
this.$observe(function(scope){
|
||||
var index = 0,
|
||||
childCount = children.length,
|
||||
lastIterElement = iterStartElement,
|
||||
collection = this.$tryEval(rhs, iterStartElement),
|
||||
childCount = childScopes.length,
|
||||
collection = scope.$eval(rhs),
|
||||
collectionLength = size(collection, true),
|
||||
fragment = (element[0].nodeName != 'OPTION') ? document.createDocumentFragment() : null,
|
||||
addFragment,
|
||||
fragment = document.createDocumentFragment(),
|
||||
addFragmentTo = (childCount < collectionLength) ? childElements[childCount] : null,
|
||||
childScope,
|
||||
key;
|
||||
|
||||
|
|
@ -1283,35 +1270,32 @@ angularWidget('@ng:repeat', function(expression, element){
|
|||
if (collection.hasOwnProperty(key)) {
|
||||
if (index < childCount) {
|
||||
// reuse existing child
|
||||
childScope = children[index];
|
||||
childScope = childScopes[index];
|
||||
childScope[valueIdent] = collection[key];
|
||||
if (keyIdent) childScope[keyIdent] = key;
|
||||
lastIterElement = childScope.$element;
|
||||
childScope.$position = index == 0
|
||||
? 'first'
|
||||
: (index == collectionLength - 1 ? 'last' : 'middle');
|
||||
childScope.$eval();
|
||||
} else {
|
||||
// grow children
|
||||
childScope = createScope(currentScope);
|
||||
childScope = parentScope.$new();
|
||||
childScope[valueIdent] = collection[key];
|
||||
if (keyIdent) childScope[keyIdent] = key;
|
||||
childScope.$index = index;
|
||||
childScope.$position = index == 0
|
||||
? 'first'
|
||||
: (index == collectionLength - 1 ? 'last' : 'middle');
|
||||
children.push(childScope);
|
||||
childScopes.push(childScope);
|
||||
linker(childScope, function(clone){
|
||||
clone.attr('ng:repeat-index', index);
|
||||
|
||||
if (fragment) {
|
||||
fragment.appendChild(clone[0]);
|
||||
addFragment = true;
|
||||
} else {
|
||||
//temporarily preserve old way for option element
|
||||
lastIterElement.after(clone);
|
||||
lastIterElement = clone;
|
||||
}
|
||||
fragment.appendChild(clone[0]);
|
||||
// TODO(misko): Temporary hack - maybe think about it - removed after we add fragment after $flush()
|
||||
// This causes double $flush for children
|
||||
// The first flush will couse a lot of DOM access (initial)
|
||||
// Second flush shuld be noop since nothing has change hence no DOM access.
|
||||
childScope.$flush();
|
||||
childElements[index + 1] = clone;
|
||||
});
|
||||
}
|
||||
index ++;
|
||||
|
|
@ -1319,15 +1303,19 @@ angularWidget('@ng:repeat', function(expression, element){
|
|||
}
|
||||
|
||||
//attach new nodes buffered in doc fragment
|
||||
if (addFragment) {
|
||||
lastIterElement.after(jqLite(fragment));
|
||||
if (addFragmentTo) {
|
||||
// TODO(misko): For performance reasons, we should do the addition after all other widgets
|
||||
// have run. For this should happend after $flush() is done!
|
||||
addFragmentTo.after(jqLite(fragment));
|
||||
}
|
||||
|
||||
// shrink children
|
||||
while(children.length > index) {
|
||||
children.pop().$element.remove();
|
||||
while(childScopes.length > index) {
|
||||
// can not use $destroy(true) since there may be multiple iterators on same parent.
|
||||
childScopes.pop().$destroy();
|
||||
childElements.pop().remove();
|
||||
}
|
||||
}, iterStartElement);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -1438,39 +1426,29 @@ angularWidget('ng:view', function(element) {
|
|||
if (!element[0]['ng:compiled']) {
|
||||
element[0]['ng:compiled'] = true;
|
||||
return annotate('$xhr.cache', '$route', function($xhr, $route, element){
|
||||
var parentScope = this,
|
||||
childScope;
|
||||
var template;
|
||||
var changeCounter = 0;
|
||||
|
||||
$route.onChange(function(){
|
||||
var src;
|
||||
changeCounter++;
|
||||
})(); //initialize the state forcefully, it's possible that we missed the initial
|
||||
//$route#onChange already
|
||||
|
||||
if ($route.current) {
|
||||
src = $route.current.template;
|
||||
childScope = $route.current.scope;
|
||||
}
|
||||
|
||||
if (src) {
|
||||
this.$observe(function(){return changeCounter;}, function() {
|
||||
var template = $route.current && $route.current.template;
|
||||
if (template) {
|
||||
//xhr's callback must be async, see commit history for more info
|
||||
$xhr('GET', src, function(code, response){
|
||||
$xhr('GET', template, function(code, response) {
|
||||
element.html(response);
|
||||
compiler.compile(element)(childScope);
|
||||
compiler.compile(element)($route.current.scope);
|
||||
});
|
||||
} else {
|
||||
element.html('');
|
||||
}
|
||||
})(); //initialize the state forcefully, it's possible that we missed the initial
|
||||
//$route#onChange already
|
||||
|
||||
// note that this propagates eval to the current childScope, where childScope is dynamically
|
||||
// bound (via $route.onChange callback) to the current scope created by $route
|
||||
parentScope.$onEval(function() {
|
||||
if (childScope) {
|
||||
childScope.$eval();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.descend(true);
|
||||
this.directives(true);
|
||||
compiler.descend(true);
|
||||
compiler.directives(true);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ describe('angular', function(){
|
|||
it('should return true if same object', function(){
|
||||
var o = {};
|
||||
expect(equals(o, o)).toEqual(true);
|
||||
expect(equals(1, '1')).toEqual(true);
|
||||
expect(equals(o, {})).toEqual(true);
|
||||
expect(equals(1, '1')).toEqual(false);
|
||||
expect(equals(1, '2')).toEqual(false);
|
||||
});
|
||||
|
||||
|
|
@ -550,6 +551,7 @@ describe('angular', function(){
|
|||
it('should link to existing node and create scope', function(){
|
||||
template = angular.element('<div>{{greeting = "hello world"}}</div>');
|
||||
scope = angular.compile(template)();
|
||||
scope.$flush();
|
||||
expect(template.text()).toEqual('hello world');
|
||||
expect(scope.greeting).toEqual('hello world');
|
||||
});
|
||||
|
|
@ -558,6 +560,7 @@ describe('angular', function(){
|
|||
scope = angular.scope();
|
||||
template = angular.element('<div>{{greeting = "hello world"}}</div>');
|
||||
angular.compile(template)(scope);
|
||||
scope.$flush();
|
||||
expect(template.text()).toEqual('hello world');
|
||||
expect(scope).toEqual(scope);
|
||||
});
|
||||
|
|
@ -572,6 +575,7 @@ describe('angular', function(){
|
|||
templateFn(scope, function(clone){
|
||||
templateClone = clone;
|
||||
});
|
||||
scope.$flush();
|
||||
|
||||
expect(template.text()).toEqual('');
|
||||
expect(scope.$element.text()).toEqual('hello world');
|
||||
|
|
@ -582,7 +586,7 @@ describe('angular', function(){
|
|||
it('should link to cloned node and create scope', function(){
|
||||
scope = angular.scope();
|
||||
template = jqLite('<div>{{greeting = "hello world"}}</div>');
|
||||
angular.compile(template)(scope, noop);
|
||||
angular.compile(template)(scope, noop).$flush();
|
||||
expect(template.text()).toEqual('');
|
||||
expect(scope.$element.text()).toEqual('hello world');
|
||||
expect(scope.greeting).toEqual('hello world');
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
describe('Binder', function(){
|
||||
|
||||
beforeEach(function(){
|
||||
var self = this;
|
||||
|
||||
this.compile = function(html, parent) {
|
||||
this.compile = function(html, parent, logErrors) {
|
||||
if (self.element) dealoc(self.element);
|
||||
var element;
|
||||
if (parent) {
|
||||
|
|
@ -15,7 +14,8 @@ describe('Binder', function(){
|
|||
element = jqLite(html);
|
||||
}
|
||||
self.element = element;
|
||||
return angular.compile(element)();
|
||||
return angular.compile(element)(angular.scope(null,
|
||||
logErrors ? {'$exceptionHandler': $exceptionHandlerMockFactory()} : null));
|
||||
};
|
||||
this.compileToHtml = function (content) {
|
||||
return sortedHtml(this.compile(content).$element);
|
||||
|
|
@ -31,20 +31,20 @@ describe('Binder', function(){
|
|||
|
||||
it('text-field should default to value attribute', function(){
|
||||
var scope = this.compile('<input type="text" name="model.price" value="abc">');
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
assertEquals('abc', scope.model.price);
|
||||
});
|
||||
|
||||
it('ChangingTextareaUpdatesModel', function(){
|
||||
var scope = this.compile('<textarea name="model.note">abc</textarea>');
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
assertEquals(scope.model.note, 'abc');
|
||||
});
|
||||
|
||||
it('ChangingRadioUpdatesModel', function(){
|
||||
var scope = this.compile('<div><input type="radio" name="model.price" value="A" checked>' +
|
||||
'<input type="radio" name="model.price" value="B"></div>');
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
assertEquals(scope.model.price, 'A');
|
||||
});
|
||||
|
||||
|
|
@ -55,7 +55,8 @@ describe('Binder', function(){
|
|||
|
||||
it('BindUpdate', function(){
|
||||
var scope = this.compile('<div ng:eval="a=123"/>');
|
||||
assertEquals(123, scope.$get('a'));
|
||||
scope.$flush();
|
||||
assertEquals(123, scope.a);
|
||||
});
|
||||
|
||||
it('ChangingSelectNonSelectedUpdatesModel', function(){
|
||||
|
|
@ -69,7 +70,7 @@ describe('Binder', function(){
|
|||
'<option value="B" selected>Extra padding</option>' +
|
||||
'<option value="C">Expedite</option>' +
|
||||
'</select>');
|
||||
assertJsonEquals(["A", "B"], scope.$get('Invoice').options);
|
||||
assertJsonEquals(["A", "B"], scope.Invoice.options);
|
||||
});
|
||||
|
||||
it('ChangingSelectSelectedUpdatesModel', function(){
|
||||
|
|
@ -79,19 +80,19 @@ describe('Binder', function(){
|
|||
|
||||
it('ExecuteInitialization', function(){
|
||||
var scope = this.compile('<div ng:init="a=123">');
|
||||
assertEquals(scope.$get('a'), 123);
|
||||
assertEquals(scope.a, 123);
|
||||
});
|
||||
|
||||
it('ExecuteInitializationStatements', function(){
|
||||
var scope = this.compile('<div ng:init="a=123;b=345">');
|
||||
assertEquals(scope.$get('a'), 123);
|
||||
assertEquals(scope.$get('b'), 345);
|
||||
assertEquals(scope.a, 123);
|
||||
assertEquals(scope.b, 345);
|
||||
});
|
||||
|
||||
it('ApplyTextBindings', function(){
|
||||
var scope = this.compile('<div ng:bind="model.a">x</div>');
|
||||
scope.$set('model', {a:123});
|
||||
scope.$eval();
|
||||
scope.model = {a:123};
|
||||
scope.$apply();
|
||||
assertEquals('123', scope.$element.text());
|
||||
});
|
||||
|
||||
|
|
@ -145,7 +146,7 @@ describe('Binder', function(){
|
|||
it('AttributesAreEvaluated', function(){
|
||||
var scope = this.compile('<a ng:bind-attr=\'{"a":"a", "b":"a+b={{a+b}}"}\'></a>');
|
||||
scope.$eval('a=1;b=2');
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
var a = scope.$element;
|
||||
assertEquals(a.attr('a'), 'a');
|
||||
assertEquals(a.attr('b'), 'a+b=3');
|
||||
|
|
@ -154,9 +155,10 @@ describe('Binder', function(){
|
|||
it('InputTypeButtonActionExecutesInScope', function(){
|
||||
var savedCalled = false;
|
||||
var scope = this.compile('<input type="button" ng:click="person.save()" value="Apply">');
|
||||
scope.$set("person.save", function(){
|
||||
scope.person = {};
|
||||
scope.person.save = function(){
|
||||
savedCalled = true;
|
||||
});
|
||||
};
|
||||
browserTrigger(scope.$element, 'click');
|
||||
assertTrue(savedCalled);
|
||||
});
|
||||
|
|
@ -164,9 +166,9 @@ describe('Binder', function(){
|
|||
it('InputTypeButtonActionExecutesInScope2', function(){
|
||||
var log = "";
|
||||
var scope = this.compile('<input type="image" ng:click="action()">');
|
||||
scope.$set("action", function(){
|
||||
scope.action = function(){
|
||||
log += 'click;';
|
||||
});
|
||||
};
|
||||
expect(log).toEqual('');
|
||||
browserTrigger(scope.$element, 'click');
|
||||
expect(log).toEqual('click;');
|
||||
|
|
@ -175,9 +177,10 @@ describe('Binder', function(){
|
|||
it('ButtonElementActionExecutesInScope', function(){
|
||||
var savedCalled = false;
|
||||
var scope = this.compile('<button ng:click="person.save()">Apply</button>');
|
||||
scope.$set("person.save", function(){
|
||||
scope.person = {};
|
||||
scope.person.save = function(){
|
||||
savedCalled = true;
|
||||
});
|
||||
};
|
||||
browserTrigger(scope.$element, 'click');
|
||||
assertTrue(savedCalled);
|
||||
});
|
||||
|
|
@ -186,9 +189,9 @@ describe('Binder', function(){
|
|||
var scope = this.compile('<ul><LI ng:repeat="item in model.items" ng:bind="item.a"/></ul>');
|
||||
var form = scope.$element;
|
||||
var items = [{a:"A"}, {a:"B"}];
|
||||
scope.$set('model', {items:items});
|
||||
scope.model = {items:items};
|
||||
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="0">A</li>' +
|
||||
|
|
@ -196,7 +199,7 @@ describe('Binder', function(){
|
|||
'</ul>', sortedHtml(form));
|
||||
|
||||
items.unshift({a:'C'});
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="0">C</li>' +
|
||||
|
|
@ -205,7 +208,7 @@ describe('Binder', function(){
|
|||
'</ul>', sortedHtml(form));
|
||||
|
||||
items.shift();
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="0">A</li>' +
|
||||
|
|
@ -214,13 +217,13 @@ describe('Binder', function(){
|
|||
|
||||
items.shift();
|
||||
items.shift();
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
});
|
||||
|
||||
it('RepeaterContentDoesNotBind', function(){
|
||||
var scope = this.compile('<ul><LI ng:repeat="item in model.items"><span ng:bind="item.a"></span></li></ul>');
|
||||
scope.$set('model', {items:[{a:"A"}]});
|
||||
scope.$eval();
|
||||
scope.model = {items:[{a:"A"}]};
|
||||
scope.$apply();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:repeat-index="0"><span ng:bind="item.a">A</span></li>' +
|
||||
|
|
@ -234,59 +237,62 @@ describe('Binder', function(){
|
|||
|
||||
it('RepeaterAdd', function(){
|
||||
var scope = this.compile('<div><input type="text" name="item.x" ng:repeat="item in items"></div>');
|
||||
scope.$set('items', [{x:'a'}, {x:'b'}]);
|
||||
scope.$eval();
|
||||
scope.items = [{x:'a'}, {x:'b'}];
|
||||
scope.$apply();
|
||||
var first = childNode(scope.$element, 1);
|
||||
var second = childNode(scope.$element, 2);
|
||||
assertEquals('a', first.val());
|
||||
assertEquals('b', second.val());
|
||||
expect(first.val()).toEqual('a');
|
||||
expect(second.val()).toEqual('b');
|
||||
return
|
||||
|
||||
first.val('ABC');
|
||||
browserTrigger(first, 'keydown');
|
||||
scope.$service('$browser').defer.flush();
|
||||
assertEquals(scope.items[0].x, 'ABC');
|
||||
expect(scope.items[0].x).toEqual('ABC');
|
||||
});
|
||||
|
||||
it('ItShouldRemoveExtraChildrenWhenIteratingOverHash', function(){
|
||||
var scope = this.compile('<div><div ng:repeat="i in items">{{i}}</div></div>');
|
||||
var items = {};
|
||||
scope.$set("items", items);
|
||||
scope.items = items;
|
||||
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
expect(scope.$element[0].childNodes.length - 1).toEqual(0);
|
||||
|
||||
items.name = "misko";
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
expect(scope.$element[0].childNodes.length - 1).toEqual(1);
|
||||
|
||||
delete items.name;
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
expect(scope.$element[0].childNodes.length - 1).toEqual(0);
|
||||
});
|
||||
|
||||
it('IfTextBindingThrowsErrorDecorateTheSpan', function(){
|
||||
var scope = this.compile('<div>{{error.throw()}}</div>');
|
||||
var scope = this.compile('<div>{{error.throw()}}</div>', null, true);
|
||||
var doc = scope.$element;
|
||||
var errorLogs = scope.$service('$log').error.logs;
|
||||
var errorLogs = scope.$service('$exceptionHandler').errors;
|
||||
|
||||
scope.$set('error.throw', function(){throw "ErrorMsg1";});
|
||||
scope.$eval();
|
||||
scope.error = {
|
||||
'throw': function(){throw "ErrorMsg1";}
|
||||
};
|
||||
scope.$apply();
|
||||
var span = childNode(doc, 0);
|
||||
assertTrue(span.hasClass('ng-exception'));
|
||||
assertTrue(!!span.text().match(/ErrorMsg1/));
|
||||
assertTrue(!!span.attr('ng-exception').match(/ErrorMsg1/));
|
||||
assertEquals(['ErrorMsg1'], errorLogs.shift());
|
||||
|
||||
scope.$set('error.throw', function(){throw "MyError";});
|
||||
scope.$eval();
|
||||
scope.error['throw'] = function(){throw "MyError";};
|
||||
scope.$apply();
|
||||
span = childNode(doc, 0);
|
||||
assertTrue(span.hasClass('ng-exception'));
|
||||
assertTrue(span.text(), span.text().match('MyError') !== null);
|
||||
assertEquals('MyError', span.attr('ng-exception'));
|
||||
assertEquals(['MyError'], errorLogs.shift());
|
||||
|
||||
scope.$set('error.throw', function(){return "ok";});
|
||||
scope.$eval();
|
||||
scope.error['throw'] = function(){return "ok";};
|
||||
scope.$apply();
|
||||
assertFalse(span.hasClass('ng-exception'));
|
||||
assertEquals('ok', span.text());
|
||||
assertEquals(null, span.attr('ng-exception'));
|
||||
|
|
@ -294,23 +300,21 @@ describe('Binder', function(){
|
|||
});
|
||||
|
||||
it('IfAttrBindingThrowsErrorDecorateTheAttribute', function(){
|
||||
var scope = this.compile('<div attr="before {{error.throw()}} after"></div>');
|
||||
var scope = this.compile('<div attr="before {{error.throw()}} after"></div>', null, true);
|
||||
var doc = scope.$element;
|
||||
var errorLogs = scope.$service('$log').error.logs;
|
||||
var errorLogs = scope.$service('$exceptionHandler').errors;
|
||||
var count = 0;
|
||||
|
||||
scope.$set('error.throw', function(){throw "ErrorMsg";});
|
||||
scope.$eval();
|
||||
assertTrue('ng-exception', doc.hasClass('ng-exception'));
|
||||
assertEquals('"ErrorMsg"', doc.attr('ng-exception'));
|
||||
assertEquals('before "ErrorMsg" after', doc.attr('attr'));
|
||||
assertEquals(['ErrorMsg'], errorLogs.shift());
|
||||
scope.error = {
|
||||
'throw': function(){throw new Error("ErrorMsg" + (++count));}
|
||||
};
|
||||
scope.$apply();
|
||||
expect(errorLogs.length).toMatch(1);
|
||||
expect(errorLogs.shift()).toMatch(/ErrorMsg1/);
|
||||
|
||||
scope.$set('error.throw', function(){ return 'X';});
|
||||
scope.$eval();
|
||||
assertFalse('!ng-exception', doc.hasClass('ng-exception'));
|
||||
assertEquals('before X after', doc.attr('attr'));
|
||||
assertEquals(null, doc.attr('ng-exception'));
|
||||
assertEquals(0, errorLogs.length);
|
||||
scope.error['throw'] = function(){ return 'X';};
|
||||
scope.$apply();
|
||||
expect(errorLogs.length).toMatch(0);
|
||||
});
|
||||
|
||||
it('NestedRepeater', function(){
|
||||
|
|
@ -318,8 +322,8 @@ describe('Binder', function(){
|
|||
'<ul name="{{i}}" ng:repeat="i in m.item"></ul>' +
|
||||
'</div></div>');
|
||||
|
||||
scope.$set('model', [{name:'a', item:['a1', 'a2']}, {name:'b', item:['b1', 'b2']}]);
|
||||
scope.$eval();
|
||||
scope.model = [{name:'a', item:['a1', 'a2']}, {name:'b', item:['b1', 'b2']}];
|
||||
scope.$apply();
|
||||
|
||||
assertEquals('<div>'+
|
||||
'<#comment></#comment>'+
|
||||
|
|
@ -338,13 +342,13 @@ describe('Binder', function(){
|
|||
it('HideBindingExpression', function(){
|
||||
var scope = this.compile('<div ng:hide="hidden == 3"/>');
|
||||
|
||||
scope.$set('hidden', 3);
|
||||
scope.$eval();
|
||||
scope.hidden = 3;
|
||||
scope.$apply();
|
||||
|
||||
assertHidden(scope.$element);
|
||||
|
||||
scope.$set('hidden', 2);
|
||||
scope.$eval();
|
||||
scope.hidden = 2;
|
||||
scope.$apply();
|
||||
|
||||
assertVisible(scope.$element);
|
||||
});
|
||||
|
|
@ -352,18 +356,18 @@ describe('Binder', function(){
|
|||
it('HideBinding', function(){
|
||||
var scope = this.compile('<div ng:hide="hidden"/>');
|
||||
|
||||
scope.$set('hidden', 'true');
|
||||
scope.$eval();
|
||||
scope.hidden = 'true';
|
||||
scope.$apply();
|
||||
|
||||
assertHidden(scope.$element);
|
||||
|
||||
scope.$set('hidden', 'false');
|
||||
scope.$eval();
|
||||
scope.hidden = 'false';
|
||||
scope.$apply();
|
||||
|
||||
assertVisible(scope.$element);
|
||||
|
||||
scope.$set('hidden', '');
|
||||
scope.$eval();
|
||||
scope.hidden = '';
|
||||
scope.$apply();
|
||||
|
||||
assertVisible(scope.$element);
|
||||
});
|
||||
|
|
@ -371,25 +375,25 @@ describe('Binder', function(){
|
|||
it('ShowBinding', function(){
|
||||
var scope = this.compile('<div ng:show="show"/>');
|
||||
|
||||
scope.$set('show', 'true');
|
||||
scope.$eval();
|
||||
scope.show = 'true';
|
||||
scope.$apply();
|
||||
|
||||
assertVisible(scope.$element);
|
||||
|
||||
scope.$set('show', 'false');
|
||||
scope.$eval();
|
||||
scope.show = 'false';
|
||||
scope.$apply();
|
||||
|
||||
assertHidden(scope.$element);
|
||||
|
||||
scope.$set('show', '');
|
||||
scope.$eval();
|
||||
scope.show = '';
|
||||
scope.$apply();
|
||||
|
||||
assertHidden(scope.$element);
|
||||
});
|
||||
|
||||
it('BindClassUndefined', function(){
|
||||
var scope = this.compile('<div ng:class="undefined"/>');
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
|
||||
assertEquals(
|
||||
'<div class="undefined" ng:class="undefined"></div>',
|
||||
|
|
@ -397,22 +401,22 @@ describe('Binder', function(){
|
|||
});
|
||||
|
||||
it('BindClass', function(){
|
||||
var scope = this.compile('<div ng:class="class"/>');
|
||||
var scope = this.compile('<div ng:class="clazz"/>');
|
||||
|
||||
scope.$set('class', 'testClass');
|
||||
scope.$eval();
|
||||
scope.clazz = 'testClass';
|
||||
scope.$apply();
|
||||
|
||||
assertEquals('<div class="testClass" ng:class="class"></div>', sortedHtml(scope.$element));
|
||||
assertEquals('<div class="testClass" ng:class="clazz"></div>', sortedHtml(scope.$element));
|
||||
|
||||
scope.$set('class', ['a', 'b']);
|
||||
scope.$eval();
|
||||
scope.clazz = ['a', 'b'];
|
||||
scope.$apply();
|
||||
|
||||
assertEquals('<div class="a b" ng:class="class"></div>', sortedHtml(scope.$element));
|
||||
assertEquals('<div class="a b" ng:class="clazz"></div>', sortedHtml(scope.$element));
|
||||
});
|
||||
|
||||
it('BindClassEvenOdd', function(){
|
||||
var scope = this.compile('<div><div ng:repeat="i in [0,1]" ng:class-even="\'e\'" ng:class-odd="\'o\'"></div></div>');
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
var d1 = jqLite(scope.$element[0].childNodes[1]);
|
||||
var d2 = jqLite(scope.$element[0].childNodes[2]);
|
||||
expect(d1.hasClass('o')).toBeTruthy();
|
||||
|
|
@ -428,31 +432,22 @@ describe('Binder', function(){
|
|||
var scope = this.compile('<div ng:style="style"/>');
|
||||
|
||||
scope.$eval('style={height: "10px"}');
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
|
||||
assertEquals("10px", scope.$element.css('height'));
|
||||
|
||||
scope.$eval('style={}');
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
});
|
||||
|
||||
it('ActionOnAHrefThrowsError', function(){
|
||||
var scope = this.compile('<a ng:click="action()">Add Phone</a>');
|
||||
var scope = this.compile('<a ng:click="action()">Add Phone</a>', null, true);
|
||||
scope.action = function(){
|
||||
throw new Error('MyError');
|
||||
};
|
||||
var input = scope.$element;
|
||||
browserTrigger(input, 'click');
|
||||
var error = input.attr('ng-exception');
|
||||
assertTrue(!!error.match(/MyError/));
|
||||
assertTrue("should have an error class", input.hasClass('ng-exception'));
|
||||
assertTrue(!!scope.$service('$log').error.logs.shift()[0].message.match(/MyError/));
|
||||
|
||||
// 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'));
|
||||
expect(scope.$service('$exceptionHandler').errors[0]).toMatch(/MyError/);
|
||||
});
|
||||
|
||||
it('ShoulIgnoreVbNonBindable', function(){
|
||||
|
|
@ -460,16 +455,15 @@ describe('Binder', function(){
|
|||
"<div ng:non-bindable>{{a}}</div>" +
|
||||
"<div ng:non-bindable=''>{{b}}</div>" +
|
||||
"<div ng:non-bindable='true'>{{c}}</div></div>");
|
||||
scope.$set('a', 123);
|
||||
scope.$eval();
|
||||
scope.a = 123;
|
||||
scope.$apply();
|
||||
assertEquals('123{{a}}{{b}}{{c}}', scope.$element.text());
|
||||
});
|
||||
|
||||
|
||||
it('RepeaterShouldBindInputsDefaults', function () {
|
||||
var scope = this.compile('<div><input value="123" name="item.name" ng:repeat="item in items"></div>');
|
||||
scope.$set('items', [{}, {name:'misko'}]);
|
||||
scope.$eval();
|
||||
scope.items = [{}, {name:'misko'}];
|
||||
scope.$apply();
|
||||
|
||||
assertEquals("123", scope.$eval('items[0].name'));
|
||||
assertEquals("misko", scope.$eval('items[1].name'));
|
||||
|
|
@ -477,8 +471,8 @@ describe('Binder', function(){
|
|||
|
||||
it('ShouldTemplateBindPreElements', function () {
|
||||
var scope = this.compile('<pre>Hello {{name}}!</pre>');
|
||||
scope.$set("name", "World");
|
||||
scope.$eval();
|
||||
scope.name = "World";
|
||||
scope.$apply();
|
||||
|
||||
assertEquals('<pre ng:bind-template="Hello {{name}}!">Hello World!</pre>', sortedHtml(scope.$element));
|
||||
});
|
||||
|
|
@ -486,9 +480,9 @@ describe('Binder', function(){
|
|||
it('FillInOptionValueWhenMissing', function(){
|
||||
var scope = this.compile(
|
||||
'<select name="foo"><option selected="true">{{a}}</option><option value="">{{b}}</option><option>C</option></select>');
|
||||
scope.$set('a', 'A');
|
||||
scope.$set('b', 'B');
|
||||
scope.$eval();
|
||||
scope.a = 'A';
|
||||
scope.b = 'B';
|
||||
scope.$apply();
|
||||
var optionA = childNode(scope.$element, 0);
|
||||
var optionB = childNode(scope.$element, 1);
|
||||
var optionC = childNode(scope.$element, 2);
|
||||
|
|
@ -508,39 +502,39 @@ describe('Binder', function(){
|
|||
'<input ng:repeat="item in items" name="item.name" ng:required/></div>',
|
||||
jqLite(document.body));
|
||||
var items = [{}, {}];
|
||||
scope.$set("items", items);
|
||||
scope.$eval();
|
||||
scope.items = items;
|
||||
scope.$apply();
|
||||
assertEquals(3, scope.$service('$invalidWidgets').length);
|
||||
|
||||
scope.$set('name', '');
|
||||
scope.$eval();
|
||||
scope.name = '';
|
||||
scope.$apply();
|
||||
assertEquals(3, scope.$service('$invalidWidgets').length);
|
||||
|
||||
scope.$set('name', ' ');
|
||||
scope.$eval();
|
||||
scope.name = ' ';
|
||||
scope.$apply();
|
||||
assertEquals(3, scope.$service('$invalidWidgets').length);
|
||||
|
||||
scope.$set('name', 'abc');
|
||||
scope.$eval();
|
||||
scope.name = 'abc';
|
||||
scope.$apply();
|
||||
assertEquals(2, scope.$service('$invalidWidgets').length);
|
||||
|
||||
items[0].name = 'abc';
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
assertEquals(1, scope.$service('$invalidWidgets').length);
|
||||
|
||||
items[1].name = 'abc';
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
assertEquals(0, scope.$service('$invalidWidgets').length);
|
||||
});
|
||||
|
||||
it('ValidateOnlyVisibleItems', function(){
|
||||
var scope = this.compile('<div><input name="name" ng:required><input ng:show="show" name="name" ng:required></div>', jqLite(document.body));
|
||||
scope.$set("show", true);
|
||||
scope.$eval();
|
||||
scope.show = true;
|
||||
scope.$apply();
|
||||
assertEquals(2, scope.$service('$invalidWidgets').length);
|
||||
|
||||
scope.$set("show", false);
|
||||
scope.$eval();
|
||||
scope.show = false;
|
||||
scope.$apply();
|
||||
assertEquals(1, scope.$service('$invalidWidgets').visible());
|
||||
});
|
||||
|
||||
|
|
@ -549,7 +543,7 @@ describe('Binder', function(){
|
|||
'<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>');
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
function assertChild(index, disabled) {
|
||||
var child = childNode(scope.$element, index);
|
||||
assertEquals(sortedHtml(child), disabled, !!child.attr('disabled'));
|
||||
|
|
@ -566,7 +560,7 @@ describe('Binder', function(){
|
|||
it('ItShouldDisplayErrorWhenActionIsSyntacticlyIncorrect', function(){
|
||||
var scope = this.compile('<div>' +
|
||||
'<input type="button" ng:click="greeting=\'ABC\'"/>' +
|
||||
'<input type="button" ng:click=":garbage:"/></div>');
|
||||
'<input type="button" ng:click=":garbage:"/></div>', null, true);
|
||||
var first = jqLite(scope.$element[0].childNodes[0]);
|
||||
var second = jqLite(scope.$element[0].childNodes[1]);
|
||||
var errorLogs = scope.$service('$log').error.logs;
|
||||
|
|
@ -576,8 +570,8 @@ describe('Binder', function(){
|
|||
expect(errorLogs).toEqual([]);
|
||||
|
||||
browserTrigger(second, 'click');
|
||||
assertTrue(second.hasClass("ng-exception"));
|
||||
expect(errorLogs.shift()[0]).toMatchError(/Syntax Error: Token ':' not a primary expression/);
|
||||
expect(scope.$service('$exceptionHandler').errors[0]).
|
||||
toMatchError(/Syntax Error: Token ':' not a primary expression/);
|
||||
});
|
||||
|
||||
it('ItShouldSelectTheCorrectRadioBox', function(){
|
||||
|
|
@ -602,7 +596,7 @@ describe('Binder', function(){
|
|||
|
||||
it('ItShouldRepeatOnHashes', function(){
|
||||
var scope = this.compile('<ul><li ng:repeat="(k,v) in {a:0,b:1}" ng:bind=\"k + v\"></li></ul>');
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind=\"k + v\" ng:repeat-index="0">a0</li>' +
|
||||
|
|
@ -613,11 +607,11 @@ describe('Binder', function(){
|
|||
|
||||
it('ItShouldFireChangeListenersBeforeUpdate', function(){
|
||||
var scope = this.compile('<div ng:bind="name"></div>');
|
||||
scope.$set("name", "");
|
||||
scope.name = "";
|
||||
scope.$watch("watched", "name=123");
|
||||
scope.$set("watched", "change");
|
||||
scope.$eval();
|
||||
assertEquals(123, scope.$get("name"));
|
||||
scope.watched = "change";
|
||||
scope.$apply();
|
||||
assertEquals(123, scope.name);
|
||||
assertEquals(
|
||||
'<div ng:bind="name">123</div>',
|
||||
sortedHtml(scope.$element));
|
||||
|
|
@ -625,26 +619,26 @@ describe('Binder', function(){
|
|||
|
||||
it('ItShouldHandleMultilineBindings', function(){
|
||||
var scope = this.compile('<div>{{\n 1 \n + \n 2 \n}}</div>');
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
assertEquals("3", scope.$element.text());
|
||||
});
|
||||
|
||||
it('ItBindHiddenInputFields', function(){
|
||||
var scope = this.compile('<input type="hidden" name="myName" value="abc" />');
|
||||
scope.$eval();
|
||||
assertEquals("abc", scope.$get("myName"));
|
||||
scope.$apply();
|
||||
assertEquals("abc", scope.myName);
|
||||
});
|
||||
|
||||
it('ItShouldUseFormaterForText', function(){
|
||||
var scope = this.compile('<input name="a" ng:format="list" value="a,b">');
|
||||
scope.$eval();
|
||||
assertEquals(['a','b'], scope.$get('a'));
|
||||
scope.$apply();
|
||||
assertEquals(['a','b'], scope.a);
|
||||
var input = scope.$element;
|
||||
input[0].value = ' x,,yz';
|
||||
browserTrigger(input, 'change');
|
||||
assertEquals(['x','yz'], scope.$get('a'));
|
||||
scope.$set('a', [1 ,2, 3]);
|
||||
scope.$eval();
|
||||
assertEquals(['x','yz'], scope.a);
|
||||
scope.a = [1 ,2, 3];
|
||||
scope.$apply();
|
||||
assertEquals('1, 2, 3', input[0].value);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ describe('compiler', function(){
|
|||
};
|
||||
},
|
||||
|
||||
watch: function(expression, element){
|
||||
observe: function(expression, element){
|
||||
return function() {
|
||||
this.$watch(expression, function(val){
|
||||
this.$observe(expression, function(scope, val){
|
||||
if (val)
|
||||
log += ":" + val;
|
||||
});
|
||||
|
|
@ -33,10 +33,12 @@ describe('compiler', function(){
|
|||
};
|
||||
});
|
||||
|
||||
|
||||
afterEach(function(){
|
||||
dealoc(scope);
|
||||
});
|
||||
|
||||
|
||||
it('should not allow compilation of multiple roots', function(){
|
||||
expect(function(){
|
||||
compiler.compile('<div>A</div><span></span>');
|
||||
|
|
@ -46,6 +48,7 @@ describe('compiler', function(){
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
it('should recognize a directive', function(){
|
||||
var e = jqLite('<div directive="expr" ignore="me"></div>');
|
||||
directives.directive = function(expression, element){
|
||||
|
|
@ -63,50 +66,56 @@ describe('compiler', function(){
|
|||
expect(log).toEqual("found:init");
|
||||
});
|
||||
|
||||
|
||||
it('should recurse to children', function(){
|
||||
scope = compile('<div><span hello="misko"/></div>');
|
||||
expect(log).toEqual("hello misko");
|
||||
});
|
||||
|
||||
it('should watch scope', function(){
|
||||
scope = compile('<span watch="name"/>');
|
||||
|
||||
it('should observe scope', function(){
|
||||
scope = compile('<span observe="name">');
|
||||
expect(log).toEqual("");
|
||||
scope.$eval();
|
||||
scope.$set('name', 'misko');
|
||||
scope.$eval();
|
||||
scope.$eval();
|
||||
scope.$set('name', 'adam');
|
||||
scope.$eval();
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
scope.name = 'misko';
|
||||
scope.$flush();
|
||||
scope.$flush();
|
||||
scope.name = 'adam';
|
||||
scope.$flush();
|
||||
scope.$flush();
|
||||
expect(log).toEqual(":misko:adam");
|
||||
});
|
||||
|
||||
|
||||
it('should prevent descend', function(){
|
||||
directives.stop = function(){ this.descend(false); };
|
||||
scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>');
|
||||
expect(log).toEqual("hello misko");
|
||||
});
|
||||
|
||||
|
||||
it('should allow creation of templates', function(){
|
||||
directives.duplicate = function(expr, element){
|
||||
element.replaceWith(document.createComment("marker"));
|
||||
element.removeAttr("duplicate");
|
||||
var linker = this.compile(element);
|
||||
return function(marker) {
|
||||
this.$onEval(function() {
|
||||
this.$observe(function() {
|
||||
var scope = linker(angular.scope(), noop);
|
||||
marker.after(scope.$element);
|
||||
});
|
||||
};
|
||||
};
|
||||
scope = compile('before<span duplicate="expr">x</span>after');
|
||||
scope.$flush();
|
||||
expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span>after</div>');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span><span>x</span>after</div>');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span><span>x</span><span>x</span>after</div>');
|
||||
});
|
||||
|
||||
|
||||
it('should process markup before directives', function(){
|
||||
markup.push(function(text, textNode, parentNode) {
|
||||
if (text == 'middle') {
|
||||
|
|
@ -120,6 +129,7 @@ describe('compiler', function(){
|
|||
expect(log).toEqual("hello middle");
|
||||
});
|
||||
|
||||
|
||||
it('should replace widgets', function(){
|
||||
widgets['NG:BUTTON'] = function(element) {
|
||||
expect(element.hasClass('ng-widget')).toEqual(true);
|
||||
|
|
@ -133,6 +143,7 @@ describe('compiler', function(){
|
|||
expect(log).toEqual('init');
|
||||
});
|
||||
|
||||
|
||||
it('should use the replaced element after calling widget', function(){
|
||||
widgets['H1'] = function(element) {
|
||||
// HTML elements which are augmented by acting as widgets, should not be marked as so
|
||||
|
|
@ -151,6 +162,7 @@ describe('compiler', function(){
|
|||
expect(scope.$element.text()).toEqual('3');
|
||||
});
|
||||
|
||||
|
||||
it('should allow multiple markups per text element', function(){
|
||||
markup.push(function(text, textNode, parent){
|
||||
var index = text.indexOf('---');
|
||||
|
|
@ -174,6 +186,7 @@ describe('compiler', function(){
|
|||
expect(sortedHtml(scope.$element)).toEqual('<div>A<hr></hr>B<hr></hr>C<p></p>D</div>');
|
||||
});
|
||||
|
||||
|
||||
it('should add class for namespace elements', function(){
|
||||
scope = compile('<ng:space>abc</ng:space>');
|
||||
var space = jqLite(scope.$element[0].firstChild);
|
||||
|
|
|
|||
|
|
@ -204,15 +204,15 @@ describe('parser', function() {
|
|||
scope.$eval("1|nonExistant");
|
||||
}).toThrow(new Error("Syntax Error: Token 'nonExistant' should be a function at column 3 of the expression [1|nonExistant] starting at [nonExistant]."));
|
||||
|
||||
scope.$set('offset', 3);
|
||||
scope.offset = 3;
|
||||
expect(scope.$eval("'abcd'|upper._case")).toEqual("ABCD");
|
||||
expect(scope.$eval("'abcd'|substring:1:offset")).toEqual("bc");
|
||||
expect(scope.$eval("'abcd'|substring:1:3|upper._case")).toEqual("BC");
|
||||
});
|
||||
|
||||
it('should access scope', function() {
|
||||
scope.$set('a', 123);
|
||||
scope.$set('b.c', 456);
|
||||
scope.a = 123;
|
||||
scope.b = {c: 456};
|
||||
expect(scope.$eval("a", scope)).toEqual(123);
|
||||
expect(scope.$eval("b.c", scope)).toEqual(456);
|
||||
expect(scope.$eval("x.y.z", scope)).not.toBeDefined();
|
||||
|
|
@ -224,32 +224,32 @@ describe('parser', function() {
|
|||
|
||||
it('should evaluate assignments', function() {
|
||||
expect(scope.$eval("a=12")).toEqual(12);
|
||||
expect(scope.$get("a")).toEqual(12);
|
||||
expect(scope.a).toEqual(12);
|
||||
|
||||
scope = createScope();
|
||||
expect(scope.$eval("x.y.z=123;")).toEqual(123);
|
||||
expect(scope.$get("x.y.z")).toEqual(123);
|
||||
expect(scope.x.y.z).toEqual(123);
|
||||
|
||||
expect(scope.$eval("a=123; b=234")).toEqual(234);
|
||||
expect(scope.$get("a")).toEqual(123);
|
||||
expect(scope.$get("b")).toEqual(234);
|
||||
expect(scope.a).toEqual(123);
|
||||
expect(scope.b).toEqual(234);
|
||||
});
|
||||
|
||||
it('should evaluate function call without arguments', function() {
|
||||
scope.$set('const', function(a,b){return 123;});
|
||||
scope['const'] = function(a,b){return 123;};
|
||||
expect(scope.$eval("const()")).toEqual(123);
|
||||
});
|
||||
|
||||
it('should evaluate function call with arguments', function() {
|
||||
scope.$set('add', function(a,b) {
|
||||
scope.add = function(a,b) {
|
||||
return a+b;
|
||||
});
|
||||
};
|
||||
expect(scope.$eval("add(1,2)")).toEqual(3);
|
||||
});
|
||||
|
||||
it('should evaluate multiplication and division', function() {
|
||||
scope.$set('taxRate', 8);
|
||||
scope.$set('subTotal', 100);
|
||||
scope.taxRate = 8;
|
||||
scope.subTotal = 100;
|
||||
expect(scope.$eval("taxRate / 100 * subTotal")).toEqual(8);
|
||||
expect(scope.$eval("subTotal * taxRate / 100")).toEqual(8);
|
||||
});
|
||||
|
|
@ -297,7 +297,7 @@ describe('parser', function() {
|
|||
return this.a;
|
||||
};
|
||||
|
||||
scope.$set("obj", new C());
|
||||
scope.obj = new C();
|
||||
expect(scope.$eval("obj.getA()")).toEqual(123);
|
||||
});
|
||||
|
||||
|
|
@ -312,29 +312,29 @@ describe('parser', function() {
|
|||
return this.a;
|
||||
};
|
||||
|
||||
scope.$set("obj", new C());
|
||||
scope.obj = new C();
|
||||
expect(scope.$eval("obj.sum(obj.getA())")).toEqual(246);
|
||||
});
|
||||
|
||||
it('should evaluate objects on scope context', function() {
|
||||
scope.$set('a', "abc");
|
||||
scope.a = "abc";
|
||||
expect(scope.$eval("{a:a}").a).toEqual("abc");
|
||||
});
|
||||
|
||||
it('should evaluate field access on function call result', function() {
|
||||
scope.$set('a', function() {
|
||||
scope.a = function() {
|
||||
return {name:'misko'};
|
||||
});
|
||||
};
|
||||
expect(scope.$eval("a().name")).toEqual("misko");
|
||||
});
|
||||
|
||||
it('should evaluate field access after array access', function () {
|
||||
scope.$set('items', [{}, {name:'misko'}]);
|
||||
scope.items = [{}, {name:'misko'}];
|
||||
expect(scope.$eval('items[1].name')).toEqual("misko");
|
||||
});
|
||||
|
||||
it('should evaluate array assignment', function() {
|
||||
scope.$set('items', []);
|
||||
scope.items = [];
|
||||
|
||||
expect(scope.$eval('items[1] = "abc"')).toEqual("abc");
|
||||
expect(scope.$eval('items[1]')).toEqual("abc");
|
||||
|
|
@ -388,7 +388,7 @@ describe('parser', function() {
|
|||
it('should evaluate undefined', function() {
|
||||
expect(scope.$eval("undefined")).not.toBeDefined();
|
||||
expect(scope.$eval("a=undefined")).not.toBeDefined();
|
||||
expect(scope.$get("a")).not.toBeDefined();
|
||||
expect(scope.a).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should allow assignment after array dereference', function(){
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ describe("resource", function() {
|
|||
var xhr, resource, CreditCard, callback, $xhrErr;
|
||||
|
||||
beforeEach(function() {
|
||||
var scope = angular.scope({}, null, {'$xhr.error': $xhrErr = jasmine.createSpy('xhr.error')});
|
||||
var scope = angular.scope(angularService, {'$xhr.error': $xhrErr = jasmine.createSpy('xhr.error')});
|
||||
xhr = scope.$service('$browser').xhr;
|
||||
resource = new ResourceFactory(scope.$service('$xhr'));
|
||||
CreditCard = resource.route('/CreditCard/:id:verb', {id:'@id.key'}, {
|
||||
|
|
|
|||
|
|
@ -15,41 +15,24 @@ describe("ScenarioSpec: Compilation", function(){
|
|||
it("should compile dom node and return scope", function(){
|
||||
var node = jqLite('<div ng:init="a=1">{{b=a+1}}</div>')[0];
|
||||
scope = angular.compile(node)();
|
||||
scope.$flush();
|
||||
expect(scope.a).toEqual(1);
|
||||
expect(scope.b).toEqual(2);
|
||||
});
|
||||
|
||||
it("should compile jQuery node and return scope", function(){
|
||||
scope = compile(jqLite('<div>{{a=123}}</div>'))();
|
||||
scope.$flush();
|
||||
expect(jqLite(scope.$element).text()).toEqual('123');
|
||||
});
|
||||
|
||||
it("should compile text node and return scope", function(){
|
||||
scope = angular.compile('<div>{{a=123}}</div>')();
|
||||
scope.$flush();
|
||||
expect(jqLite(scope.$element).text()).toEqual('123');
|
||||
});
|
||||
});
|
||||
|
||||
describe('scope', function(){
|
||||
it("should have $set, $get, $eval, $updateView methods", function(){
|
||||
scope = angular.compile('<div>{{a}}</div>')();
|
||||
scope.$eval("$invalidWidgets.push({})");
|
||||
expect(scope.$set("a", 2)).toEqual(2);
|
||||
expect(scope.$get("a")).toEqual(2);
|
||||
expect(scope.$eval("a=3")).toEqual(3);
|
||||
scope.$eval();
|
||||
expect(jqLite(scope.$element).text()).toEqual('3');
|
||||
});
|
||||
|
||||
it("should have $ objects", function(){
|
||||
scope = angular.compile('<div></div>')(angular.scope({$config: {a:"b"}}));
|
||||
expect(scope.$service('$location')).toBeDefined();
|
||||
expect(scope.$get('$eval')).toBeDefined();
|
||||
expect(scope.$get('$config')).toBeDefined();
|
||||
expect(scope.$get('$config.a')).toEqual("b");
|
||||
});
|
||||
});
|
||||
|
||||
describe("configuration", function(){
|
||||
it("should take location object", function(){
|
||||
var url = "http://server/#?book=moby";
|
||||
|
|
|
|||
|
|
@ -1,246 +1,497 @@
|
|||
'use strict';
|
||||
|
||||
describe('scope/model', function(){
|
||||
describe('Scope', function(){
|
||||
var root, mockHandler;
|
||||
|
||||
var temp;
|
||||
|
||||
beforeEach(function() {
|
||||
temp = window.temp = {};
|
||||
temp.InjectController = function(exampleService, extra) {
|
||||
this.localService = exampleService;
|
||||
this.extra = extra;
|
||||
this.$root.injectController = this;
|
||||
};
|
||||
temp.InjectController.$inject = ["exampleService"];
|
||||
beforeEach(function(){
|
||||
root = createScope(angular.service, {
|
||||
$updateView: function(){
|
||||
root.$flush();
|
||||
},
|
||||
'$exceptionHandler': $exceptionHandlerMockFactory()
|
||||
});
|
||||
mockHandler = root.$service('$exceptionHandler');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
window.temp = undefined;
|
||||
|
||||
describe('$root', function(){
|
||||
it('should point to itself', function(){
|
||||
expect(root.$root).toEqual(root);
|
||||
expect(root.hasOwnProperty('$root')).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
it('should not have $root on children, but should inherit', function(){
|
||||
var child = root.$new();
|
||||
expect(child.$root).toEqual(root);
|
||||
expect(child.hasOwnProperty('$root')).toBeFalsy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should create a scope with parent', function(){
|
||||
var model = createScope({name:'Misko'});
|
||||
expect(model.name).toEqual('Misko');
|
||||
|
||||
describe('$parent', function(){
|
||||
it('should point to itself in root', function(){
|
||||
expect(root.$root).toEqual(root);
|
||||
});
|
||||
|
||||
|
||||
it('should point to parent', function(){
|
||||
var child = root.$new();
|
||||
expect(root.$parent).toEqual(null);
|
||||
expect(child.$parent).toEqual(root);
|
||||
expect(child.$new().$parent).toEqual(child);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have $get/$set/$parent', function(){
|
||||
var parent = {};
|
||||
var model = createScope(parent);
|
||||
model.$set('name', 'adam');
|
||||
expect(model.name).toEqual('adam');
|
||||
expect(model.$get('name')).toEqual('adam');
|
||||
expect(model.$parent).toEqual(model);
|
||||
expect(model.$root).toEqual(model);
|
||||
|
||||
describe('$id', function(){
|
||||
it('should have a unique id', function(){
|
||||
expect(root.$id < root.$new().$id).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return noop function when LHS is undefined', function(){
|
||||
var model = createScope();
|
||||
expect(model.$eval('x.$filter()')).toEqual(undefined);
|
||||
|
||||
describe('this', function(){
|
||||
it('should have a \'this\'', function(){
|
||||
expect(root['this']).toEqual(root);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('$new()', function(){
|
||||
it('should create a child scope', function(){
|
||||
var child = root.$new();
|
||||
root.a = 123;
|
||||
expect(child.a).toEqual(123);
|
||||
});
|
||||
|
||||
|
||||
it('should instantiate controller and bind functions', function(){
|
||||
function Cntl($browser, name){
|
||||
this.$browser = $browser;
|
||||
this.callCount = 0;
|
||||
this.name = name;
|
||||
}
|
||||
Cntl.$inject = ['$browser'];
|
||||
|
||||
Cntl.prototype = {
|
||||
myFn: function(){
|
||||
expect(this).toEqual(cntl);
|
||||
this.callCount++;
|
||||
}
|
||||
};
|
||||
|
||||
var cntl = root.$new(Cntl, ['misko']);
|
||||
|
||||
expect(root.$browser).toBeUndefined();
|
||||
expect(root.myFn).toBeUndefined();
|
||||
|
||||
expect(cntl.$browser).toBeDefined();
|
||||
expect(cntl.name).toEqual('misko');
|
||||
|
||||
cntl.myFn();
|
||||
cntl.$new().myFn();
|
||||
expect(cntl.callCount).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('$service', function(){
|
||||
it('should have it on root', function(){
|
||||
expect(root.hasOwnProperty('$service')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('$watch/$digest', function(){
|
||||
it('should watch and fire on simple property change', function(){
|
||||
var spy = jasmine.createSpy();
|
||||
root.$watch('name', spy);
|
||||
expect(spy).not.wasCalled();
|
||||
root.$digest();
|
||||
expect(spy).not.wasCalled();
|
||||
root.name = 'misko';
|
||||
root.$digest();
|
||||
expect(spy).wasCalledWith(root, 'misko', undefined);
|
||||
});
|
||||
|
||||
|
||||
it('should watch and fire on expression change', function(){
|
||||
var spy = jasmine.createSpy();
|
||||
root.$watch('name.first', spy);
|
||||
root.name = {};
|
||||
expect(spy).not.wasCalled();
|
||||
root.$digest();
|
||||
expect(spy).not.wasCalled();
|
||||
root.name.first = 'misko';
|
||||
root.$digest();
|
||||
expect(spy).wasCalled();
|
||||
});
|
||||
|
||||
it('should delegate exceptions', function(){
|
||||
root.$watch('a', function(){throw new Error('abc');});
|
||||
root.a = 1;
|
||||
root.$digest();
|
||||
expect(mockHandler.errors[0].message).toEqual('abc');
|
||||
$logMock.error.logs.length = 0;
|
||||
});
|
||||
|
||||
|
||||
it('should fire watches in order of addition', function(){
|
||||
// this is not an external guarantee, just our own sanity
|
||||
var log = '';
|
||||
root.$watch('a', function(){ log += 'a'; });
|
||||
root.$watch('b', function(){ log += 'b'; });
|
||||
root.$watch('c', function(){ log += 'c'; });
|
||||
root.a = root.b = root.c = 1;
|
||||
root.$digest();
|
||||
expect(log).toEqual('abc');
|
||||
});
|
||||
|
||||
|
||||
it('should delegate $digest to children in addition order', function(){
|
||||
// this is not an external guarantee, just our own sanity
|
||||
var log = '';
|
||||
var childA = root.$new();
|
||||
var childB = root.$new();
|
||||
var childC = root.$new();
|
||||
childA.$watch('a', function(){ log += 'a'; });
|
||||
childB.$watch('b', function(){ log += 'b'; });
|
||||
childC.$watch('c', function(){ log += 'c'; });
|
||||
childA.a = childB.b = childC.c = 1;
|
||||
root.$digest();
|
||||
expect(log).toEqual('abc');
|
||||
});
|
||||
|
||||
|
||||
it('should repeat watch cycle while model changes are identified', function(){
|
||||
var log = '';
|
||||
root.$watch('c', function(self, v){self.d = v; log+='c'; });
|
||||
root.$watch('b', function(self, v){self.c = v; log+='b'; });
|
||||
root.$watch('a', function(self, v){self.b = v; log+='a'; });
|
||||
root.a = 1;
|
||||
expect(root.$digest()).toEqual(3);
|
||||
expect(root.b).toEqual(1);
|
||||
expect(root.c).toEqual(1);
|
||||
expect(root.d).toEqual(1);
|
||||
expect(log).toEqual('abc');
|
||||
});
|
||||
|
||||
|
||||
it('should prevent infinite recursion', function(){
|
||||
root.$watch('a', function(self, v){self.b++;});
|
||||
root.$watch('b', function(self, v){self.a++;});
|
||||
root.a = root.b = 0;
|
||||
|
||||
expect(function(){
|
||||
root.$digest();
|
||||
}).toThrow('100 $digest() iterations reached. Aborting!');
|
||||
});
|
||||
|
||||
|
||||
it('should not fire upon $watch registration on initial $digest', function(){
|
||||
var log = '';
|
||||
root.a = 1;
|
||||
root.$watch('a', function(){ log += 'a'; });
|
||||
root.$watch('b', function(){ log += 'b'; });
|
||||
expect(log).toEqual('');
|
||||
expect(root.$digest()).toEqual(0);
|
||||
expect(log).toEqual('');
|
||||
});
|
||||
|
||||
|
||||
it('should return the listener to force a initial watch', function(){
|
||||
var log = '';
|
||||
root.a = 1;
|
||||
root.$watch('a', function(scope, o1, o2){ log += scope.a + ':' + (o1 == o2 == 1) ; })();
|
||||
expect(log).toEqual('1:true');
|
||||
expect(root.$digest()).toEqual(0);
|
||||
expect(log).toEqual('1:true');
|
||||
});
|
||||
|
||||
|
||||
it('should watch objects', function(){
|
||||
var log = '';
|
||||
root.a = [];
|
||||
root.b = {};
|
||||
root.$watch('a', function(){ log +='.';});
|
||||
root.$watch('b', function(){ log +='!';});
|
||||
root.$digest();
|
||||
expect(log).toEqual('');
|
||||
|
||||
root.a.push({});
|
||||
root.b.name = '';
|
||||
|
||||
root.$digest();
|
||||
expect(log).toEqual('.!');
|
||||
});
|
||||
|
||||
|
||||
it('should prevent recursion', function(){
|
||||
var callCount = 0;
|
||||
root.$watch('name', function(){
|
||||
expect(function(){
|
||||
root.$digest();
|
||||
}).toThrow('$digest already in progress');
|
||||
expect(function(){
|
||||
root.$flush();
|
||||
}).toThrow('$digest already in progress');
|
||||
callCount++;
|
||||
});
|
||||
root.name = 'a';
|
||||
root.$digest();
|
||||
expect(callCount).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('$observe/$flush', function(){
|
||||
it('should register simple property observer and fire on change', function(){
|
||||
var spy = jasmine.createSpy();
|
||||
root.$observe('name', spy);
|
||||
expect(spy).not.wasCalled();
|
||||
root.$flush();
|
||||
expect(spy).wasCalled();
|
||||
expect(spy.mostRecentCall.args[0]).toEqual(root);
|
||||
expect(spy.mostRecentCall.args[1]).toEqual(undefined);
|
||||
expect(spy.mostRecentCall.args[2].toString()).toEqual(NaN.toString());
|
||||
root.name = 'misko';
|
||||
root.$flush();
|
||||
expect(spy).wasCalledWith(root, 'misko', undefined);
|
||||
});
|
||||
|
||||
|
||||
it('should register expression observers and fire them on change', function(){
|
||||
var spy = jasmine.createSpy();
|
||||
root.$observe('name.first', spy);
|
||||
root.name = {};
|
||||
expect(spy).not.wasCalled();
|
||||
root.$flush();
|
||||
expect(spy).wasCalled();
|
||||
root.name.first = 'misko';
|
||||
root.$flush();
|
||||
expect(spy).wasCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should delegate exceptions', function(){
|
||||
root.$observe('a', function(){throw new Error('abc');});
|
||||
root.a = 1;
|
||||
root.$flush();
|
||||
expect(mockHandler.errors[0].message).toEqual('abc');
|
||||
$logMock.error.logs.shift();
|
||||
});
|
||||
|
||||
|
||||
it('should fire observers in order of addition', function(){
|
||||
// this is not an external guarantee, just our own sanity
|
||||
var log = '';
|
||||
root.$observe('a', function(){ log += 'a'; });
|
||||
root.$observe('b', function(){ log += 'b'; });
|
||||
root.$observe('c', function(){ log += 'c'; });
|
||||
root.a = root.b = root.c = 1;
|
||||
root.$flush();
|
||||
expect(log).toEqual('abc');
|
||||
});
|
||||
|
||||
|
||||
it('should delegate $flush to children in addition order', function(){
|
||||
// this is not an external guarantee, just our own sanity
|
||||
var log = '';
|
||||
var childA = root.$new();
|
||||
var childB = root.$new();
|
||||
var childC = root.$new();
|
||||
childA.$observe('a', function(){ log += 'a'; });
|
||||
childB.$observe('b', function(){ log += 'b'; });
|
||||
childC.$observe('c', function(){ log += 'c'; });
|
||||
childA.a = childB.b = childC.c = 1;
|
||||
root.$flush();
|
||||
expect(log).toEqual('abc');
|
||||
});
|
||||
|
||||
|
||||
it('should fire observers once at beggining and on change', function(){
|
||||
var log = '';
|
||||
root.$observe('c', function(self, v){self.d = v; log += 'c';});
|
||||
root.$observe('b', function(self, v){self.c = v; log += 'b';});
|
||||
root.$observe('a', function(self, v){self.b = v; log += 'a';});
|
||||
root.a = 1;
|
||||
root.$flush();
|
||||
expect(root.b).toEqual(1);
|
||||
expect(log).toEqual('cba');
|
||||
root.$flush();
|
||||
expect(root.c).toEqual(1);
|
||||
expect(log).toEqual('cbab');
|
||||
root.$flush();
|
||||
expect(root.d).toEqual(1);
|
||||
expect(log).toEqual('cbabc');
|
||||
});
|
||||
|
||||
|
||||
it('should fire on initial observe', function(){
|
||||
var log = '';
|
||||
root.a = 1;
|
||||
root.$observe('a', function(){ log += 'a'; });
|
||||
root.$observe('b', function(){ log += 'b'; });
|
||||
expect(log).toEqual('');
|
||||
root.$flush();
|
||||
expect(log).toEqual('ab');
|
||||
});
|
||||
|
||||
|
||||
it('should observe objects', function(){
|
||||
var log = '';
|
||||
root.a = [];
|
||||
root.b = {};
|
||||
root.$observe('a', function(){ log +='.';});
|
||||
root.$observe('a', function(){ log +='!';});
|
||||
root.$flush();
|
||||
expect(log).toEqual('.!');
|
||||
|
||||
root.$flush();
|
||||
expect(log).toEqual('.!');
|
||||
|
||||
root.a.push({});
|
||||
root.b.name = '';
|
||||
|
||||
root.$digest();
|
||||
expect(log).toEqual('.!');
|
||||
});
|
||||
|
||||
|
||||
it('should prevent recursion', function(){
|
||||
var callCount = 0;
|
||||
root.$observe('name', function(){
|
||||
expect(function(){
|
||||
root.$digest();
|
||||
}).toThrow('$flush already in progress');
|
||||
expect(function(){
|
||||
root.$flush();
|
||||
}).toThrow('$flush already in progress');
|
||||
callCount++;
|
||||
});
|
||||
root.name = 'a';
|
||||
root.$flush();
|
||||
expect(callCount).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('$destroy', function(){
|
||||
var first, middle, last, log;
|
||||
|
||||
beforeEach(function(){
|
||||
log = '';
|
||||
|
||||
first = root.$new();
|
||||
middle = root.$new();
|
||||
last = root.$new();
|
||||
|
||||
first.$watch(function(){ log += '1';});
|
||||
middle.$watch(function(){ log += '2';});
|
||||
last.$watch(function(){ log += '3';});
|
||||
|
||||
log = '';
|
||||
});
|
||||
|
||||
|
||||
it('should ignore remove on root', function(){
|
||||
root.$destroy();
|
||||
root.$digest();
|
||||
expect(log).toEqual('123');
|
||||
});
|
||||
|
||||
|
||||
it('should remove first', function(){
|
||||
first.$destroy();
|
||||
root.$digest();
|
||||
expect(log).toEqual('23');
|
||||
});
|
||||
|
||||
|
||||
it('should remove middle', function(){
|
||||
middle.$destroy();
|
||||
root.$digest();
|
||||
expect(log).toEqual('13');
|
||||
});
|
||||
|
||||
|
||||
it('should remove last', function(){
|
||||
last.$destroy();
|
||||
root.$digest();
|
||||
expect(log).toEqual('12');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('$eval', function(){
|
||||
var model;
|
||||
it('should eval an expression', function(){
|
||||
expect(root.$eval('a=1')).toEqual(1);
|
||||
expect(root.a).toEqual(1);
|
||||
|
||||
beforeEach(function(){model = createScope();});
|
||||
|
||||
it('should eval function with correct this', function(){
|
||||
model.$eval(function(){
|
||||
this.name = 'works';
|
||||
});
|
||||
expect(model.name).toEqual('works');
|
||||
root.$eval(function(self){self.b=2;});
|
||||
expect(root.b).toEqual(2);
|
||||
});
|
||||
|
||||
it('should eval expression with correct this', function(){
|
||||
model.$eval('name="works"');
|
||||
expect(model.name).toEqual('works');
|
||||
});
|
||||
|
||||
it('should not bind regexps', function(){
|
||||
model.exp = /abc/;
|
||||
expect(model.$eval('exp')).toEqual(model.exp);
|
||||
});
|
||||
|
||||
it('should do nothing on empty string and not update view', function(){
|
||||
var onEval = jasmine.createSpy('onEval');
|
||||
model.$onEval(onEval);
|
||||
model.$eval('');
|
||||
expect(onEval).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ignore none string/function', function(){
|
||||
model.$eval(null);
|
||||
model.$eval({});
|
||||
model.$tryEval(null);
|
||||
model.$tryEval({});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('$watch', function(){
|
||||
it('should watch an expression for change', function(){
|
||||
var model = createScope();
|
||||
model.oldValue = "";
|
||||
var nameCount = 0, evalCount = 0;
|
||||
model.name = 'adam';
|
||||
model.$watch('name', function(){ nameCount ++; });
|
||||
model.$watch(function(){return model.name;}, function(newValue, oldValue){
|
||||
this.newValue = newValue;
|
||||
this.oldValue = oldValue;
|
||||
});
|
||||
model.$onEval(function(){evalCount ++;});
|
||||
model.name = 'misko';
|
||||
model.$eval();
|
||||
expect(nameCount).toEqual(2);
|
||||
expect(evalCount).toEqual(1);
|
||||
expect(model.newValue).toEqual('misko');
|
||||
expect(model.oldValue).toEqual('adam');
|
||||
|
||||
describe('$apply', function(){
|
||||
it('should apply expression with full lifecycle', function(){
|
||||
var log = '';
|
||||
var child = root.$new();
|
||||
root.$watch('a', function(scope, a){ log += '1'; });
|
||||
root.$observe('a', function(scope, a){ log += '2'; });
|
||||
child.$apply('$parent.a=0');
|
||||
expect(log).toEqual('12');
|
||||
});
|
||||
|
||||
it('should eval with no arguments', function(){
|
||||
var model = createScope();
|
||||
var count = 0;
|
||||
model.$onEval(function(){count++;});
|
||||
model.$eval();
|
||||
expect(count).toEqual(1);
|
||||
|
||||
it('should catch exceptions', function(){
|
||||
var log = '';
|
||||
var child = root.$new();
|
||||
root.$watch('a', function(scope, a){ log += '1'; });
|
||||
root.$observe('a', function(scope, a){ log += '2'; });
|
||||
root.a = 0;
|
||||
child.$apply(function(){ throw new Error('MyError'); });
|
||||
expect(log).toEqual('12');
|
||||
expect(mockHandler.errors[0].message).toEqual('MyError');
|
||||
$logMock.error.logs.shift();
|
||||
});
|
||||
|
||||
it('should run listener upon registration by default', function() {
|
||||
var model = createScope();
|
||||
var count = 0,
|
||||
nameNewVal = 'crazy val 1',
|
||||
nameOldVal = 'crazy val 2';
|
||||
|
||||
model.$watch('name', function(newVal, oldVal){
|
||||
count ++;
|
||||
nameNewVal = newVal;
|
||||
nameOldVal = oldVal;
|
||||
describe('exceptions', function(){
|
||||
var $exceptionHandler, $updateView, log;
|
||||
beforeEach(function(){
|
||||
log = '';
|
||||
$exceptionHandler = jasmine.createSpy('$exceptionHandler');
|
||||
$updateView = jasmine.createSpy('$updateView');
|
||||
root.$service = function(name) {
|
||||
return {$updateView:$updateView, $exceptionHandler:$exceptionHandler}[name];
|
||||
};
|
||||
root.$watch(function(){ log += '$digest;'; });
|
||||
log = '';
|
||||
});
|
||||
|
||||
expect(count).toBe(1);
|
||||
expect(nameNewVal).not.toBeDefined();
|
||||
expect(nameOldVal).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should not run listener upon registration if flag is passed in', function() {
|
||||
var model = createScope();
|
||||
var count = 0,
|
||||
nameNewVal = 'crazy val 1',
|
||||
nameOldVal = 'crazy val 2';
|
||||
|
||||
model.$watch('name', function(newVal, oldVal){
|
||||
count ++;
|
||||
nameNewVal = newVal;
|
||||
nameOldVal = oldVal;
|
||||
}, undefined, false);
|
||||
|
||||
expect(count).toBe(0);
|
||||
expect(nameNewVal).toBe('crazy val 1');
|
||||
expect(nameOldVal).toBe('crazy val 2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('$bind', function(){
|
||||
it('should curry a function with respect to scope', function(){
|
||||
var model = createScope();
|
||||
model.name = 'misko';
|
||||
expect(model.$bind(function(){return this.name;})()).toEqual('misko');
|
||||
});
|
||||
});
|
||||
|
||||
describe('$tryEval', function(){
|
||||
it('should report error using the provided error handler and $log.error', function(){
|
||||
var scope = createScope(),
|
||||
errorLogs = scope.$service('$log').error.logs;
|
||||
|
||||
scope.$tryEval(function(){throw "myError";}, function(error){
|
||||
scope.error = error;
|
||||
it('should execute and return value and update', function(){
|
||||
root.name = 'abc';
|
||||
expect(root.$apply(function(scope){
|
||||
return scope.name;
|
||||
})).toEqual('abc');
|
||||
expect(log).toEqual('$digest;');
|
||||
expect($exceptionHandler).not.wasCalled();
|
||||
expect($updateView).wasCalled();
|
||||
});
|
||||
expect(scope.error).toEqual('myError');
|
||||
expect(errorLogs.shift()[0]).toBe("myError");
|
||||
});
|
||||
|
||||
it('should report error on visible element', function(){
|
||||
var element = jqLite('<div></div>'),
|
||||
scope = createScope(),
|
||||
errorLogs = scope.$service('$log').error.logs;
|
||||
|
||||
scope.$tryEval(function(){throw "myError";}, element);
|
||||
expect(element.attr('ng-exception')).toEqual('myError');
|
||||
expect(element.hasClass('ng-exception')).toBeTruthy();
|
||||
expect(errorLogs.shift()[0]).toBe("myError");
|
||||
});
|
||||
|
||||
it('should report error on $excetionHandler', function(){
|
||||
var scope = createScope(null, {$exceptionHandler: $exceptionHandlerMockFactory},
|
||||
{$log: $logMock});
|
||||
scope.$tryEval(function(){throw "myError";});
|
||||
expect(scope.$service('$exceptionHandler').errors.shift()).toEqual("myError");
|
||||
expect(scope.$service('$log').error.logs.shift()).toEqual(["myError"]);
|
||||
it('should catch exception and update', function(){
|
||||
var error = new Error('MyError');
|
||||
root.$apply(function(){ throw error; });
|
||||
expect(log).toEqual('$digest;');
|
||||
expect($exceptionHandler).wasCalledWith(error);
|
||||
expect($updateView).wasCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// $onEval
|
||||
describe('$onEval', function(){
|
||||
it("should eval using priority", function(){
|
||||
var scope = createScope();
|
||||
scope.log = "";
|
||||
scope.$onEval('log = log + "middle;"');
|
||||
scope.$onEval(-1, 'log = log + "first;"');
|
||||
scope.$onEval(1, 'log = log + "last;"');
|
||||
scope.$eval();
|
||||
expect(scope.log).toEqual('first;middle;last;');
|
||||
});
|
||||
|
||||
it("should have $root and $parent", function(){
|
||||
var parent = createScope();
|
||||
var scope = createScope(parent);
|
||||
expect(scope.$root).toEqual(parent);
|
||||
expect(scope.$parent).toEqual(parent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getterFn', function(){
|
||||
it('should get chain', function(){
|
||||
expect(getterFn('a.b')(undefined)).toEqual(undefined);
|
||||
expect(getterFn('a.b')({})).toEqual(undefined);
|
||||
expect(getterFn('a.b')({a:null})).toEqual(undefined);
|
||||
expect(getterFn('a.b')({a:{}})).toEqual(undefined);
|
||||
expect(getterFn('a.b')({a:{b:null}})).toEqual(null);
|
||||
expect(getterFn('a.b')({a:{b:0}})).toEqual(0);
|
||||
expect(getterFn('a.b')({a:{b:'abc'}})).toEqual('abc');
|
||||
});
|
||||
|
||||
it('should map type method on top of expression', function(){
|
||||
expect(getterFn('a.$filter')({a:[]})('')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should bind function this', function(){
|
||||
expect(getterFn('a')({a:function($){return this.b + $;}, b:1})(2)).toEqual(3);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('$new', function(){
|
||||
it('should create new child scope and $become controller', function(){
|
||||
var parent = createScope(null, angularService, {exampleService: 'Example Service'});
|
||||
var child = parent.$new(temp.InjectController, 10);
|
||||
expect(child.localService).toEqual('Example Service');
|
||||
expect(child.extra).toEqual(10);
|
||||
|
||||
child.$onEval(function(){ this.run = true; });
|
||||
parent.$eval();
|
||||
expect(child.run).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('$become', function(){
|
||||
it('should inject properties on controller defined in $inject', function(){
|
||||
var parent = createScope(null, angularService, {exampleService: 'Example Service'});
|
||||
var child = createScope(parent);
|
||||
child.$become(temp.InjectController, 10);
|
||||
expect(child.localService).toEqual('Example Service');
|
||||
expect(child.extra).toEqual(10);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
describe('ValidatorTest', function(){
|
||||
describe('Validator', function(){
|
||||
|
||||
it('ShouldHaveThisSet', function() {
|
||||
var validator = {};
|
||||
|
|
@ -11,7 +11,7 @@ describe('ValidatorTest', function(){
|
|||
};
|
||||
var scope = compile('<input name="name" ng:validate="myValidator:\'hevery\'"/>')();
|
||||
scope.name = 'misko';
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
assertEquals('misko', validator.first);
|
||||
assertEquals('hevery', validator.last);
|
||||
expect(validator._this.$id).toEqual(scope.$id);
|
||||
|
|
@ -118,7 +118,7 @@ describe('ValidatorTest', function(){
|
|||
value=v; fn=f;
|
||||
};
|
||||
scope.name = "misko";
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(value).toEqual('misko');
|
||||
expect(input.hasClass('ng-input-indicator-wait')).toBeTruthy();
|
||||
fn("myError");
|
||||
|
|
@ -158,7 +158,7 @@ describe('ValidatorTest', function(){
|
|||
scope.asyncFn = jasmine.createSpy();
|
||||
scope.updateFn = jasmine.createSpy();
|
||||
scope.name = 'misko';
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(scope.asyncFn).toHaveBeenCalledWith('misko', scope.asyncFn.mostRecentCall.args[1]);
|
||||
assertTrue(scope.$element.hasClass('ng-input-indicator-wait'));
|
||||
scope.asyncFn.mostRecentCall.args[1]('myError', {id: 1234, data:'data'});
|
||||
|
|
|
|||
|
|
@ -22,8 +22,9 @@ describe("directive", function(){
|
|||
|
||||
it("should ng:eval", function() {
|
||||
var scope = compile('<div ng:init="a=0" ng:eval="a = a + 1"></div>');
|
||||
scope.$flush();
|
||||
expect(scope.a).toEqual(1);
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(scope.a).toEqual(2);
|
||||
});
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ describe("directive", function(){
|
|||
var scope = compile('<div ng:bind="a"></div>');
|
||||
expect(element.text()).toEqual('');
|
||||
scope.a = 'misko';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.hasClass('ng-binding')).toEqual(true);
|
||||
expect(element.text()).toEqual('misko');
|
||||
});
|
||||
|
|
@ -40,24 +41,24 @@ describe("directive", function(){
|
|||
it('should set text to blank if undefined', function() {
|
||||
var scope = compile('<div ng:bind="a"></div>');
|
||||
scope.a = 'misko';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('misko');
|
||||
scope.a = undefined;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('');
|
||||
});
|
||||
|
||||
it('should set html', function() {
|
||||
var scope = compile('<div ng:bind="html|html"></div>');
|
||||
scope.html = '<div unknown>hello</div>';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(lowercase(element.html())).toEqual('<div>hello</div>');
|
||||
});
|
||||
|
||||
it('should set unsafe html', function() {
|
||||
var scope = compile('<div ng:bind="html|html:\'unsafe\'"></div>');
|
||||
scope.html = '<div onclick="">hello</div>';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(lowercase(element.html())).toEqual('<div onclick="">hello</div>');
|
||||
});
|
||||
|
||||
|
|
@ -66,7 +67,7 @@ describe("directive", function(){
|
|||
return jqLite('<a>hello</a>');
|
||||
};
|
||||
var scope = compile('<div ng:bind="0|myElement"></div>');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(lowercase(element.html())).toEqual('<a>hello</a>');
|
||||
});
|
||||
|
||||
|
|
@ -76,12 +77,14 @@ describe("directive", function(){
|
|||
return 'HELLO';
|
||||
};
|
||||
var scope = compile('<div>before<div ng:bind="0|myFilter"></div>after</div>');
|
||||
scope.$flush();
|
||||
expect(sortedHtml(scope.$element)).toEqual('<div>before<div class="filter" ng:bind="0|myFilter">HELLO</div>after</div>');
|
||||
});
|
||||
|
||||
|
||||
it('should suppress rendering of falsy values', function(){
|
||||
var scope = compile('<div>{{ null }}{{ undefined }}{{ "" }}-{{ 0 }}{{ false }}</div>');
|
||||
scope.$flush();
|
||||
expect(scope.$element.text()).toEqual('-0false');
|
||||
});
|
||||
|
||||
|
|
@ -90,8 +93,8 @@ describe("directive", function(){
|
|||
describe('ng:bind-template', function(){
|
||||
it('should ng:bind-template', function() {
|
||||
var scope = compile('<div ng:bind-template="Hello {{name}}!"></div>');
|
||||
scope.$set('name', 'Misko');
|
||||
scope.$eval();
|
||||
scope.name = 'Misko';
|
||||
scope.$flush();
|
||||
expect(element.hasClass('ng-binding')).toEqual(true);
|
||||
expect(element.text()).toEqual('Hello Misko!');
|
||||
});
|
||||
|
|
@ -103,6 +106,7 @@ describe("directive", function(){
|
|||
return text;
|
||||
};
|
||||
var scope = compile('<div>before<span ng:bind-template="{{\'HELLO\'|myFilter}}">INNER</span>after</div>');
|
||||
scope.$flush();
|
||||
expect(scope.$element.text()).toEqual("beforeHELLOafter");
|
||||
expect(innerText).toEqual('INNER');
|
||||
});
|
||||
|
|
@ -112,12 +116,14 @@ describe("directive", function(){
|
|||
describe('ng:bind-attr', function(){
|
||||
it('should bind attributes', function(){
|
||||
var scope = compile('<div ng:bind-attr="{src:\'http://localhost/mysrc\', alt:\'myalt\'}"/>');
|
||||
scope.$flush();
|
||||
expect(element.attr('src')).toEqual('http://localhost/mysrc');
|
||||
expect(element.attr('alt')).toEqual('myalt');
|
||||
});
|
||||
|
||||
it('should not pretty print JSON in attributes', function(){
|
||||
var scope = compile('<img alt="{{ {a:1} }}"/>');
|
||||
scope.$flush();
|
||||
expect(element.attr('alt')).toEqual('{"a":1}');
|
||||
});
|
||||
});
|
||||
|
|
@ -132,7 +138,7 @@ describe("directive", function(){
|
|||
scope.disabled = true;
|
||||
scope.readonly = true;
|
||||
scope.checked = true;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
|
||||
expect(input.disabled).toEqual(true);
|
||||
expect(input.readOnly).toEqual(true);
|
||||
|
|
@ -142,16 +148,16 @@ describe("directive", function(){
|
|||
describe('ng:click', function(){
|
||||
it('should get called on a click', function(){
|
||||
var scope = compile('<div ng:click="clicked = true"></div>');
|
||||
scope.$eval();
|
||||
expect(scope.$get('clicked')).toBeFalsy();
|
||||
scope.$flush();
|
||||
expect(scope.clicked).toBeFalsy();
|
||||
|
||||
browserTrigger(element, 'click');
|
||||
expect(scope.$get('clicked')).toEqual(true);
|
||||
expect(scope.clicked).toEqual(true);
|
||||
});
|
||||
|
||||
it('should stop event propagation', function() {
|
||||
var scope = compile('<div ng:click="outer = true"><div ng:click="inner = true"></div></div>');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(scope.outer).not.toBeDefined();
|
||||
expect(scope.inner).not.toBeDefined();
|
||||
|
||||
|
|
@ -169,7 +175,7 @@ describe("directive", function(){
|
|||
var scope = compile('<form action="" ng:submit="submitted = true">' +
|
||||
'<input type="submit"/>' +
|
||||
'</form>');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(scope.submitted).not.toBeDefined();
|
||||
|
||||
browserTrigger(element.children()[0]);
|
||||
|
|
@ -177,23 +183,22 @@ describe("directive", function(){
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
describe('ng:class', function() {
|
||||
it('should add new and remove old classes dynamically', function() {
|
||||
var scope = compile('<div class="existing" ng:class="dynClass"></div>');
|
||||
scope.dynClass = 'A';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.hasClass('existing')).toBe(true);
|
||||
expect(element.hasClass('A')).toBe(true);
|
||||
|
||||
scope.dynClass = 'B';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.hasClass('existing')).toBe(true);
|
||||
expect(element.hasClass('A')).toBe(false);
|
||||
expect(element.hasClass('B')).toBe(true);
|
||||
|
||||
delete scope.dynClass;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.hasClass('existing')).toBe(true);
|
||||
expect(element.hasClass('A')).toBe(false);
|
||||
expect(element.hasClass('B')).toBe(false);
|
||||
|
|
@ -201,7 +206,7 @@ describe("directive", function(){
|
|||
|
||||
it('should support adding multiple classes', function(){
|
||||
var scope = compile('<div class="existing" ng:class="[\'A\', \'B\']"></div>');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.hasClass('existing')).toBeTruthy();
|
||||
expect(element.hasClass('A')).toBeTruthy();
|
||||
expect(element.hasClass('B')).toBeTruthy();
|
||||
|
|
@ -211,7 +216,7 @@ describe("directive", function(){
|
|||
|
||||
it('should ng:class odd/even', function(){
|
||||
var scope = compile('<ul><li ng:repeat="i in [0,1]" class="existing" ng:class-odd="\'odd\'" ng:class-even="\'even\'"></li><ul>');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
var e1 = jqLite(element[0].childNodes[1]);
|
||||
var e2 = jqLite(element[0].childNodes[2]);
|
||||
expect(e1.hasClass('existing')).toBeTruthy();
|
||||
|
|
@ -223,32 +228,32 @@ describe("directive", function(){
|
|||
describe('ng:style', function(){
|
||||
it('should set', function(){
|
||||
var scope = compile('<div ng:style="{height: \'40px\'}"></div>');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.css('height')).toEqual('40px');
|
||||
});
|
||||
|
||||
it('should silently ignore undefined style', function() {
|
||||
var scope = compile('<div ng:style="myStyle"></div>');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.hasClass('ng-exception')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should preserve and remove previous style', function(){
|
||||
var scope = compile('<div style="height: 10px;" ng:style="myStyle"></div>');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(getStyle(element)).toEqual({height: '10px'});
|
||||
scope.myStyle = {height: '20px', width: '10px'};
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(getStyle(element)).toEqual({height: '20px', width: '10px'});
|
||||
scope.myStyle = {};
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(getStyle(element)).toEqual({height: '10px'});
|
||||
});
|
||||
});
|
||||
|
||||
it('should silently ignore undefined ng:style', function() {
|
||||
var scope = compile('<div ng:style="myStyle"></div>');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.hasClass('ng-exception')).toBeFalsy();
|
||||
});
|
||||
|
||||
|
|
@ -258,9 +263,10 @@ describe("directive", function(){
|
|||
var element = jqLite('<div ng:show="exp"></div>'),
|
||||
scope = compile(element);
|
||||
|
||||
scope.$flush();
|
||||
expect(isCssVisible(element)).toEqual(false);
|
||||
scope.exp = true;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(isCssVisible(element)).toEqual(true);
|
||||
});
|
||||
|
||||
|
|
@ -271,7 +277,7 @@ describe("directive", function(){
|
|||
|
||||
expect(isCssVisible(element)).toBe(false);
|
||||
scope.exp = true;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(isCssVisible(element)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -283,7 +289,7 @@ describe("directive", function(){
|
|||
|
||||
expect(isCssVisible(element)).toBe(true);
|
||||
scope.exp = true;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(isCssVisible(element)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -333,11 +339,13 @@ describe("directive", function(){
|
|||
expect(scope.greeter.greeting).toEqual('hello');
|
||||
expect(scope.childGreeter.greeting).toEqual('hey');
|
||||
expect(scope.childGreeter.$parent.greeting).toEqual('hello');
|
||||
scope.$flush();
|
||||
expect(scope.$element.text()).toEqual('hey dude!');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
//TODO(misko): this needs to be deleted when ng:eval-order is gone
|
||||
it('should eval things according to ng:eval-order', function(){
|
||||
var scope = compile(
|
||||
'<div ng:init="log=\'\'">' +
|
||||
|
|
@ -348,6 +356,7 @@ describe("directive", function(){
|
|||
'<span bind-template="{{log = log + \'d\'}}"></span>' +
|
||||
'</span>' +
|
||||
'</div>');
|
||||
scope.$flush();
|
||||
expect(scope.log).toEqual('abcde');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -20,24 +20,25 @@ describe("markups", function(){
|
|||
it('should translate {{}} in text', function(){
|
||||
compile('<div>hello {{name}}!</div>');
|
||||
expect(sortedHtml(element)).toEqual('<div>hello <span ng:bind="name"></span>!</div>');
|
||||
scope.$set('name', 'Misko');
|
||||
scope.$eval();
|
||||
scope.name = 'Misko';
|
||||
scope.$flush();
|
||||
expect(sortedHtml(element)).toEqual('<div>hello <span ng:bind="name">Misko</span>!</div>');
|
||||
});
|
||||
|
||||
it('should translate {{}} in terminal nodes', function(){
|
||||
compile('<select name="x"><option value="">Greet {{name}}!</option></select>');
|
||||
scope.$flush();
|
||||
expect(sortedHtml(element).replace(' selected="true"', '')).toEqual('<select name="x"><option ng:bind-template="Greet {{name}}!">Greet !</option></select>');
|
||||
scope.$set('name', 'Misko');
|
||||
scope.$eval();
|
||||
scope.name = 'Misko';
|
||||
scope.$flush();
|
||||
expect(sortedHtml(element).replace(' selected="true"', '')).toEqual('<select name="x"><option ng:bind-template="Greet {{name}}!">Greet Misko!</option></select>');
|
||||
});
|
||||
|
||||
it('should translate {{}} in attributes', function(){
|
||||
compile('<div src="http://server/{{path}}.png"/>');
|
||||
expect(element.attr('ng:bind-attr')).toEqual('{"src":"http://server/{{path}}.png"}');
|
||||
scope.$set('path', 'a/b');
|
||||
scope.$eval();
|
||||
scope.path = 'a/b';
|
||||
scope.$flush();
|
||||
expect(element.attr('src')).toEqual("http://server/a/b.png");
|
||||
});
|
||||
|
||||
|
|
@ -94,57 +95,57 @@ describe("markups", function(){
|
|||
it('should bind disabled', function() {
|
||||
compile('<button ng:disabled="{{isDisabled}}">Button</button>');
|
||||
scope.isDisabled = false;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.attr('disabled')).toBeFalsy();
|
||||
scope.isDisabled = true;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.attr('disabled')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should bind checked', function() {
|
||||
compile('<input type="checkbox" ng:checked="{{isChecked}}" />');
|
||||
scope.isChecked = false;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.attr('checked')).toBeFalsy();
|
||||
scope.isChecked=true;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.attr('checked')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should bind selected', function() {
|
||||
compile('<select><option value=""></option><option ng:selected="{{isSelected}}">Greetings!</option></select>');
|
||||
scope.isSelected=false;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.children()[1].selected).toBeFalsy();
|
||||
scope.isSelected=true;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.children()[1].selected).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should bind readonly', function() {
|
||||
compile('<input type="text" ng:readonly="{{isReadonly}}" />');
|
||||
scope.isReadonly=false;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.attr('readOnly')).toBeFalsy();
|
||||
scope.isReadonly=true;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.attr('readOnly')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should bind multiple', function() {
|
||||
compile('<select ng:multiple="{{isMultiple}}"></select>');
|
||||
scope.isMultiple=false;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.attr('multiple')).toBeFalsy();
|
||||
scope.isMultiple='multiple';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.attr('multiple')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should bind src', function() {
|
||||
compile('<div ng:src="{{url}}" />');
|
||||
scope.url = 'http://localhost/';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.attr('src')).toEqual('http://localhost/');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ angular.service('$log', function() {
|
|||
* this:
|
||||
*
|
||||
* <pre>
|
||||
* var scope = angular.scope(null, {'$exceptionHandler': $exceptionHandlerMockFactory});
|
||||
* var scope = angular.scope(null, {'$exceptionHandler': $exceptionHandlerMockFactory()});
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -31,14 +31,13 @@ describe('angular.scenario.SpecRunner', function() {
|
|||
$window.setTimeout = function(fn, timeout) {
|
||||
fn();
|
||||
};
|
||||
$root = angular.scope({
|
||||
emit: function(eventName) {
|
||||
log.push(eventName);
|
||||
},
|
||||
on: function(eventName) {
|
||||
log.push('Listener Added for ' + eventName);
|
||||
}
|
||||
});
|
||||
$root = angular.scope();
|
||||
$root.emit = function(eventName) {
|
||||
log.push(eventName);
|
||||
};
|
||||
$root.on = function(eventName) {
|
||||
log.push('Listener Added for ' + eventName);
|
||||
};
|
||||
$root.application = new ApplicationMock($window);
|
||||
$root.$window = $window;
|
||||
runner = $root.$new(angular.scenario.SpecRunner);
|
||||
|
|
|
|||
|
|
@ -10,14 +10,13 @@ describe("angular.scenario.dsl", function() {
|
|||
document: _jQuery("<div></div>"),
|
||||
angular: new angular.scenario.testing.MockAngular()
|
||||
};
|
||||
$root = angular.scope({
|
||||
emit: function(eventName) {
|
||||
eventLog.push(eventName);
|
||||
},
|
||||
on: function(eventName) {
|
||||
eventLog.push('Listener Added for ' + eventName);
|
||||
}
|
||||
});
|
||||
$root = angular.scope();
|
||||
$root.emit = function(eventName) {
|
||||
eventLog.push(eventName);
|
||||
};
|
||||
$root.on = function(eventName) {
|
||||
eventLog.push('Listener Added for ' + eventName);
|
||||
};
|
||||
$root.futures = [];
|
||||
$root.futureLog = [];
|
||||
$root.$window = $window;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ describe('$cookieStore', function() {
|
|||
|
||||
it('should serialize objects to json', function() {
|
||||
$cookieStore.put('objectCookie', {id: 123, name: 'blah'});
|
||||
scope.$eval(); //force eval in test
|
||||
scope.$flush();
|
||||
expect($browser.cookies()).toEqual({'objectCookie': '{"id":123,"name":"blah"}'});
|
||||
});
|
||||
|
||||
|
|
@ -30,12 +30,12 @@ describe('$cookieStore', function() {
|
|||
|
||||
it('should delete objects from the store when remove is called', function() {
|
||||
$cookieStore.put('gonner', { "I'll":"Be Back"});
|
||||
scope.$eval(); //force eval in test
|
||||
scope.$flush(); //force eval in test
|
||||
$browser.poll();
|
||||
expect($browser.cookies()).toEqual({'gonner': '{"I\'ll":"Be Back"}'});
|
||||
|
||||
$cookieStore.remove('gonner');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect($browser.cookies()).toEqual({});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ describe('$cookies', function() {
|
|||
beforeEach(function() {
|
||||
$browser = new MockBrowser();
|
||||
$browser.cookieHash['preexisting'] = 'oldCookie';
|
||||
scope = angular.scope(null, angular.service, {$browser: $browser});
|
||||
scope = angular.scope(angular.service, {$browser: $browser});
|
||||
scope.$cookies = scope.$service('$cookies');
|
||||
});
|
||||
|
||||
|
|
@ -38,13 +38,13 @@ describe('$cookies', function() {
|
|||
|
||||
it('should create or update a cookie when a value is assigned to a property', function() {
|
||||
scope.$cookies.oatmealCookie = 'nom nom';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
|
||||
expect($browser.cookies()).
|
||||
toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'});
|
||||
|
||||
scope.$cookies.oatmealCookie = 'gone';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
|
||||
expect($browser.cookies()).
|
||||
toEqual({'preexisting': 'oldCookie', 'oatmealCookie': 'gone'});
|
||||
|
|
@ -56,7 +56,7 @@ describe('$cookies', function() {
|
|||
scope.$cookies.nullVal = null;
|
||||
scope.$cookies.undefVal = undefined;
|
||||
scope.$cookies.preexisting = function(){};
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'});
|
||||
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'});
|
||||
});
|
||||
|
|
@ -64,13 +64,13 @@ describe('$cookies', function() {
|
|||
|
||||
it('should remove a cookie when a $cookies property is deleted', function() {
|
||||
scope.$cookies.oatmealCookie = 'nom nom';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
$browser.poll();
|
||||
expect($browser.cookies()).
|
||||
toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'});
|
||||
|
||||
delete scope.$cookies.oatmealCookie;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
|
||||
expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'});
|
||||
});
|
||||
|
|
@ -85,16 +85,16 @@ describe('$cookies', function() {
|
|||
|
||||
//drop if no previous value
|
||||
scope.$cookies.longCookie = longVal;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'});
|
||||
|
||||
|
||||
//reset if previous value existed
|
||||
scope.$cookies.longCookie = 'shortVal';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'});
|
||||
scope.$cookies.longCookie = longVal;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ describe('$defer', function() {
|
|||
var scope, $browser, $defer, $exceptionHandler;
|
||||
|
||||
beforeEach(function(){
|
||||
scope = angular.scope({}, angular.service,
|
||||
scope = angular.scope(angular.service,
|
||||
{'$exceptionHandler': jasmine.createSpy('$exceptionHandler')});
|
||||
$browser = scope.$service('$browser');
|
||||
$defer = scope.$service('$defer');
|
||||
|
|
@ -41,32 +41,32 @@ describe('$defer', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should call eval after each callback is executed', function() {
|
||||
var evalSpy = this.spyOn(scope, '$eval').andCallThrough();
|
||||
it('should call $apply after each callback is executed', function() {
|
||||
var applySpy = this.spyOn(scope, '$apply').andCallThrough();
|
||||
|
||||
$defer(function() {});
|
||||
expect(evalSpy).not.toHaveBeenCalled();
|
||||
expect(applySpy).not.toHaveBeenCalled();
|
||||
|
||||
$browser.defer.flush();
|
||||
expect(evalSpy).toHaveBeenCalled();
|
||||
expect(applySpy).toHaveBeenCalled();
|
||||
|
||||
evalSpy.reset(); //reset the spy;
|
||||
applySpy.reset(); //reset the spy;
|
||||
|
||||
$defer(function() {});
|
||||
$defer(function() {});
|
||||
$browser.defer.flush();
|
||||
expect(evalSpy.callCount).toBe(2);
|
||||
expect(applySpy.callCount).toBe(2);
|
||||
});
|
||||
|
||||
|
||||
it('should call eval even if an exception is thrown in callback', function() {
|
||||
var evalSpy = this.spyOn(scope, '$eval').andCallThrough();
|
||||
it('should call $apply even if an exception is thrown in callback', function() {
|
||||
var applySpy = this.spyOn(scope, '$apply').andCallThrough();
|
||||
|
||||
$defer(function() {throw "Test Error";});
|
||||
expect(evalSpy).not.toHaveBeenCalled();
|
||||
expect(applySpy).not.toHaveBeenCalled();
|
||||
|
||||
$browser.defer.flush();
|
||||
expect(evalSpy).toHaveBeenCalled();
|
||||
expect(applySpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should allow you to specify the delay time', function(){
|
||||
|
|
|
|||
|
|
@ -14,11 +14,12 @@ describe('$exceptionHandler', function() {
|
|||
|
||||
|
||||
it('should log errors', function(){
|
||||
var scope = createScope({}, {$exceptionHandler: $exceptionHandlerFactory},
|
||||
{$log: $logMock}),
|
||||
var scope = createScope({$exceptionHandler: $exceptionHandlerFactory},
|
||||
{$log: $logMock}),
|
||||
$log = scope.$service('$log'),
|
||||
$exceptionHandler = scope.$service('$exceptionHandler');
|
||||
|
||||
$log.error.rethrow = false;
|
||||
$exceptionHandler('myError');
|
||||
expect($log.error.logs.shift()).toEqual(['myError']);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,21 +21,21 @@ describe('$invalidWidgets', function() {
|
|||
expect($invalidWidgets.length).toEqual(1);
|
||||
|
||||
scope.price = 123;
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect($invalidWidgets.length).toEqual(0);
|
||||
|
||||
scope.$element.remove();
|
||||
scope.price = 'abc';
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect($invalidWidgets.length).toEqual(0);
|
||||
|
||||
jqLite(document.body).append(scope.$element);
|
||||
scope.price = 'abcd'; //force revalidation, maybe this should be done automatically?
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect($invalidWidgets.length).toEqual(1);
|
||||
|
||||
jqLite(document.body).html('');
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect($invalidWidgets.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -46,9 +46,10 @@ describe('$location', function() {
|
|||
$location.update('http://www.angularjs.org/');
|
||||
$location.update({path: '/a/b'});
|
||||
expect($location.href).toEqual('http://www.angularjs.org/a/b');
|
||||
expect($browser.getUrl()).toEqual(origBrowserUrl);
|
||||
scope.$eval();
|
||||
expect($browser.getUrl()).toEqual('http://www.angularjs.org/a/b');
|
||||
$location.path = '/c';
|
||||
scope.$digest();
|
||||
expect($browser.getUrl()).toEqual('http://www.angularjs.org/c');
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -65,7 +66,7 @@ describe('$location', function() {
|
|||
|
||||
it('should update hash on hashPath or hashSearch update', function() {
|
||||
$location.update('http://server/#path?a=b');
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
$location.update({hashPath: '', hashSearch: {}});
|
||||
|
||||
expect($location.hash).toEqual('');
|
||||
|
|
@ -74,10 +75,10 @@ describe('$location', function() {
|
|||
|
||||
it('should update hashPath and hashSearch on $location.hash change upon eval', function(){
|
||||
$location.update('http://server/#path?a=b');
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
|
||||
$location.hash = '';
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
|
||||
expect($location.href).toEqual('http://server/');
|
||||
expect($location.hashPath).toEqual('');
|
||||
|
|
@ -88,11 +89,13 @@ describe('$location', function() {
|
|||
it('should update hash on $location.hashPath or $location.hashSearch change upon eval',
|
||||
function() {
|
||||
$location.update('http://server/#path?a=b');
|
||||
scope.$eval();
|
||||
expect($location.href).toEqual('http://server/#path?a=b');
|
||||
expect($location.hashPath).toEqual('path');
|
||||
expect($location.hashSearch).toEqual({a:'b'});
|
||||
|
||||
$location.hashPath = '';
|
||||
$location.hashSearch = {};
|
||||
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
|
||||
expect($location.href).toEqual('http://server/');
|
||||
expect($location.hash).toEqual('');
|
||||
|
|
@ -103,14 +106,14 @@ describe('$location', function() {
|
|||
scope.$location = scope.$service('$location'); //publish to the scope for $watch
|
||||
|
||||
var log = '';
|
||||
scope.$watch('$location.hash', function(){
|
||||
log += this.$location.hashPath + ';';
|
||||
});
|
||||
scope.$watch('$location.hash', function(scope){
|
||||
log += scope.$location.hashPath + ';';
|
||||
})();
|
||||
expect(log).toEqual(';');
|
||||
|
||||
log = '';
|
||||
scope.$location.hash = '/abc';
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(scope.$location.hash).toEqual('/abc');
|
||||
expect(log).toEqual('/abc;');
|
||||
});
|
||||
|
|
@ -120,7 +123,7 @@ describe('$location', function() {
|
|||
|
||||
it('should update hash with escaped hashPath', function() {
|
||||
$location.hashPath = 'foo=bar';
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect($location.hash).toBe('foo%3Dbar');
|
||||
});
|
||||
|
||||
|
|
@ -133,7 +136,7 @@ describe('$location', function() {
|
|||
$location.host = 'host';
|
||||
$location.href = 'https://hrefhost:23/hrefpath';
|
||||
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
|
||||
expect($location).toEqualData({href: 'https://hrefhost:23/hrefpath',
|
||||
protocol: 'https',
|
||||
|
|
@ -156,7 +159,7 @@ describe('$location', function() {
|
|||
$location.host = 'host';
|
||||
$location.path = '/path';
|
||||
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
|
||||
expect($location).toEqualData({href: 'http://host:333/path#hash',
|
||||
protocol: 'http',
|
||||
|
|
@ -237,7 +240,7 @@ describe('$location', function() {
|
|||
expect($location.href).toBe('http://server');
|
||||
expect($location.hash).toBe('');
|
||||
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
|
||||
expect($location.href).toBe('http://server');
|
||||
expect($location.hash).toBe('');
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ describe('$log', function() {
|
|||
function warn(){ logger+= 'warn;'; }
|
||||
function info(){ logger+= 'info;'; }
|
||||
function error(){ logger+= 'error;'; }
|
||||
var scope = createScope({}, {$log: $logFactory},
|
||||
{$exceptionHandler: rethrow,
|
||||
$window: {console: {log: log,
|
||||
warn: warn,
|
||||
info: info,
|
||||
error: error}}}),
|
||||
var scope = createScope({$log: $logFactory},
|
||||
{$exceptionHandler: rethrow,
|
||||
$window: {console: {log: log,
|
||||
warn: warn,
|
||||
info: info,
|
||||
error: error}}}),
|
||||
$log = scope.$service('$log');
|
||||
|
||||
$log.log();
|
||||
|
|
@ -38,9 +38,9 @@ describe('$log', function() {
|
|||
it('should use console.log() if other not present', function(){
|
||||
var logger = "";
|
||||
function log(){ logger+= 'log;'; }
|
||||
var scope = createScope({}, {$log: $logFactory},
|
||||
{$window: {console:{log:log}},
|
||||
$exceptionHandler: rethrow});
|
||||
var scope = createScope({$log: $logFactory},
|
||||
{$window: {console:{log:log}},
|
||||
$exceptionHandler: rethrow});
|
||||
var $log = scope.$service('$log');
|
||||
$log.log();
|
||||
$log.warn();
|
||||
|
|
@ -51,9 +51,9 @@ describe('$log', function() {
|
|||
|
||||
|
||||
it('should use noop if no console', function(){
|
||||
var scope = createScope({}, {$log: $logFactory},
|
||||
{$window: {},
|
||||
$exceptionHandler: rethrow}),
|
||||
var scope = createScope({$log: $logFactory},
|
||||
{$window: {},
|
||||
$exceptionHandler: rethrow}),
|
||||
$log = scope.$service('$log');
|
||||
$log.log();
|
||||
$log.warn();
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ describe('$route', function() {
|
|||
$location, $route;
|
||||
|
||||
function BookChapter() {
|
||||
this.log = '<init>';
|
||||
log += '<init>';
|
||||
}
|
||||
scope = compile('<div></div>')();
|
||||
$location = scope.$service('$location');
|
||||
|
|
@ -28,28 +28,28 @@ describe('$route', function() {
|
|||
$route.onChange(function(){
|
||||
log += 'onChange();';
|
||||
});
|
||||
|
||||
$location.update('http://server#/Book/Moby/Chapter/Intro?p=123');
|
||||
scope.$eval();
|
||||
expect(log).toEqual('onChange();');
|
||||
scope.$digest();
|
||||
expect(log).toEqual('onChange();<init>');
|
||||
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'});
|
||||
expect($route.current.scope.log).toEqual('<init>');
|
||||
var lastId = $route.current.scope.$id;
|
||||
|
||||
log = '';
|
||||
$location.update('http://server#/Blank?ignore');
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(log).toEqual('onChange();');
|
||||
expect($route.current.params).toEqual({ignore:true});
|
||||
expect($route.current.scope.$id).not.toEqual(lastId);
|
||||
|
||||
log = '';
|
||||
$location.update('http://server#/NONE');
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(log).toEqual('onChange();');
|
||||
expect($route.current).toEqual(null);
|
||||
|
||||
$route.when('/NONE', {template:'instant update'});
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect($route.current.template).toEqual('instant update');
|
||||
});
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ describe('$route', function() {
|
|||
expect(onChangeSpy).not.toHaveBeenCalled();
|
||||
|
||||
$location.updateHash('/foo');
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
|
||||
expect($route.current.template).toEqual('foo.html');
|
||||
expect($route.current.controller).toBeUndefined();
|
||||
|
|
@ -98,7 +98,7 @@ describe('$route', function() {
|
|||
expect(onChangeSpy).not.toHaveBeenCalled();
|
||||
|
||||
$location.updateHash('/unknownRoute');
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
|
||||
expect($route.current.template).toBe('404.html');
|
||||
expect($route.current.controller).toBe(NotFoundCtrl);
|
||||
|
|
@ -107,7 +107,7 @@ describe('$route', function() {
|
|||
|
||||
onChangeSpy.reset();
|
||||
$location.updateHash('/foo');
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
|
||||
expect($route.current.template).toEqual('foo.html');
|
||||
expect($route.current.controller).toBeUndefined();
|
||||
|
|
@ -115,6 +115,39 @@ describe('$route', function() {
|
|||
expect(onChangeSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should $destroy old routes', function(){
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$route = scope.$service('$route');
|
||||
|
||||
$route.when('/foo', {template: 'foo.html', controller: function(){ this.name = 'FOO';}});
|
||||
$route.when('/bar', {template: 'bar.html', controller: function(){ this.name = 'BAR';}});
|
||||
$route.when('/baz', {template: 'baz.html'});
|
||||
|
||||
expect(scope.$childHead).toEqual(null);
|
||||
|
||||
$location.updateHash('/foo');
|
||||
scope.$digest();
|
||||
expect(scope.$$childHead).toBeTruthy();
|
||||
expect(scope.$$childHead).toEqual(scope.$$childTail);
|
||||
|
||||
$location.updateHash('/bar');
|
||||
scope.$digest();
|
||||
expect(scope.$$childHead).toBeTruthy();
|
||||
expect(scope.$$childHead).toEqual(scope.$$childTail);
|
||||
return
|
||||
|
||||
$location.updateHash('/baz');
|
||||
scope.$digest();
|
||||
expect(scope.$$childHead).toBeTruthy();
|
||||
expect(scope.$$childHead).toEqual(scope.$$childTail);
|
||||
|
||||
$location.updateHash('/');
|
||||
scope.$digest();
|
||||
expect(scope.$$childHead).toEqual(null);
|
||||
expect(scope.$$childTail).toEqual(null);
|
||||
});
|
||||
|
||||
|
||||
describe('redirection', function() {
|
||||
|
||||
|
|
@ -134,7 +167,7 @@ describe('$route', function() {
|
|||
expect($route.current).toBeNull();
|
||||
expect(onChangeSpy).not.toHaveBeenCalled();
|
||||
|
||||
scope.$eval(); //triggers initial route change - match the redirect route
|
||||
scope.$digest(); //triggers initial route change - match the redirect route
|
||||
$browser.defer.flush(); //triger route change - match the route we redirected to
|
||||
|
||||
expect($location.hash).toBe('/foo');
|
||||
|
|
@ -143,7 +176,7 @@ describe('$route', function() {
|
|||
|
||||
onChangeSpy.reset();
|
||||
$location.updateHash('');
|
||||
scope.$eval(); //match the redirect route + update $browser
|
||||
scope.$digest(); //match the redirect route + update $browser
|
||||
$browser.defer.flush(); //match the route we redirected to
|
||||
|
||||
expect($location.hash).toBe('/foo');
|
||||
|
|
@ -152,7 +185,7 @@ describe('$route', function() {
|
|||
|
||||
onChangeSpy.reset();
|
||||
$location.updateHash('/baz');
|
||||
scope.$eval(); //match the redirect route + update $browser
|
||||
scope.$digest(); //match the redirect route + update $browser
|
||||
$browser.defer.flush(); //match the route we redirected to
|
||||
|
||||
expect($location.hash).toBe('/bar');
|
||||
|
|
@ -170,10 +203,10 @@ describe('$route', function() {
|
|||
|
||||
$route.when('/foo/:id/foo/:subid/:extraId', {redirectTo: '/bar/:id/:subid/23'});
|
||||
$route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'});
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
|
||||
$location.updateHash('/foo/id1/foo/subid3/gah');
|
||||
scope.$eval(); //triggers initial route change - match the redirect route
|
||||
scope.$digest(); //triggers initial route change - match the redirect route
|
||||
$browser.defer.flush(); //triger route change - match the route we redirected to
|
||||
|
||||
expect($location.hash).toBe('/bar/id1/subid3/23?extraId=gah');
|
||||
|
|
@ -190,10 +223,10 @@ describe('$route', function() {
|
|||
|
||||
$route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'});
|
||||
$route.when('/foo/:id/:extra', {redirectTo: '/bar/:id/:subid/99'});
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
|
||||
$location.hash = '/foo/id3/eId?subid=sid1&appended=true';
|
||||
scope.$eval(); //triggers initial route change - match the redirect route
|
||||
scope.$digest(); //triggers initial route change - match the redirect route
|
||||
$browser.defer.flush(); //triger route change - match the route we redirected to
|
||||
|
||||
expect($location.hash).toBe('/bar/id3/sid1/99?appended=true&extra=eId');
|
||||
|
|
@ -210,10 +243,10 @@ describe('$route', function() {
|
|||
$route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'});
|
||||
$route.when('/foo/:id',
|
||||
{redirectTo: customRedirectFn});
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
|
||||
$location.hash = '/foo/id3?subid=sid1&appended=true';
|
||||
scope.$eval(); //triggers initial route change - match the redirect route
|
||||
scope.$digest(); //triggers initial route change - match the redirect route
|
||||
$browser.defer.flush(); //triger route change - match the route we redirected to
|
||||
|
||||
expect($location.hash).toBe('custom');
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ describe('$updateView', function() {
|
|||
browser.isMock = false;
|
||||
browser.defer = jasmine.createSpy('defer');
|
||||
|
||||
scope = angular.scope(null, null, {$browser:browser});
|
||||
scope = angular.scope(null, {$browser:browser});
|
||||
$updateView = scope.$service('$updateView');
|
||||
scope.$onEval(function(){ evalCount++; });
|
||||
scope.$observe(function(){ evalCount++; });
|
||||
evalCount = 0;
|
||||
});
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ describe('$updateView', function() {
|
|||
|
||||
it('should update immediatelly in test/mock mode', function(){
|
||||
scope = angular.scope();
|
||||
scope.$onEval(function(){ evalCount++; });
|
||||
scope.$observe(function(){ evalCount++; });
|
||||
expect(evalCount).toEqual(0);
|
||||
scope.$service('$updateView')();
|
||||
expect(evalCount).toEqual(1);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ describe('$xhr.bulk', function() {
|
|||
var scope, $browser, $browserXhr, $log, $xhrBulk, $xhrError, log;
|
||||
|
||||
beforeEach(function(){
|
||||
scope = angular.scope({}, null, {'$xhr.error': $xhrError = jasmine.createSpy('$xhr.error')});
|
||||
scope = angular.scope(angular.service, {
|
||||
'$xhr.error': $xhrError = jasmine.createSpy('$xhr.error'),
|
||||
'$log': $log = {}
|
||||
});
|
||||
$browser = scope.$service('$browser');
|
||||
$browserXhr = $browser.xhr;
|
||||
$xhrBulk = scope.$service('$xhr.bulk');
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ describe('$xhr.cache', function() {
|
|||
var scope, $browser, $browserXhr, $xhrErr, cache, log;
|
||||
|
||||
beforeEach(function() {
|
||||
scope = angular.scope({}, null, {'$xhr.error': $xhrErr = jasmine.createSpy('$xhr.error')});
|
||||
scope = angular.scope(angularService, {'$xhr.error': $xhrErr = jasmine.createSpy('$xhr.error')});
|
||||
$browser = scope.$service('$browser');
|
||||
$browserXhr = $browser.xhr;
|
||||
cache = scope.$service('$xhr.cache');
|
||||
|
|
@ -126,22 +126,22 @@ describe('$xhr.cache', function() {
|
|||
|
||||
|
||||
it('should call eval after callbacks for both cache hit and cache miss execute', function() {
|
||||
var evalSpy = this.spyOn(scope, '$eval').andCallThrough();
|
||||
var flushSpy = this.spyOn(scope, '$flush').andCallThrough();
|
||||
|
||||
$browserXhr.expectGET('/url').respond('+');
|
||||
cache('GET', '/url', null, callback);
|
||||
expect(evalSpy).not.toHaveBeenCalled();
|
||||
expect(flushSpy).not.toHaveBeenCalled();
|
||||
|
||||
$browserXhr.flush();
|
||||
expect(evalSpy).toHaveBeenCalled();
|
||||
expect(flushSpy).toHaveBeenCalled();
|
||||
|
||||
evalSpy.reset(); //reset the spy
|
||||
flushSpy.reset(); //reset the spy
|
||||
|
||||
cache('GET', '/url', null, callback);
|
||||
expect(evalSpy).not.toHaveBeenCalled();
|
||||
expect(flushSpy).not.toHaveBeenCalled();
|
||||
|
||||
$browser.defer.flush();
|
||||
expect(evalSpy).toHaveBeenCalled();
|
||||
expect(flushSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call the error callback on error if provided', function() {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ describe('$xhr.error', function() {
|
|||
var scope, $browser, $browserXhr, $xhr, $xhrError, log;
|
||||
|
||||
beforeEach(function(){
|
||||
scope = angular.scope({}, angular.service, {
|
||||
scope = angular.scope(angular.service, {
|
||||
'$xhr.error': $xhrError = jasmine.createSpy('$xhr.error')
|
||||
});
|
||||
$browser = scope.$service('$browser');
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ describe('$xhr', function() {
|
|||
var scope, $browser, $browserXhr, $log, $xhr, $xhrErr, log;
|
||||
|
||||
beforeEach(function(){
|
||||
var scope = angular.scope({}, null, {'$xhr.error': $xhrErr = jasmine.createSpy('xhr.error')});
|
||||
var scope = angular.scope(angular.service, {
|
||||
'$xhr.error': $xhrErr = jasmine.createSpy('xhr.error')});
|
||||
$log = scope.$service('$log');
|
||||
$browser = scope.$service('$browser');
|
||||
$browserXhr = $browser.xhr;
|
||||
|
|
|
|||
|
|
@ -130,10 +130,11 @@ function clearJqCache(){
|
|||
count ++;
|
||||
delete jqCache[key];
|
||||
forEach(value, function(value, key){
|
||||
if (value.$element)
|
||||
dump(key, sortedHtml(value.$element));
|
||||
else
|
||||
dump(key, toJson(value));
|
||||
if (value.$element) {
|
||||
dump('LEAK', key, value.$id, sortedHtml(value.$element));
|
||||
} else {
|
||||
dump('LEAK', key, toJson(value));
|
||||
}
|
||||
});
|
||||
});
|
||||
if (count) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ describe("widget", function(){
|
|||
} else {
|
||||
element = jqLite(html);
|
||||
}
|
||||
return scope = angular.compile(element)();
|
||||
scope = angular.compile(element)();
|
||||
scope.$apply();
|
||||
return scope;
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -26,25 +28,25 @@ describe("widget", function(){
|
|||
describe("text", function(){
|
||||
it('should input-text auto init and handle keydown/change events', function(){
|
||||
compile('<input type="Text" name="name" value="Misko" ng:change="count = count + 1" ng:init="count=0"/>');
|
||||
expect(scope.$get('name')).toEqual("Misko");
|
||||
expect(scope.$get('count')).toEqual(0);
|
||||
expect(scope.name).toEqual("Misko");
|
||||
expect(scope.count).toEqual(0);
|
||||
|
||||
scope.$set('name', 'Adam');
|
||||
scope.$eval();
|
||||
scope.name = 'Adam';
|
||||
scope.$digest();
|
||||
expect(element.val()).toEqual("Adam");
|
||||
|
||||
element.val('Shyam');
|
||||
browserTrigger(element, 'keydown');
|
||||
// keydown event must be deferred
|
||||
expect(scope.$get('name')).toEqual('Adam');
|
||||
expect(scope.name).toEqual('Adam');
|
||||
scope.$service('$browser').defer.flush();
|
||||
expect(scope.$get('name')).toEqual('Shyam');
|
||||
expect(scope.$get('count')).toEqual(1);
|
||||
expect(scope.name).toEqual('Shyam');
|
||||
expect(scope.count).toEqual(1);
|
||||
|
||||
element.val('Kai');
|
||||
browserTrigger(element, 'change');
|
||||
expect(scope.$get('name')).toEqual('Kai');
|
||||
expect(scope.$get('count')).toEqual(2);
|
||||
expect(scope.name).toEqual('Kai');
|
||||
expect(scope.count).toEqual(2);
|
||||
});
|
||||
|
||||
it('should not trigger eval if value does not change', function(){
|
||||
|
|
@ -67,15 +69,15 @@ describe("widget", function(){
|
|||
|
||||
it("should format text", function(){
|
||||
compile('<input type="Text" name="list" value="a,b,c" ng:format="list"/>');
|
||||
expect(scope.$get('list')).toEqual(['a', 'b', 'c']);
|
||||
expect(scope.list).toEqual(['a', 'b', 'c']);
|
||||
|
||||
scope.$set('list', ['x', 'y', 'z']);
|
||||
scope.$eval();
|
||||
scope.list = ['x', 'y', 'z'];
|
||||
scope.$digest();
|
||||
expect(element.val()).toEqual("x, y, z");
|
||||
|
||||
element.val('1, 2, 3');
|
||||
browserTrigger(element);
|
||||
expect(scope.$get('list')).toEqual(['1', '2', '3']);
|
||||
expect(scope.list).toEqual(['1', '2', '3']);
|
||||
});
|
||||
|
||||
it("should come up blank if null", function(){
|
||||
|
|
@ -87,7 +89,7 @@ describe("widget", function(){
|
|||
it("should show incorect text while number does not parse", function(){
|
||||
compile('<input type="text" name="age" ng:format="number"/>');
|
||||
scope.age = 123;
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
scope.$element.val('123X');
|
||||
browserTrigger(scope.$element, 'change');
|
||||
expect(scope.$element.val()).toEqual('123X');
|
||||
|
|
@ -98,11 +100,11 @@ describe("widget", function(){
|
|||
it("should clober incorect text if model changes", function(){
|
||||
compile('<input type="text" name="age" ng:format="number" value="123X"/>');
|
||||
scope.age = 456;
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(scope.$element.val()).toEqual('456');
|
||||
});
|
||||
|
||||
it("should not clober text if model changes doe to itself", function(){
|
||||
it("should not clober text if model changes due to itself", function(){
|
||||
compile('<input type="text" name="list" ng:format="list" value="a"/>');
|
||||
|
||||
scope.$element.val('a ');
|
||||
|
|
@ -128,7 +130,7 @@ describe("widget", function(){
|
|||
|
||||
it("should come up blank when no value specifiend", function(){
|
||||
compile('<input type="text" name="age" ng:format="number"/>');
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(scope.$element.val()).toEqual('');
|
||||
expect(scope.age).toEqual(null);
|
||||
});
|
||||
|
|
@ -173,7 +175,7 @@ describe("widget", function(){
|
|||
expect(scope.$element[0].checked).toEqual(false);
|
||||
|
||||
scope.state = "Worked";
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(scope.state).toEqual("Worked");
|
||||
expect(scope.$element[0].checked).toEqual(true);
|
||||
});
|
||||
|
|
@ -186,8 +188,8 @@ describe("widget", function(){
|
|||
expect(element.hasClass('ng-validation-error')).toBeTruthy();
|
||||
expect(element.attr('ng-validation-error')).toEqual('Not a number');
|
||||
|
||||
scope.$set('price', '123');
|
||||
scope.$eval();
|
||||
scope.price = '123';
|
||||
scope.$digest();
|
||||
expect(element.hasClass('ng-validation-error')).toBeFalsy();
|
||||
expect(element.attr('ng-validation-error')).toBeFalsy();
|
||||
|
||||
|
|
@ -202,8 +204,8 @@ describe("widget", function(){
|
|||
expect(element.hasClass('ng-validation-error')).toBeTruthy();
|
||||
expect(element.attr('ng-validation-error')).toEqual('Required');
|
||||
|
||||
scope.$set('price', '123');
|
||||
scope.$eval();
|
||||
scope.price = '123';
|
||||
scope.$digest();
|
||||
expect(element.hasClass('ng-validation-error')).toBeFalsy();
|
||||
expect(element.attr('ng-validation-error')).toBeFalsy();
|
||||
});
|
||||
|
|
@ -215,7 +217,7 @@ describe("widget", function(){
|
|||
expect(lastValue).toEqual("NOT_CALLED");
|
||||
|
||||
scope.url = 'http://server';
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(lastValue).toEqual("http://server");
|
||||
|
||||
delete angularValidator.myValidator;
|
||||
|
|
@ -240,8 +242,8 @@ describe("widget", function(){
|
|||
expect(element.hasClass('ng-validation-error')).toBeTruthy();
|
||||
expect(element.attr('ng-validation-error')).toEqual('Required');
|
||||
|
||||
scope.$set('price', 'xxx');
|
||||
scope.$eval();
|
||||
scope.price = 'xxx';
|
||||
scope.$digest();
|
||||
expect(element.hasClass('ng-validation-error')).toBeFalsy();
|
||||
expect(element.attr('ng-validation-error')).toBeFalsy();
|
||||
|
||||
|
|
@ -254,19 +256,19 @@ describe("widget", function(){
|
|||
it('should allow conditions on ng:required', function() {
|
||||
compile('<input type="text" name="price" ng:required="ineedz"/>',
|
||||
jqLite(document.body));
|
||||
scope.$set('ineedz', false);
|
||||
scope.$eval();
|
||||
scope.ineedz = false;
|
||||
scope.$digest();
|
||||
expect(element.hasClass('ng-validation-error')).toBeFalsy();
|
||||
expect(element.attr('ng-validation-error')).toBeFalsy();
|
||||
|
||||
scope.$set('price', 'xxx');
|
||||
scope.$eval();
|
||||
scope.price = 'xxx';
|
||||
scope.$digest();
|
||||
expect(element.hasClass('ng-validation-error')).toBeFalsy();
|
||||
expect(element.attr('ng-validation-error')).toBeFalsy();
|
||||
|
||||
scope.$set('price', '');
|
||||
scope.$set('ineedz', true);
|
||||
scope.$eval();
|
||||
scope.price = '';
|
||||
scope.ineedz = true;
|
||||
scope.$digest();
|
||||
expect(element.hasClass('ng-validation-error')).toBeTruthy();
|
||||
expect(element.attr('ng-validation-error')).toEqual('Required');
|
||||
|
||||
|
|
@ -278,31 +280,31 @@ describe("widget", function(){
|
|||
|
||||
it("should process ng:required2", function() {
|
||||
compile('<textarea name="name">Misko</textarea>');
|
||||
expect(scope.$get('name')).toEqual("Misko");
|
||||
expect(scope.name).toEqual("Misko");
|
||||
|
||||
scope.$set('name', 'Adam');
|
||||
scope.$eval();
|
||||
scope.name = 'Adam';
|
||||
scope.$digest();
|
||||
expect(element.val()).toEqual("Adam");
|
||||
|
||||
element.val('Shyam');
|
||||
browserTrigger(element);
|
||||
expect(scope.$get('name')).toEqual('Shyam');
|
||||
expect(scope.name).toEqual('Shyam');
|
||||
|
||||
element.val('Kai');
|
||||
browserTrigger(element);
|
||||
expect(scope.$get('name')).toEqual('Kai');
|
||||
expect(scope.name).toEqual('Kai');
|
||||
});
|
||||
|
||||
it('should call ng:change on button click', function(){
|
||||
compile('<input type="button" value="Click Me" ng:change="clicked = true"/>');
|
||||
browserTrigger(element);
|
||||
expect(scope.$get('clicked')).toEqual(true);
|
||||
expect(scope.clicked).toEqual(true);
|
||||
});
|
||||
|
||||
it('should support button alias', function(){
|
||||
compile('<button ng:change="clicked = true">Click {{"Me"}}.</button>');
|
||||
browserTrigger(element);
|
||||
expect(scope.$get('clicked')).toEqual(true);
|
||||
expect(scope.clicked).toEqual(true);
|
||||
expect(scope.$element.text()).toEqual("Click Me.");
|
||||
});
|
||||
|
||||
|
|
@ -319,11 +321,11 @@ describe("widget", function(){
|
|||
expect(b.name.split('@')[1]).toEqual('chose');
|
||||
expect(scope.chose).toEqual('B');
|
||||
scope.chose = 'A';
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(a.checked).toEqual(true);
|
||||
|
||||
scope.chose = 'B';
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(a.checked).toEqual(false);
|
||||
expect(b.checked).toEqual(true);
|
||||
expect(scope.clicked).not.toBeDefined();
|
||||
|
|
@ -364,12 +366,11 @@ describe("widget", function(){
|
|||
'</select>');
|
||||
expect(scope.selection).toEqual('B');
|
||||
scope.selection = 'A';
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(scope.selection).toEqual('A');
|
||||
expect(element[0].childNodes[0].selected).toEqual(true);
|
||||
});
|
||||
|
||||
|
||||
it('should compile children of a select without a name, but not create a model for it',
|
||||
function() {
|
||||
compile('<select>' +
|
||||
|
|
@ -379,7 +380,7 @@ describe("widget", function(){
|
|||
'</select>');
|
||||
scope.a = 'foo';
|
||||
scope.b = 'bar';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
|
||||
expect(scope.$element.text()).toBe('foobarC');
|
||||
});
|
||||
|
|
@ -394,9 +395,10 @@ describe("widget", function(){
|
|||
'</select>');
|
||||
expect(scope.selection).toEqual(['B']);
|
||||
scope.selection = ['A'];
|
||||
scope.$eval();
|
||||
scope.$digest();
|
||||
expect(element[0].childNodes[0].selected).toEqual(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should ignore text widget which have no name', function(){
|
||||
|
|
@ -412,19 +414,12 @@ describe("widget", function(){
|
|||
});
|
||||
|
||||
it('should report error on assignment error', function(){
|
||||
compile('<input type="text" name="throw \'\'" value="x"/>');
|
||||
expect(element.hasClass('ng-exception')).toBeTruthy();
|
||||
expect(scope.$service('$log').error.logs.shift()[0]).
|
||||
toMatchError(/Syntax Error: Token '''' is an unexpected token/);
|
||||
expect(function(){
|
||||
compile('<input type="text" name="throw \'\'" value="x"/>');
|
||||
}).toThrow("Syntax Error: Token '''' is an unexpected token at column 7 of the expression [throw ''] starting at [''].");
|
||||
$logMock.error.logs.shift();
|
||||
});
|
||||
|
||||
it('should report error on ng:change exception', function(){
|
||||
compile('<button ng:change="a-2=x">click</button>');
|
||||
browserTrigger(element);
|
||||
expect(element.hasClass('ng-exception')).toBeTruthy();
|
||||
expect(scope.$service('$log').error.logs.shift()[0]).
|
||||
toMatchError(/Syntax Error: Token '=' implies assignment but \[a-2\] can not be assigned to/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ng:switch', function(){
|
||||
|
|
@ -436,43 +431,38 @@ describe("widget", function(){
|
|||
'</ng:switch>');
|
||||
expect(element.html()).toEqual('');
|
||||
scope.select = 1;
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
expect(element.text()).toEqual('first:');
|
||||
scope.name="shyam";
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
expect(element.text()).toEqual('first:shyam');
|
||||
scope.select = 2;
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
expect(element.text()).toEqual('second:shyam');
|
||||
scope.name = 'misko';
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
expect(element.text()).toEqual('second:misko');
|
||||
scope.select = true;
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
expect(element.text()).toEqual('true:misko');
|
||||
});
|
||||
|
||||
it("should compare stringified versions", function(){
|
||||
var switchWidget = angular.widget('ng:switch');
|
||||
expect(switchWidget.equals(true, 'true')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should switch on switch-when-default', function(){
|
||||
compile('<ng:switch on="select">' +
|
||||
'<div ng:switch-when="1">one</div>' +
|
||||
'<div ng:switch-default>other</div>' +
|
||||
'</ng:switch>');
|
||||
scope.$eval();
|
||||
'<div ng:switch-when="1">one</div>' +
|
||||
'<div ng:switch-default>other</div>' +
|
||||
'</ng:switch>');
|
||||
scope.$apply();
|
||||
expect(element.text()).toEqual('other');
|
||||
scope.select = 1;
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
expect(element.text()).toEqual('one');
|
||||
});
|
||||
|
||||
it('should call change on switch', function(){
|
||||
var scope = angular.compile('<ng:switch on="url" change="name=\'works\'"><div ng:switch-when="a">{{name}}</div></ng:switch>')();
|
||||
scope.url = 'a';
|
||||
scope.$eval();
|
||||
scope.$apply();
|
||||
expect(scope.name).toEqual(undefined);
|
||||
expect(scope.$element.text()).toEqual('works');
|
||||
dealoc(scope);
|
||||
|
|
@ -483,11 +473,11 @@ describe("widget", function(){
|
|||
it('should include on external file', function() {
|
||||
var element = jqLite('<ng:include src="url" scope="childScope"></ng:include>');
|
||||
var scope = angular.compile(element)();
|
||||
scope.childScope = createScope();
|
||||
scope.childScope = scope.$new();
|
||||
scope.childScope.name = 'misko';
|
||||
scope.url = 'myUrl';
|
||||
scope.$service('$xhr.cache').data.myUrl = {value:'{{name}}'};
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('misko');
|
||||
dealoc(scope);
|
||||
});
|
||||
|
|
@ -495,16 +485,16 @@ describe("widget", function(){
|
|||
it('should remove previously included text if a falsy value is bound to src', function() {
|
||||
var element = jqLite('<ng:include src="url" scope="childScope"></ng:include>');
|
||||
var scope = angular.compile(element)();
|
||||
scope.childScope = createScope();
|
||||
scope.childScope = scope.$new();
|
||||
scope.childScope.name = 'igor';
|
||||
scope.url = 'myUrl';
|
||||
scope.$service('$xhr.cache').data.myUrl = {value:'{{name}}'};
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
|
||||
expect(element.text()).toEqual('igor');
|
||||
|
||||
scope.url = undefined;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
|
||||
expect(element.text()).toEqual('');
|
||||
dealoc(scope);
|
||||
|
|
@ -515,11 +505,14 @@ describe("widget", function(){
|
|||
var scope = angular.compile(element)();
|
||||
scope.url = 'myUrl';
|
||||
scope.$service('$xhr.cache').data.myUrl = {value:'{{c=c+1}}'};
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
// TODO(misko): because we are using scope==this, the eval gets registered
|
||||
// during the flush phase and hence does not get called.
|
||||
// I don't think passing 'this' makes sense. Does having scope on ng:include makes sense?
|
||||
// should we make scope="this" ilegal?
|
||||
scope.$flush();
|
||||
|
||||
// this one should really be just '1', but due to lack of real events things are not working
|
||||
// properly. see discussion at: http://is.gd/ighKk
|
||||
expect(element.text()).toEqual('4');
|
||||
expect(element.text()).toEqual('1');
|
||||
dealoc(element);
|
||||
});
|
||||
|
||||
|
|
@ -531,11 +524,28 @@ describe("widget", function(){
|
|||
|
||||
scope.url = 'myUrl';
|
||||
scope.$service('$xhr.cache').data.myUrl = {value:'my partial'};
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('my partial');
|
||||
expect(scope.loaded).toBe(true);
|
||||
dealoc(element);
|
||||
});
|
||||
|
||||
it('should destroy old scope', function(){
|
||||
var element = jqLite('<ng:include src="url"></ng:include>');
|
||||
var scope = angular.compile(element)();
|
||||
|
||||
expect(scope.$$childHead).toBeFalsy();
|
||||
|
||||
scope.url = 'myUrl';
|
||||
scope.$service('$xhr.cache').data.myUrl = {value:'my partial'};
|
||||
scope.$flush();
|
||||
expect(scope.$$childHead).toBeTruthy();
|
||||
|
||||
scope.url = null;
|
||||
scope.$flush();
|
||||
expect(scope.$$childHead).toBeFalsy();
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
describe('a', function() {
|
||||
|
|
@ -624,7 +634,7 @@ describe("widget", function(){
|
|||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
var options = select.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="0">A</option>');
|
||||
|
|
@ -639,7 +649,7 @@ describe("widget", function(){
|
|||
});
|
||||
scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'};
|
||||
scope.selected = scope.object.red;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
var options = select.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="blue">blue</option>');
|
||||
|
|
@ -648,7 +658,7 @@ describe("widget", function(){
|
|||
expect(options[2].selected).toEqual(true);
|
||||
|
||||
scope.object.azur = '8888FF';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
options = select.find('option');
|
||||
expect(options[3].selected).toEqual(true);
|
||||
});
|
||||
|
|
@ -656,18 +666,18 @@ describe("widget", function(){
|
|||
it('should grow list', function(){
|
||||
createSingleSelect();
|
||||
scope.values = [];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(1); // because we add special empty option
|
||||
expect(sortedHtml(select.find('option')[0])).toEqual('<option value="?"></option>');
|
||||
|
||||
scope.values.push({name:'A'});
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(1);
|
||||
expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
|
||||
scope.values.push({name:'B'});
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
expect(sortedHtml(select.find('option')[1])).toEqual('<option value="1">B</option>');
|
||||
|
|
@ -677,23 +687,23 @@ describe("widget", function(){
|
|||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(3);
|
||||
|
||||
scope.values.pop();
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
expect(sortedHtml(select.find('option')[1])).toEqual('<option value="1">B</option>');
|
||||
|
||||
scope.values.pop();
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(1);
|
||||
expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
|
||||
scope.values.pop();
|
||||
scope.selected = null;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(1); // we add back the special empty option
|
||||
});
|
||||
|
||||
|
|
@ -701,17 +711,17 @@ describe("widget", function(){
|
|||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(3);
|
||||
|
||||
scope.values = [{name:'1'}, {name:'2'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
|
||||
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(3);
|
||||
});
|
||||
|
||||
|
|
@ -719,11 +729,11 @@ describe("widget", function(){
|
|||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
|
||||
scope.values = [{name:'B'}, {name:'C'}, {name:'D'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
var options = select.find('option');
|
||||
expect(options.length).toEqual(3);
|
||||
expect(sortedHtml(options[0])).toEqual('<option value="0">B</option>');
|
||||
|
|
@ -734,19 +744,19 @@ describe("widget", function(){
|
|||
it('should preserve existing options', function(){
|
||||
createSingleSelect(true);
|
||||
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(1);
|
||||
|
||||
scope.values = [{name:'A'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(jqLite(select.find('option')[0]).text()).toEqual('blank');
|
||||
expect(jqLite(select.find('option')[1]).text()).toEqual('A');
|
||||
|
||||
scope.values = [];
|
||||
scope.selected = null;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(1);
|
||||
expect(jqLite(select.find('option')[0]).text()).toEqual('blank');
|
||||
});
|
||||
|
|
@ -756,11 +766,11 @@ describe("widget", function(){
|
|||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('0');
|
||||
|
||||
scope.selected = scope.values[1];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('1');
|
||||
});
|
||||
|
||||
|
|
@ -775,7 +785,7 @@ describe("widget", function(){
|
|||
{name:'D', group:'first'},
|
||||
{name:'E', group:'second'}];
|
||||
scope.selected = scope.values[3];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('3');
|
||||
|
||||
var first = jqLite(select.find('optgroup')[0]);
|
||||
|
|
@ -793,7 +803,7 @@ describe("widget", function(){
|
|||
expect(e.text()).toEqual('E');
|
||||
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('0');
|
||||
});
|
||||
|
||||
|
|
@ -801,11 +811,11 @@ describe("widget", function(){
|
|||
createSelect({'name':'selected', 'ng:options':'item.id as item.name for item in values'});
|
||||
scope.values = [{id:10, name:'A'}, {id:20, name:'B'}];
|
||||
scope.selected = scope.values[0].id;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('0');
|
||||
|
||||
scope.selected = scope.values[1].id;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('1');
|
||||
});
|
||||
|
||||
|
|
@ -816,11 +826,11 @@ describe("widget", function(){
|
|||
});
|
||||
scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'};
|
||||
scope.selected = 'green';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('green');
|
||||
|
||||
scope.selected = 'blue';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('blue');
|
||||
});
|
||||
|
||||
|
|
@ -831,11 +841,11 @@ describe("widget", function(){
|
|||
});
|
||||
scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'};
|
||||
scope.selected = '00FF00';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('green');
|
||||
|
||||
scope.selected = '0000FF';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('blue');
|
||||
});
|
||||
|
||||
|
|
@ -843,13 +853,13 @@ describe("widget", function(){
|
|||
createSingleSelect();
|
||||
scope.values = [{name:'A'}];
|
||||
scope.selected = null;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(select.val()).toEqual('');
|
||||
expect(jqLite(select.find('option')[0]).val()).toEqual('');
|
||||
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('0');
|
||||
expect(select.find('option').length).toEqual(1);
|
||||
});
|
||||
|
|
@ -858,13 +868,13 @@ describe("widget", function(){
|
|||
createSingleSelect(true);
|
||||
scope.values = [{name:'A'}];
|
||||
scope.selected = null;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(select.val()).toEqual('');
|
||||
expect(jqLite(select.find('option')[0]).val()).toEqual('');
|
||||
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('0');
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
});
|
||||
|
|
@ -873,13 +883,13 @@ describe("widget", function(){
|
|||
createSingleSelect();
|
||||
scope.values = [{name:'A'}];
|
||||
scope.selected = {};
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(select.val()).toEqual('?');
|
||||
expect(jqLite(select.find('option')[0]).val()).toEqual('?');
|
||||
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('0');
|
||||
expect(select.find('option').length).toEqual(1);
|
||||
});
|
||||
|
|
@ -890,7 +900,7 @@ describe("widget", function(){
|
|||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('0');
|
||||
|
||||
select.val('1');
|
||||
|
|
@ -907,7 +917,7 @@ describe("widget", function(){
|
|||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.count = 0;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(scope.count).toEqual(0);
|
||||
|
||||
select.val('1');
|
||||
|
|
@ -924,7 +934,7 @@ describe("widget", function(){
|
|||
createSelect({name:'selected', 'ng:options':'item.id as item.name for item in values'});
|
||||
scope.values = [{id:10, name:'A'}, {id:20, name:'B'}];
|
||||
scope.selected = scope.values[0].id;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.val()).toEqual('0');
|
||||
|
||||
select.val('1');
|
||||
|
|
@ -937,7 +947,7 @@ describe("widget", function(){
|
|||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
scope.selected = scope.values[0];
|
||||
select.val('0');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
|
||||
select.val('');
|
||||
browserTrigger(select, 'change');
|
||||
|
|
@ -951,19 +961,19 @@ describe("widget", function(){
|
|||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
|
||||
scope.selected = [];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(jqLite(select.find('option')[0]).attr('selected')).toEqual(false);
|
||||
expect(jqLite(select.find('option')[1]).attr('selected')).toEqual(false);
|
||||
|
||||
scope.selected.push(scope.values[1]);
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(select.find('option')[0].selected).toEqual(false);
|
||||
expect(select.find('option')[1].selected).toEqual(true);
|
||||
|
||||
scope.selected.push(scope.values[0]);
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(select.find('option')[0].selected).toEqual(true);
|
||||
expect(select.find('option')[1].selected).toEqual(true);
|
||||
|
|
@ -974,7 +984,7 @@ describe("widget", function(){
|
|||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
|
||||
scope.selected = [];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
select.find('option')[0].selected = true;
|
||||
|
||||
browserTrigger(select, 'change');
|
||||
|
|
@ -991,24 +1001,30 @@ describe("widget", function(){
|
|||
var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>');
|
||||
|
||||
Array.prototype.extraProperty = "should be ignored";
|
||||
// INIT
|
||||
scope.items = ['misko', 'shyam'];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.find('li').length).toEqual(2);
|
||||
expect(element.text()).toEqual('misko;shyam;');
|
||||
delete Array.prototype.extraProperty;
|
||||
|
||||
// GROW
|
||||
scope.items = ['adam', 'kai', 'brad'];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.find('li').length).toEqual(3);
|
||||
expect(element.text()).toEqual('adam;kai;brad;');
|
||||
|
||||
// SHRINK
|
||||
scope.items = ['brad'];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.find('li').length).toEqual(1);
|
||||
expect(element.text()).toEqual('brad;');
|
||||
});
|
||||
|
||||
it('should ng:repeat over object', function(){
|
||||
var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>');
|
||||
scope.$set('items', {misko:'swe', shyam:'set'});
|
||||
scope.$eval();
|
||||
scope.items = {misko:'swe', shyam:'set'};
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('misko:swe;shyam:set;');
|
||||
});
|
||||
|
||||
|
|
@ -1020,28 +1036,23 @@ describe("widget", function(){
|
|||
var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>');
|
||||
scope.items = new Class();
|
||||
scope.items.name = 'value';
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('name:value;');
|
||||
});
|
||||
|
||||
it('should error on wrong parsing of ng:repeat', function(){
|
||||
var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>');
|
||||
expect(function(){
|
||||
compile('<ul><li ng:repeat="i dont parse"></li></ul>');
|
||||
}).toThrow("Expected ng:repeat in form of '_item_ in _collection_' but got 'i dont parse'.");
|
||||
|
||||
expect(scope.$service('$log').error.logs.shift()[0]).
|
||||
toEqualError("Expected ng:repeat in form of '_item_ in _collection_' but got 'i dont parse'.");
|
||||
|
||||
expect(scope.$element.attr('ng-exception')).
|
||||
toMatch(/Expected ng:repeat in form of '_item_ in _collection_' but got 'i dont parse'/);
|
||||
expect(scope.$element).toHaveClass('ng-exception');
|
||||
|
||||
dealoc(scope);
|
||||
$logMock.error.logs.shift();
|
||||
});
|
||||
|
||||
it('should expose iterator offset as $index when iterating over arrays', function() {
|
||||
var scope = compile('<ul><li ng:repeat="item in items" ' +
|
||||
'ng:bind="item + $index + \'|\'"></li></ul>');
|
||||
scope.items = ['misko', 'shyam', 'frodo'];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('misko0|shyam1|frodo2|');
|
||||
});
|
||||
|
||||
|
|
@ -1049,7 +1060,7 @@ describe("widget", function(){
|
|||
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
|
||||
'ng:bind="key + \':\' + val + $index + \'|\'"></li></ul>');
|
||||
scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'};
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('misko:m0|shyam:s1|frodo:f2|');
|
||||
});
|
||||
|
||||
|
|
@ -1057,16 +1068,16 @@ describe("widget", function(){
|
|||
var scope = compile('<ul><li ng:repeat="item in items" ' +
|
||||
'ng:bind="item + \':\' + $position + \'|\'"></li></ul>');
|
||||
scope.items = ['misko', 'shyam', 'doug'];
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('misko:first|shyam:middle|doug:last|');
|
||||
|
||||
scope.items.push('frodo');
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('misko:first|shyam:middle|doug:middle|frodo:last|');
|
||||
|
||||
scope.items.pop();
|
||||
scope.items.pop();
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('misko:first|shyam:last|');
|
||||
});
|
||||
|
||||
|
|
@ -1074,12 +1085,12 @@ describe("widget", function(){
|
|||
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
|
||||
'ng:bind="key + \':\' + val + \':\' + $position + \'|\'"></li></ul>');
|
||||
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('misko:m:first|shyam:s:middle|doug:d:middle|frodo:f:last|');
|
||||
|
||||
delete scope.items.doug;
|
||||
delete scope.items.frodo;
|
||||
scope.$eval();
|
||||
scope.$flush();
|
||||
expect(element.text()).toEqual('misko:m:first|shyam:s:last|');
|
||||
});
|
||||
});
|
||||
|
|
@ -1089,8 +1100,8 @@ describe("widget", function(){
|
|||
|
||||
it('should prevent compilation of the owning element and its children', function(){
|
||||
var scope = compile('<div ng:non-bindable><span ng:bind="name"></span></div>');
|
||||
scope.$set('name', 'misko');
|
||||
scope.$eval();
|
||||
scope.name = 'misko';
|
||||
scope.$digest();
|
||||
expect(element.text()).toEqual('');
|
||||
});
|
||||
});
|
||||
|
|
@ -1113,7 +1124,7 @@ describe("widget", function(){
|
|||
|
||||
it('should do nothing when no routes are defined', function() {
|
||||
$location.updateHash('/unknown');
|
||||
rootScope.$eval();
|
||||
rootScope.$digest();
|
||||
expect(rootScope.$element.text()).toEqual('');
|
||||
});
|
||||
|
||||
|
|
@ -1126,13 +1137,15 @@ describe("widget", function(){
|
|||
|
||||
$location.updateHash('/foo');
|
||||
$browser.xhr.expectGET('myUrl1').respond('<div>{{1+3}}</div>');
|
||||
rootScope.$eval();
|
||||
rootScope.$digest();
|
||||
rootScope.$flush();
|
||||
$browser.xhr.flush();
|
||||
expect(rootScope.$element.text()).toEqual('4');
|
||||
|
||||
$location.updateHash('/bar');
|
||||
$browser.xhr.expectGET('myUrl2').respond('angular is da best');
|
||||
rootScope.$eval();
|
||||
rootScope.$digest();
|
||||
rootScope.$flush();
|
||||
$browser.xhr.flush();
|
||||
expect(rootScope.$element.text()).toEqual('angular is da best');
|
||||
});
|
||||
|
|
@ -1142,12 +1155,14 @@ describe("widget", function(){
|
|||
|
||||
$location.updateHash('/foo');
|
||||
$browser.xhr.expectGET('myUrl1').respond('<div>{{1+3}}</div>');
|
||||
rootScope.$eval();
|
||||
rootScope.$digest();
|
||||
rootScope.$flush();
|
||||
$browser.xhr.flush();
|
||||
expect(rootScope.$element.text()).toEqual('4');
|
||||
|
||||
$location.updateHash('/unknown');
|
||||
rootScope.$eval();
|
||||
rootScope.$digest();
|
||||
rootScope.$flush();
|
||||
expect(rootScope.$element.text()).toEqual('');
|
||||
});
|
||||
|
||||
|
|
@ -1157,16 +1172,20 @@ describe("widget", function(){
|
|||
|
||||
$location.updateHash('/foo');
|
||||
$browser.xhr.expectGET('myUrl1').respond('<div>{{parentVar}}</div>');
|
||||
rootScope.$eval();
|
||||
rootScope.$digest();
|
||||
rootScope.$flush();
|
||||
$browser.xhr.flush();
|
||||
expect(rootScope.$element.text()).toEqual('parent');
|
||||
|
||||
rootScope.parentVar = 'new parent';
|
||||
rootScope.$eval();
|
||||
rootScope.$digest();
|
||||
rootScope.$flush();
|
||||
expect(rootScope.$element.text()).toEqual('new parent');
|
||||
});
|
||||
|
||||
it('should be possible to nest ng:view in ng:include', function() {
|
||||
dealoc(rootScope); // we are about to override it.
|
||||
|
||||
var myApp = angular.scope();
|
||||
var $browser = myApp.$service('$browser');
|
||||
$browser.xhr.expectGET('includePartial.html').respond('view: <ng:view></ng:view>');
|
||||
|
|
@ -1175,13 +1194,14 @@ describe("widget", function(){
|
|||
var $route = myApp.$service('$route');
|
||||
$route.when('/foo', {controller: angular.noop, template: 'viewPartial.html'});
|
||||
|
||||
dealoc(rootScope); // we are about to override it.
|
||||
rootScope = angular.compile(
|
||||
'<div>' +
|
||||
'include: <ng:include src="\'includePartial.html\'">' +
|
||||
'</ng:include></div>')(myApp);
|
||||
rootScope.$apply();
|
||||
|
||||
$browser.xhr.expectGET('viewPartial.html').respond('content');
|
||||
rootScope.$flush();
|
||||
$browser.xhr.flush();
|
||||
|
||||
expect(rootScope.$element.text()).toEqual('include: view: content');
|
||||
|
|
@ -1211,21 +1231,21 @@ describe("widget", function(){
|
|||
respond('<div ng:init="log.push(\'init\')">' +
|
||||
'<div ng:controller="ChildCtrl"></div>' +
|
||||
'</div>');
|
||||
rootScope.$eval();
|
||||
rootScope.$apply();
|
||||
$browser.xhr.flush();
|
||||
|
||||
expect(rootScope.log).toEqual(['parent', 'init', 'child']);
|
||||
expect(rootScope.log).toEqual(['parent', 'child', 'init']);
|
||||
|
||||
$location.updateHash('');
|
||||
rootScope.$eval();
|
||||
expect(rootScope.log).toEqual(['parent', 'init', 'child']);
|
||||
rootScope.$apply();
|
||||
expect(rootScope.log).toEqual(['parent', 'child', 'init']);
|
||||
|
||||
rootScope.log = [];
|
||||
$location.updateHash('/foo');
|
||||
rootScope.$eval();
|
||||
rootScope.$apply();
|
||||
$browser.defer.flush();
|
||||
|
||||
expect(rootScope.log).toEqual(['parent', 'init', 'child']);
|
||||
expect(rootScope.log).toEqual(['parent', 'child', 'init']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue