Compare commits

...

28 Commits

Author SHA1 Message Date
81e6d13241 chore: bump up the version to 2.0.0-alpha.15 2015-03-24 07:50:39 -07:00
f8e7a37c0d fix(view): fixed view instantiation to use the component template's change detector when creating BindingPropagationConfig 2015-03-24 07:49:28 -07:00
c686e7ea30 chore(doc-gen): ignore non-jsdoc style comments
Now the visitor will find the last jsdoc style comment (e.g. `/** jsdoc comment */`)
before the current code item, ignoring any inline style comments (e.g. `// inline comment`)
in between.

Closes #1072
2015-03-24 11:25:58 +00:00
7e89af8190 chore(doc-gen): move @publicModule tag-def to base package
This prevents unwanted "unknown tag" warnings when generating the non-public docs.
2015-03-24 10:57:42 +00:00
539e8e2cce chore(doc-gen): add specific template for displaying variable exports
Closes #1071
2015-03-24 10:57:42 +00:00
aab084866c doc(test): add a comment on why tests are disabled 2015-03-24 09:52:41 +01:00
0e61a86763 docs: annotations 2015-03-24 00:42:58 +00:00
1c9938ed98 chore(packaging): bump version to alpha.14 2015-03-23 17:14:55 -07:00
47c1a0f381 feat(forms): added value accessor for input=text 2015-03-23 08:53:27 -07:00
514529b5d9 refactor(formed): changed forms to use event and property setters instead of NgElement 2015-03-23 08:52:54 -07:00
a12dc7d75a refactor(forms): wrapped all validators into the Validator class 2015-03-23 08:50:56 -07:00
41b53e71e1 feat(selector): support , for multiple targets
Fixes #867
Closes #1019
2015-03-23 10:06:33 +01:00
0fb9f3bd6c fix(ElementBinderBuilder): properly bind to web component properties
Fixes #776

Closes #1024
2015-03-22 14:14:36 +01:00
81f3f32217 refactor(DirectiveParser): remove checks for missing directives
Based on the discussion in #776 we can't reliably check if a given
element has a particular property at the compilation time. As such
the existing algorithm detecting "missing" directives can't be used.

We need to see if there is a different / better algorithm or maybe
those checks need to be moved later in the process (runtime). Leaving
integration tests in place (disabled) so we can come back to the
topic after unblocking the situation.

This commit effectivelly reverts 94e203b9df
2015-03-22 14:14:36 +01:00
b35f288794 refactor(dart/transform): Use package:guinness in tests
`guinness` is a Dart port of Jasmine. Since the rest of Angular 2 uses
Jasmine, use it for the transformer too.

Closes #8

Closes #1037

Closes #1000
2015-03-21 15:18:15 -07:00
4e82cc0861 refactor(dart/transform): Test directive_linker as a unit
Formerly, it was tested only as a piece of the transformer pipeline. Add
its own directory and test the linker on its own.
2015-03-21 15:18:15 -07:00
c735644c57 refactor(dart/transform): Minor logging changes
Enable easier testing by providing a null log implementation and a way
to use it.
2015-03-21 15:18:15 -07:00
5d479fa0ae refactor(dart/transform): Remove ngData
Now that we have `Parser`, `ngData` is redundant & unnecessary.
2015-03-21 15:18:15 -07:00
8baedca972 style(dart/transform): Remove src from library directives
Conform to Angular 2 style by removing `src` from library directives.
Completed with:
```
find -name "*.dart" | xargs sed -i -e 's!library\(.*\)src\.\(.*\)!library \1\2!'
```

Closes #1005

Closes #1038
2015-03-21 14:55:11 -07:00
02aa8e7945 feat(compiler): support bindings for any attribute
Closes #1029
2015-03-21 14:55:11 -07:00
ee523efcb4 feat(ShadowCss): Support the new deep combinator syntax >>>
fixes #990

ref http://dev.w3.org/csswg/css-scoping-1/#deep-combinator

Closes #1028
2015-03-21 14:55:11 -07:00
eef5f7e06d README - don't forget to build app before start
Don't forget to build app before start HTTP server

Closes #1014
2015-03-21 14:55:11 -07:00
83402930f2 chore(install+test): single cmd to full install/test & test JS w/o Dart
* `npm install` now does a full install; auxiliary installation steps
have been integrated into the `postinstall` script.
* Updated developer docs `DEVELOPER.md` accordingly; also added
instructions to dev docs for performing full tests (via `npm test`) --
same as those run on Travis.
* Reorg in tests so that JS tests can run without a Dart env.

Partly fixes #945 **under the assumption that when running JS tests
locally, `ChromeCanary` is the desired browser to use**. Note that CI
tests (Travis) still uses `DartiumWithWebPlatform` across the board
(Maybe because ChromeCanary isn't being installed?)

Fixes #1012.

Closes #1010
2015-03-21 14:55:11 -07:00
bd48c927d0 fix(ViewContainer) removeChild called with null parent
In view_container.js, templateElement.parentNode can be null
when two template tags are nested in one another.
Accessing the parent node through view.nodes[0].parentNode fixes
the problem.

closes #997

Closes #999
2015-03-21 14:55:10 -07:00
b61b8d60b7 refactor(forEach): change to for-of with iterable
rename: foreach -> for
rename: array -> iterable
update: DartParseTreeWriter
update: naive_infinite_scroll
update: todo
fix: tests in foreach_spec

Closes #919
2015-03-21 14:19:21 -07:00
f1fca5abb6 (docs) decorator events property
As from what i understand shouldn't the event property rather be events: https://github.com/angular/angular/blob/master/modules/angular2/src/core/annotations/annotations.js#L161

Closes #1018
2015-03-21 18:26:13 +00:00
045ce3c77a Fix which dependency is injected w/ current elem.
Docs for the "Injecting a directive from the current element" indicate that having a dependency of `dependency: Dependency` should cause the current element's dependency to be injected, but then uses the ID value from the parent element in the example.

Closes #1032
2015-03-21 18:14:43 +00:00
f822066e2a docs: annotations 2015-03-21 18:05:12 +00:00
119 changed files with 1627 additions and 1057 deletions

View File

@ -95,23 +95,22 @@ Next, install the modules and packages needed to build Angular and run tests:
```shell
# Install Angular project dependencies (package.json)
npm install
# Ensure protractor has the latest webdriver
$(npm bin)/webdriver-manager update
# Install Dart packages
pub get
```
**Optional**: In this document, we make use of project local `npm` package scripts and binaries
(stored under `./node_modules/.bin`) by prefixing these command invocations with `$(npm bin)`; in
particular `gulp` and `protractor` commands. If you prefer, you can drop this path prefix by
globally installing these two packages as follows:
particular `gulp` and `protractor` commands. If you prefer, you can drop this path prefix by either:
*Option 1*: globally installing these two packages as follows:
* `npm install -g gulp` (you might need to prefix this command with `sudo`)
* `npm install -g protractor` (you might need to prefix this command with `sudo`)
Since global installs can become stale, we avoid their use in these instructions.
Since global installs can become stale, and required versions can vary by project, we avoid their
use in these instructions.
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
## Build commands
@ -133,22 +132,35 @@ $(npm bin)/gulp clean
## Running Tests Locally
### Basic tests
### Full test suite
1. `$(npm bin)/gulp test.unit.js`: JS tests in a browser; runs in **watch mode** (i.e. karma
watches the test files for changes and re-runs tests when files are updated).
2. `$(npm bin)/gulp test.unit.cjs`: JS tests in NodeJS; runs in **watch mode**
3. `$(npm bin)/gulp test.unit.dart`: Dart tests in Dartium; runs in **watch mode**.
* `npm test`: full test suite for both JS and Dart versions of Angular. These are the same tests as
those run on Travis.
You can selectively run either the JS or Dart versions as follows:
* `$(npm bin)/gulp test.all.js`
* `$(npm bin)/gulp test.all.dart`
### Unit tests
You can run just the unit tests as follows:
* `$(npm bin)/gulp test.unit.js`: JS tests in a browser; runs in **watch mode** (i.e. karma
watches the test files for changes and re-runs tests when files are updated).
* `$(npm bin)/gulp test.unit.cjs`: JS tests in NodeJS; runs in **watch mode**.
* `$(npm bin)/gulp test.unit.dart`: Dart tests in Dartium; runs in **watch mode**.
If you prefer running tests in "single-run" mode rather than watch mode use
* `$(npm bin)/gulp test.unit.js/ci`
* `$(npm bin)/gulp test.unit.cjs/ci`
* `$(npm bin)/gulp test.unit.dart/ci`
**Note**: If you want to only run a single test you can alter the test you wish
to run by changing `it` to `iit` or `describe` to `ddescribe`. This will only
run that individual test and make it much easier to debug. `xit` and `xdescribe`
can also be useful to exclude a test and a group of tests respectively.
**Note**: If you want to only run a single test you can alter the test you wish to run by changing
`it` to `iit` or `describe` to `ddescribe`. This will only run that individual test and make it
much easier to debug. `xit` and `xdescribe` can also be useful to exclude a test and a group of
tests respectively.
**Note** for transpiler tests: The karma preprocessor is setup in a way so that after every test
run the transpiler is reloaded. With that it is possible to make changes to the preprocessor and
@ -232,4 +244,3 @@ on the `debugger;` statement.
You can then step into the code and add watches.
The `debugger;` statement is needed because WebStorm will stop in a transpiled file. Breakpoints in
the original source files are not supported at the moment.

View File

@ -36,6 +36,7 @@ comments in the source `modules/examples/src/hello_world/index.js`.
You can build this example as either JS or Dart app:
* JS:
* `$(npm bin)/gulp build.js.dev`, and
* `$(npm bin)/gulp serve.js.dev`, and
* open `localhost:8000/examples/src/hello_world/` in Chrome.
* Dart:

View File

@ -89,6 +89,10 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage])
];
})
// Add in a custom tag that we use when generating public docs
.config(function(parseTagsProcessor) {
parseTagsProcessor.tagDefinitions.push({ name: 'publicModule' });
})
// Configure ids and paths
.config(function(computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES) {

View File

@ -30,11 +30,18 @@ module.exports = function AttachCommentTreeVisitor(ParseTreeVisitor, log) {
log.silly('tree: ' + tree.constructor.name + ' - ' + tree.location.start.line);
while (this.currentComment &&
this.currentComment.range.end.offset < tree.location.start.offset) {
log.silly('comment: ' + this.currentComment.range.start.line + ' - ' +
this.currentComment.range.end.line + ' : ' +
this.currentComment.range.toString());
tree.commentBefore = this.currentComment;
this.currentComment.treeAfter = tree;
var commentText = this.currentComment.range.toString();
// Only store the comment if it is JSDOC style (e.g. /** some comment */)
if (/^\/\*\*([\w\W]*)\*\/$/.test(commentText)) {
log.info('comment: ' + this.currentComment.range.start.line + ' - ' +
this.currentComment.range.end.line + ' : ' +
commentText);
tree.commentBefore = this.currentComment;
this.currentComment.treeAfter = tree;
}
this.index++;
this.currentComment = this.comments[this.index];
}

View File

@ -0,0 +1,8 @@
{% extends 'layout/base.template.html' %}
{% block body %}
<h1>{$ doc.name $} <span class="type">variable</span></h1>
<p class="module">exported from <a href="/{$ doc.moduleDoc.path $}">{$ doc.moduleDoc.id $}</a></p>
<p>{$ doc.description | marked $}</p>
{% endblock %}

View File

@ -6,10 +6,6 @@ module.exports = new Package('angular-public', [basePackage])
.processor(require('./processors/filterPublicDocs'))
.config(function(parseTagsProcessor) {
parseTagsProcessor.tagDefinitions.push({ name: 'publicModule' });
})
.config(function(processClassDocs, filterPublicDocs, EXPORT_DOC_TYPES) {
processClassDocs.ignorePrivateMembers = true;
filterPublicDocs.docTypes = EXPORT_DOC_TYPES;

View File

@ -1,5 +1,6 @@
var gulp = require('gulp');
var gulpPlugins = require('gulp-load-plugins')();
var shell = require('gulp-shell');
var runSequence = require('run-sequence');
var madge = require('madge');
var merge = require('merge');
@ -26,6 +27,7 @@ var runServerDartTests = require('./tools/build/run_server_dart_tests');
var transformCJSTests = require('./tools/build/transformCJSTests');
var util = require('./tools/build/util');
// Note: when DART_SDK is not found, all gulp tasks ending with `.dart` will be skipped.
var DART_SDK = require('./tools/build/dartdetect')(gulp);
// -----------------------
// configuration
@ -431,7 +433,11 @@ gulp.task('build/multicopy.dart', copy.multicopy(gulp, gulpPlugins, {
// ------------
// pubspec
gulp.task('build/pubspec.dart', pubget(gulp, gulpPlugins, {
// Run a top-level `pub get` for this project.
gulp.task('pubget.dart', pubget.dir(gulp, gulpPlugins, { dir: '.', command: DART_SDK.PUB }));
// Run `pub get` over CONFIG.dest.dart
gulp.task('build/pubspec.dart', pubget.subDir(gulp, gulpPlugins, {
dir: CONFIG.dest.dart,
command: DART_SDK.PUB
}));
@ -588,6 +594,22 @@ createDocsTasks(true);
createDocsTasks(false);
// ------------------
// CI tests suites
gulp.task('test.js', function(done) {
runSequence('test.transpiler.unittest', 'docs/test', 'test.unit.js/ci',
'test.unit.cjs/ci', done);
});
gulp.task('test.dart', function(done) {
runSequence('test.transpiler.unittest', 'docs/test', 'test.unit.dart/ci', done);
});
// Reuse the Travis scripts
// TODO: rename test_*.sh to test_all_*.sh
gulp.task('test.all.js', shell.task(['./scripts/ci/test_js.sh']))
gulp.task('test.all.dart', shell.task(['./scripts/ci/test_dart.sh']))
// karma tests
// These tests run in the browser and are allowed to access
// HTML DOM APIs.

View File

@ -18,7 +18,7 @@ export * from './src/change_detection/pipes/pipe';
import {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector}
from './src/change_detection/proto_change_detector';
import {PipeRegistry} from './src/change_detection/pipes/pipe_registry';
import {ArrayChangesFactory} from './src/change_detection/pipes/array_changes';
import {IterableChangesFactory} from './src/change_detection/pipes/iterable_changes';
import {KeyValueChangesFactory} from './src/change_detection/pipes/keyvalue_changes';
import {NullPipeFactory} from './src/change_detection/pipes/null_pipe';
@ -31,7 +31,7 @@ export class ChangeDetection {
export var defaultPipes = {
"iterableDiff" : [
new ArrayChangesFactory(),
new IterableChangesFactory(),
new NullPipeFactory()
],
"keyValDiff" : [

View File

@ -2,6 +2,7 @@ export * from './src/core/annotations/visibility';
export * from './src/core/compiler/interfaces';
export * from './src/core/annotations/template';
export * from './src/core/application';
export * from './src/core/annotations/di';
export * from './src/core/compiler/compiler';

View File

@ -1,4 +1,4 @@
export * from './src/directives/foreach';
export * from './src/directives/for';
export * from './src/directives/if';
export * from './src/directives/non_bindable';
export * from './src/directives/switch';

View File

@ -187,7 +187,7 @@ Example:
<pre>
```
<ul>
<li template="foreach: #item in items">
<li template="for: #item of items">
{{item}}
</li>
</ul>
@ -201,8 +201,8 @@ Example:
<pre>
```
<ul>
<template def-foreach:"item"
bind-foreach-in="items">
<template def-for:"item"
bind-for-in="items">
<li>
{{item}}
</li>
@ -221,8 +221,8 @@ Example:
<pre>
```
<template #foreach="item"
[foreach-in]="items">
<template #for="item"
[for-in]="items">
_some_content_to_repeat_
</template>
```
@ -234,8 +234,8 @@ Example:
Example:
<pre>
```
<template def-foreach="item"
bind-foreach-in="items">
<template def-for="item"
bind-for-in="items">
_some_content_to_repeat_
</template>
```
@ -408,22 +408,22 @@ NOTE: Only Viewport directives can be placed on the template element. (Decorator
### Template Microsyntax
Often times it is necessary to encode a lot of different bindings into a template to control how the instantiation
of the templates occurs. One such example is `foreach`.
of the templates occurs. One such example is `for`.
```
<form #foo=form>
</form>
<ul>
<template foreach #person [in]="people" #i="index">
<template for #person [in]="people" #i="index">
<li>{{i}}. {{person}}<li>
</template>
</ul>
```
Where:
* `foreach` triggers the foreach directive.
* `[in]="people"` binds an iterable object to the `foreach` controller.
* `#person` exports the implicit `foreach` item.
* `for` triggers the for directive.
* `[in]="people"` binds an iterable object to the `for` controller.
* `#person` exports the implicit `for` item.
* `#i=index` exports item index as `i`.
The above example is explicit but quite wordy. For this reason in most situations a short hand version of the
@ -431,7 +431,7 @@ syntax is preferable.
```
<ul>
<li template="foreach; #person; in=people; #i=index;">{{i}}. {{person}}<li>
<li template="for; #person; in=people; #i=index;">{{i}}. {{person}}<li>
</ul>
```
@ -441,19 +441,28 @@ which allows us to further shorten the text.
```
<ul>
<li template="foreach #person in people #i=index">{{i}}. {{person}}<li>
<li template="for #person of people #i=index">{{i}}. {{person}}<li>
</ul>
```
We can also optionally use `var` instead of `#` and add `:` to `foreach` which creates the following recommended
microsyntax for `foreach`.
We can also optionally use `var` instead of `#` and add `:` to `for` which creates the following recommended
microsyntax for `for`.
```
<ul>
<li template="foreach: var person in people; var i=index">{{i}}. {{person}}<li>
<li template="for: var person of people; var i=index">{{i}}. {{person}}<li>
</ul>
```
Finally, we can move the `for` keyword to the left hand side and prefix it with `*` as so:
```
<ul>
<li *for="var person of people; var i=index">{{i}}. {{person}}<li>
</ul>
```
The format is intentionally defined freely, so that developers of directives can build an expressive microsyntax for
their directives. The following code describes a more formal definition.

View File

@ -70,7 +70,7 @@ Here is a trivial example of a tooltip decorator. The directive will log a toolt
bind: { // List which properties need to be bound
text: 'tooltip' // - DOM element tooltip property should be
}, // mapped to the directive text property.
event: { // List which events need to be mapped.
events: { // List which events need to be mapped.
mouseover: 'show' // - Invoke the show() method every time
} // the mouseover event is fired.
})

View File

@ -16,17 +16,17 @@ import {
import {NO_CHANGE, Pipe} from './pipe';
export class ArrayChangesFactory {
export class IterableChangesFactory {
supports(obj):boolean {
return ArrayChanges.supportsObj(obj);
return IterableChanges.supportsObj(obj);
}
create():Pipe {
return new ArrayChanges();
return new IterableChanges();
}
}
export class ArrayChanges extends Pipe {
export class IterableChanges extends Pipe {
_collection;
_length:int;
_linkedRecords:_DuplicateMap;
@ -66,7 +66,7 @@ export class ArrayChanges extends Pipe {
}
supports(obj):boolean {
return ArrayChanges.supportsObj(obj);
return IterableChanges.supportsObj(obj);
}
get collection() {

View File

@ -7,58 +7,62 @@ import {Injectable} from 'angular2/di';
/**
* Directives allow you to attach behavior to elements in the DOM.
*
* Directive is an abstract concept, instead use concrete directives: [Component], [DynamicComponent], [Decorator]
* Directive is an abstract concept, instead use concrete directives: [Component], [DynamicComponent], [Decorator]
* or [Viewport].
*
* A directive consists of a single directive annotation and a controller class. When the directive's [selector] matches
* elements in the DOM, the following steps occur:
*
* 1. For each directive, the [ElementInjector] attempts to resolve the directive's constructor arguments.
* 2. Angular instantiates directives for each matched element using [ElementInjector].
* 2. Angular instantiates directives for each matched element using [ElementInjector] in a depth-first order,
* as declared in the HTML.
*
* ## Understanding How Injection Works
*
*
* There are three stages of injection resolution.
* - *Pre-existing Injectors*:
* - The terminal [Injector] cannot resolve dependencies. It either throws an error or, if the dependency was
* - *Pre-existing Injectors*:
* - The terminal [Injector] cannot resolve dependencies. It either throws an error or, if the dependency was
* specified as `@Optional`, returns `null`.
* - The primordial injector resolves browser singleton resources, such as: cookies, title, location, and others.
* - *Component Injectors*: Each `@Component` has its own [Injector], and they follow the same parent-child hierachy
* - *Component Injectors*: Each `@Component` has its own [Injector], and they follow the same parent-child hierachy
* as the components in the DOM.
* - *Element Injectors*: Each component has a Shadow DOM. Within the Shadow DOM each element has an [ElementInjector]
* - *Element Injectors*: Each component has a Shadow DOM. Within the Shadow DOM each element has an [ElementInjector]
* which follow the same parent-child hiercachy as the DOM elements themselves.
*
* When resolving dependencies, the current injector is asked to resolve the dependency first, and if it does not
* have it, it delegates to the parent injector.
*
*
* When a template is instantiated, it also must instantiate the corresponding directives in a depth-first order. The
* current [ElementInjector] resolves the constructor dependencies for each directive.
*
* Angular then resolves dependencies as follows, according to the order in which they appear in the [View]:
*
* 1. Dependencies on element injectors and their parents until it encounters a Shadow DOM boundary
* 2. Dependencies on component injectors and their parents until it encounters the root component
* 3. Dependencies on pre-existing injectors
*
*
* The [ElementInjector] can inject other directives, element-specific special objects, or can delegate to the parent
*
* 1. Dependencies on the current element
* 2. Dependencies on element injectors and their parents until it encounters a Shadow DOM boundary
* 3. Dependencies on component injectors and their parents until it encounters the root component
* 4. Dependencies on pre-existing injectors
*
*
* The [ElementInjector] can inject other directives, element-specific special objects, or it can delegate to the parent
* injector.
*
*
* To inject other directives, declare the constructor parameter as:
* - `directive:DirectiveType`: a directive on the current element only
* - `@Ancestor() d:Type`: any directive that matches the type between the current element (excluded) and the Shadow DOM root [TODO: what does (excluded) mean? Does this apply to the @Parent annotation also?]
* - `@Parent() d:Type`: any directive that matches the type on a direct parent element only
* - `@Children query:Query<Type>`: A live collection of direct child directives
* - `@Descendants query:Query<Type>`: A live collection of any child directives
*
* - `directive:DirectiveType`: a directive on the current element only
* - `@Ancestor() directive:DirectiveType`: any directive that matches the type between the current element and the
* Shadow DOM root. Current Element is not included in the resolution, therefor even if it could resolve it, it will
* be ignored.
* - `@Parent() directive:DirectiveType`: any directive that matches the type on a direct parent element only.
* - `@Children query:Query<DirectiveType>`: A live collection of direct child directives [TO BE IMPLEMENTED].
* - `@Descendants query:Query<DirectiveType>`: A live collection of any child directives [TO BE IMPLEMENTED].
*
* To inject element-specific special objects, declare the constructor parameter as:
* - `element: NgElement` to obtain a DOM element (DEPRECATED: replacment coming)
* - `viewContainer: ViewContainer` to control child template instantiation, for [Viewport] directives only
* - `bindingPropagation: BindingPropagation` to control change detection in a more granular way
*
* - `element: NgElement` to obtain a DOM element (DEPRECATED: replacment coming)
* - `viewContainer: ViewContainer` to control child template instantiation, for [Viewport] directives only
* - `bindingPropagation: BindingPropagation` to control change detection in a more granular way.
*
* ## Example
*
* The following example demonstrates how dependency injection resolves constructor arguments in practice.
*
*
* Assume this HTML structure:
* Assume this HTML template:
*
* ```
* <div dependency="1">
@ -93,9 +97,10 @@ import {Injectable} from 'angular2/di';
*
* Let's step through the different ways in which `MyDirective` could be declared...
*
*
* ### No injection
*
* Here the constructor is declared with no arguments, so nothing is injected into `MyDirective`.
* Here the constructor is declared with no arguments, therefore nothing is injected into `MyDirective`.
*
* ```
* @Decorator({ selector: '[my-directive]' })
@ -105,9 +110,8 @@ import {Injectable} from 'angular2/di';
* }
* ```
*
* This directive would return nothing for the example code above. [TODO: True? We spent a lot of time talking about
* errors but in this case, there's nothing to error on, right? I don't understand the diff between "returns" and "injects"
* when the example is showing a directive not the template. Which is the correct verb?]
* This directive would be instantiated with no dependencies.
*
*
* ### Component-level injection
*
@ -124,8 +128,9 @@ import {Injectable} from 'angular2/di';
* }
* ```
*
* This directive would return `dependency=3` for the example code above. [TODO: True? Is "return" the right verb?]
*
* This directive would be instantiated with a dependency on `SomeService`.
*
*
* ### Injecting a directive from the current element
*
* Directives can inject other directives declared on the current element.
@ -134,18 +139,18 @@ import {Injectable} from 'angular2/di';
* @Decorator({ selector: '[my-directive]' })
* class MyDirective {
* constructor(dependency: Dependency) {
* expect(dependency.id).toEqual(2);
* expect(dependency.id).toEqual(3);
* }
* }
* ```
* This directive would also return `dependency=3` for the example code above. [TODO: True? Why is this the same?]
*
* This directive would be instantiated with `Dependency` declared at the same element, in this case `dependency="3"`.
*
*
* ### Injecting a directive from a direct parent element
*
* Directives can inject other directives declared on a direct parent element. By definition, a directive with a
* Directives can inject other directives declared on a direct parent element. By definition, a directive with a
* `@Parent` annotation does not attempt to resolve dependencies for the current element, even if this would satisfy
* the dependency. [TODO: did I get the subject/verb right?]
* the dependency.
*
* ```
* @Decorator({ selector: '[my-directive]' })
@ -155,17 +160,15 @@ import {Injectable} from 'angular2/di';
* }
* }
* ```
* This directive would return `dependency=2` for the example code above. [TODO: True?]
*
* This directive would be instantiated with `Dependency` declared at the parent element, in this case `dependency="2"`.
*
*
* ### Injecting a directive from any ancestor elements
*
* Directives can inject other directives declared on any ancestor element, i.e. on the parent element and its parents.
* By definition, a directive with an `@Ancestor` annotation does not attempt to resolve dependencies for the current
* element, even if this would satisfy the dependency. [TODO: did I get the subject/verb right? ]
* Directives can inject other directives declared on any ancestor element (in the current Shadow DOM), i.e. on the
* parent element and its parents. By definition, a directive with an `@Ancestor` annotation does not attempt to
* resolve dependencies for the current element, even if this would satisfy the dependency.
*
* Unlike the `@Parent` which only checks the parent `@Ancestor` checks the parent, as well as its
* parents recursivly. If `dependency="2"` would not be present this injection would return `dependency="1"`.
* ```
* @Decorator({ selector: '[my-directive]' })
* class MyDirective {
@ -174,59 +177,61 @@ import {Injectable} from 'angular2/di';
* }
* }
* ```
*
* This directive would also return `dependency=2` for the example code above. If `dependency=2` hadn't been declared
* on the parent `div`, this directive would return `d[TODO: True?]
*
* ### Injecting query of child directives. [PENDING IMPLEMENTATION]
* Unlike the `@Parent` which only checks the parent, `@Ancestor` checks the parent, as well as its
* parents recursively. If `dependency="2"` didn't exist on the direct parent, this injection would have returned
* `dependency="1"`.
*
* In some cases the directive may be interersted in injecting its child directives. This is not directly possible
* since parent directives are guarteed to be created before child directives. Instead we can injecto a container
* which can than be filled once the data is needed.
*
* ### Injecting a live collection of direct child directives [PENDING IMPLEMENTATION]
*
* A directive can also query for other child directives. Since parent directives are instantiated before child
* directives, a directive can't simply inject the list of child directives. Instead, the directive asynchronously
* injects a [Query], which updates as children are added, removed, or moved by any [ViewPort] directive such as a
* `for`, an `if`, or a `switch`.
*
* ```
* @Decorator({ selector: '[my-directive]' })
* class MyDirective {
* constructor(@Children() dependencys:Query<Maker>) {
* // dependencys will eventuall contain: [4, 6]
* // this will upbate if children are added/removed/moved,
* // for example by having for or if.
* constructor(@Children() dependencies:Query<Maker>) {
* }
* }
* ```
*
* This directive would be instantiated with a [Query] which contains `Dependency` 4 and 6. Here, `Dependency` 5 would
* not be included, because it is not a direct child.
*
* ### Injecting query of descendant directives. [PENDING IMPLEMENTATION]
* ### Injecting a live collection of direct descendant directives [PENDING IMPLEMENTATION]
*
* Similar to `@Children` but also includ childre of those children.
* Similar to `@Children` above, but also includes the children of the child elements.
*
* ```
* @Decorator({ selector: '[my-directive]' })
* class MyDirective {
* constructor(@Children() dependencys:Query<Maker>) {
* // dependencys will eventuall contain: [4, 5, 6]
* // this will upbate if children are added/removed/moved,
* // for example by having for or if.
* constructor(@Children() dependencies:Query<Maker>) {
* }
* }
* ```
*
* This directive would be instantiated with a Query which would contain `Dependency` 4, 5 and 6.
*
* ### Optional injection
*
* Finally there may be times when we would like to inject a component which may or may not be there. For this
* use case angular supports `@Optional` injection.
* The normal behavior of directives is to return an error when a specified dependency cannot be resolved. If you
* would like to inject `null` on unresolved dependency instead, you can annotate that dependency with `@Optional()`.
* This explicitly permits the author of a template to treat some of the surrounding directives as optional.
*
* ```
* @Decorator({ selector: '[my-directive]' })
* class MyDirective {
* constructor(@Optional() @Ancestor() form:Form) {
* // this will search for a Form directive above itself,
* // and inject null if not found
* constructor(@Optional() dependency:Dependency) {
* }
* }
* ```
*
* This directive would be instantiated with a `Dependency` directive found on the current element. If none can be
* found, the injector supplies `null` instead of throwing an error.
*
* @publicModule angular2/annotations
*/
@ABSTRACT()
@ -235,13 +240,16 @@ export class Directive extends Injectable {
* The CSS selector that triggers the instantiation of a directive.
*
* Angular only allows directives to trigger on CSS selectors that do not cross element boundaries.
* The supported selectors are:
*
* - `element-name` select by element name.
* - `.class` select by class name.
* - `[attribute]` select by attribute name.
* - `[attribute=value]` select by attribute name and value.
* - `:not(sub_selector)` select only if the element does not match the `sub_selector`.
* `selector` may be declared as one of the following:
*
* - `element-name`: select by element name.
* - `.class`: select by class name.
* - `[attribute]`: select by attribute name.
* - `[attribute=value]`: select by attribute name and value.
* - `:not(sub_selector)`: select only if the element does not match the `sub_selector`.
* - `selector1, selector2`: select if either `selector1` or `selector2` matches.
*
*
* ## Example
*
@ -270,7 +278,8 @@ export class Directive extends Injectable {
* - `bindingProperty` specifies the DOM property where the value is read from.
*
* You can include [Pipes] when specifying a `bindingProperty` to allow for data transformation and structural
* change detection of the value.
* change detection of the value. These pipes will be evaluated in the context of this component.
*
*
* ## Syntax
*
@ -285,43 +294,45 @@ export class Directive extends Injectable {
* ```
*
*
* ## Basic Property Binding:
* ## Basic Property Binding
*
* We can easily build a simple `Tooltip` directive that exposes a `tooltip` property, which can be used in templates
* with standard Angular syntax. For example:
*
* ```
* @Decorator({
* selector: '[tooltip]',
* bind: {
* 'tooltipText': 'tooltip'
* 'text': 'tooltip'
* }
* })
* class Tooltip {
* set tooltipText(text) {
* set text(text) {
* // This will get called every time the 'tooltip' binding changes with the new value.
* }
* }
* ```
*
* As used in this example:
* We can then bind to the `tooltip' property as either an expression (`someExpression`) or as a string literal, as
* shown in the HTML template below:
*
* ```html
* <div [tooltip]="someExpression">
* <div [tooltip]="someExpression">...</div>
* <div tooltip="Some Text">...</div>
* ```
*
* Whenever the `someExpression` expression changes, the `bind` declaration instructs Angular to update the
* `Tooltip`'s `tooltipText` property.
*
*
* Similarly in this example:
*
* ```html
* <div tooltip="Some Text">
* ```
*
* The `Tooltip`'s `tooltipText` property gets initialized to the `Some Text` literal.
*
*
* ## Bindings With Pipes:
*
* You can also use pipes when writing binding definitions for a directive.
*
* For example, we could write a binding that updates the directive on structural changes, rather than on reference
* changes, as normally occurs in change detection. (See: [Pipe] and [keyValueDiff] documentaition for more details.)
*
* ```
* @Decorator({
* selector: '[class-set]',
@ -336,29 +347,30 @@ export class Directive extends Injectable {
* }
* ```
*
* As used in this example:
* The template that this directive is used in may also contain its own pipes. For example:
*
* ```html
* <div [class-set]="someExpression">
* <div [class-set]="someExpression | somePipe">
* ```
*
* In the above example, the `ClassSet` uses the `keyValDiff` [Pipe] for watching structural changes. This means that
* the `classChanges` setter gets invoked if the expression changes to a different reference, or if the
* structure of the expression changes. (Shallow property watching of the object)
*
* NOTE: The `someExpression` can also contain its own [Pipe]s. In this case, the two pipes compose as if they were
* inlined.
* In this case, the two pipes compose as if they were inlined: `someExpression | somePipe | keyValDiff`.
*
*/
bind:any; // StringMap
/**
* Specifies which DOM events the directive listens to and what the action should be when they occur.
* Specifies which DOM events a directive listens to.
*
* The `events` property defines a set of `event` to `method` key-value pairs:
*
* - `event1` specifies the DOM event that the directive listens to.
* - `onMethod1` specifies the method to execute when the event occurs.
* - `event1`: the DOM event that the directive listens to.
* - `statement`: the statment to execute when the event occurs.
*
*
* When writing a directive event binding, you can also refer to the following local variables:
* - `$event`: Current event object which triggerd the event.
* - `$target`: The source of the event. This will be either a DOM element or an Angular directive.
* [TO BE IMPLEMENTED]
*
*
* ## Syntax
@ -366,7 +378,7 @@ export class Directive extends Injectable {
* ```
* @Directive({
* events: {
* 'event1': 'onMethod1',
* 'event1': 'onMethod1(arguments)',
* ...
* }
* }
@ -374,20 +386,24 @@ export class Directive extends Injectable {
*
* ## Basic Event Binding:
*
* Suppose you want to write a directive that triggers on `change` events in the DOM. You would define the event
* binding as follows:
*
* ```
* @Decorator({
* selector: 'input',
* events: {
* 'change': 'onChange'
* 'change': 'onChange($event)'
* }
* })
* class InputDecorator {
* onChange(event:Event) {
* // invoked whenever the DOM element fires the 'change' event.
* }
* }
* ```
*
* Here `InputDecorator` is invoked whenever the DOM element fires the 'change' event.
*
*/
events:any; // StringMap
@ -420,6 +436,8 @@ export class Directive extends Injectable {
/**
* Returns true if a directive participates in a given [LifecycleEvent].
*
* See: [onChange], [onDestroy] for details.
*/
hasLifecycleHook(hook:string):boolean {
return isPresent(this.lifecycle) ? ListWrapper.contains(this.lifecycle, hook) : false;
@ -427,98 +445,87 @@ export class Directive extends Injectable {
}
/**
* Components are angular directives with Shadow DOM views.
* Declare template views for an Angular application.
*
* Componests are used to encapsulate state and template into reusable building blocks. An angular component requires
* an `@Component` and at least one `@Template` annotation (see [Template] for more datails.) Components instances are
* used as the context for evaluation of the Shadow DOM view.
* Each angular component requires a single `@Component` and at least one `@Template` annotation. This allows Angular to
* encapsulate state information and templates. These form the fundamental reusable building blocks for developing an
* application. There can only be one component per DOM element.
*
* Restrictions:
* - Thre can anly be one component per DOM element.
* When a component is instantiated, Angular
* - creates a shadow DOM for the component.
* - loads the selected template into the shadow DOM.
* - creates a child [Injector] which is configured with the [Component.services].
*
* All template expressions and statments are then evaluted against the component instance.
*
* For details on the `@Template` annotation, see [Template].
*
* ## Example
* @Component({
* selector: 'greet'
* })
* @Template({
* inline: 'Hello {{name}}'
* })
* class Greet {
* name: string;
*
* constructor() {
* this.name = 'World';
* }
* }
* ```
* @Component({
* selector: 'greet'
* })
* @Template({
* inline: 'Hello {{name}}!'
* })
* class Greet {
* name: string;
*
* constructor() {
* this.name = 'World';
* }
* }
* ```
*
* @publicModule angular2/annotations
*/
export class Component extends Directive {
/**
* Defines the set of injectables that are visible to a Component and its children.
* Defines the set of injectable objects that are visible to a Component and its children.
*
* When a [Component] defines [injectables], Angular creates a new application-level [Injector] for the component
* and its children. Injectables are defined as a list of [Binding]s, (or as [Type]s as short hand). These bindings
* are passed to the [Injector] constructor when making a new child [Injector]. The injectables are available for
* all child directives of the Component (but not the declaring component's light DOM directives).
* The [services] defined in the Component annotation allow you to configure a set of bindings for the component's
* injector.
*
* ## Example
* // Example of a class which we would like to inject.
* class Greeter {
* salutation:string;
*
* constructor(salutation:string) {
* this.salutation = salutation;
* }
*
* greet(name:string) {
* return this.salutation + ' ' + name + '!';
* }
* }
*
* @Component({
* selector: 'greet',
* services: [
* bind(String).toValue('Hello'), // Configure injection of string
* Greeter // Make Greeter available for injection
* ]
* })
* @Template({
* inline: '<child></child>',
* directives: Child
* })
* class Greet {
* greeter: Greeter;
*
* constructor(greeter: Greeter) {
* // Greeter can be injected here becouse it was declared as injectable
* // in this component, or parent component.
* this.greeter = greeter;
* }
* }
*
* @Decorator({
* selector: 'child'
* })
* class Child {
* greeter: Greeter;
*
* constructor(greeter: Greeter) {
* // Greeter can be injected here becouse it was declared as injectable
* // in a an ancestor component.
* this.greeter = greeter;
* }
* }
* When a component is instantiated, Angular creates a new child Injector, which is configured with the bindings in
* the Component [services] annotation. The injectable objects then become available for injection to the component
* itself and any of the directives in the component's template, i.e. they are not available to the directives which
* are children in the component's light DOM.
*
*
* Let's look at the [services] part of the example above.
* The syntax for configuring the [services] injectable is identical to [Injector] injectable configuration. See
* [Injector] for additional detail.
*
* services: [
* bind(String).toValue('Hello'),
* Greeter
* ]
*
* Here the `Greeter` is a short hand for `bind(Greeter).toClass(Greeter)`. See [bind] DSL for more details.
* ## Simple Example
*
* Here is an example of a class that can be injected:
*
* ```
* class Greeter {
* greet(name:string) {
* return 'Hello ' + name + '!';
* }
* }
*
* @Component({
* selector: 'greet',
* services: [
* Greeter
* ]
* })
* @Template({
* inline: `{{greeter.greet('world')}}!`,
* directives: Child
* })
* class HelloWorld {
* greeter:Greeter;
*
* constructor(greeter:Greeter) {
* this.greeter = greeter;
* }
* }
* ```
*/
services:List;
@ -549,27 +556,34 @@ export class Component extends Directive {
}
/**
* DynamicComponents allow loading child components impretivly.
*
* A Component can be made of other compontents. This recursive nature must be resolved synchronously during the
* component template processing. This means that all templates are resolved synchronously. This prevents lazy loading
* of code or delayed binding of views to the components.
*
* A DynamicComponent is a placeholder into which a regular component can be loaded imperativly and thus breaking
* the all components must be resolved synchronously restriction. Once loaded the component is premanent.
*
*
* Directive used for dynamically loading components.
*
* Regular angular components are statically resolved. DynamicComponent allows to you resolve a component at runtime
* instead by providing a placeholder into which a regular angular component can be dynamically loaded. Once loaded,
* the dynamically-loaded component becomes permanent and cannot be changed.
*
*
* ## Example
*
* Here we have `DynamicComp` which acts as the placeholder for `HelloCmp`. At runtime, the dynamic component
* `DynamicComp` requests loading of the `HelloCmp` component.
*
* There is nothing special about `HelloCmp`, which is a regular angular component. It can also be used in other static
* locations.
*
* ```
* @DynamicComponent({
* selector: 'dynamic-comp'
* })
* class DynamicComp {
* done;
* helloCmp:HelloCmp;
* constructor(loader:PrivateComponentLoader, location:PrivateComponentLocation) {
* this.done = loader.load(HelloCmp, location);
* loader.load(HelloCmp, location).then((helloCmp) => {
* this.helloCmp = helloCmp;
* });
* }
* }
*
*
* @Component({
* selector: 'hello-cmp'
* })
@ -582,11 +596,17 @@ export class Component extends Directive {
* this.greeting = "hello";
* }
* }
*
*
* ```
*
*
*
* @publicModule angular2/annotations
*/
export class DynamicComponent extends Directive {
/**
* Same as [Component.services].
*/
// TODO(vsankin): Please extract into AbstractComponent
services:any; //List;
@CONST()
@ -615,26 +635,24 @@ export class DynamicComponent extends Directive {
}
/**
* Decorators allow attaching behavior to DOM elements in a composable manner.
* Directive that attaches behavior to DOM elements.
*
* A decorator directive attaches behavior to a DOM element in a composable manner.
* (see: http://en.wikipedia.org/wiki/Composition_over_inheritance)
*
* Decorators:
* - are simplest form of [Directive]s.
* - are besed used as compostinion pattern ()
* - are best used as a composition pattern ()
*
* Decoraters differ from [Component]s in that they:
* - can have any number of decorators per element
* Decorators differ from [Component]s in that they:
* - can have multiple decorators per element
* - do not create their own evaluation context
* - do not have template (and therefor do not create Shadow DOM)
*
*
* ## Example
*
* Let's say we would like to add tool-tip behavior to any alement.
*
* ```
* <div tooltip="some text here"></div>
* ```
*
* We could have a decorator directive like so:
* Here we use a decorator directive to simply define basic tool-tip behavior.
*
* ```
* @Decorator({
@ -643,8 +661,8 @@ export class DynamicComponent extends Directive {
* 'text': 'tooltip'
* },
* event: {
* 'onmouseenter': 'onMouseEnter',
* 'onmouseleave': 'onMouseLeave'
* 'onmouseenter': 'onMouseEnter()',
* 'onmouseleave': 'onMouseLeave()'
* }
* })
* class Tooltip{
@ -667,10 +685,23 @@ export class DynamicComponent extends Directive {
* }
* }
* ```
* In our HTML template, we can then add this behavior to a `<div>` or any other element with the `tooltip` selector,
* like so:
*
* ```
* <div tooltip="some text here"></div>
* ```
*
* @publicModule angular2/annotations
*/
export class Decorator extends Directive {
/**
* If set to true the compiler does not compile the children of this directive.
*/
//TODO(vsavkin): This would better fall under the Macro directive concept.
compileChildren: boolean;
@CONST()
constructor({
selector,
@ -697,22 +728,45 @@ export class Decorator extends Directive {
}
/**
* Viewport is used for controlling the instatiation of inline templates.
* Directive that controls the instantiation, destruction, and positioning of inline template elements.
*
* Viewport consist of a controller which can inject [ViewContainer]. A [ViewContainer] rerpsents a location in the
* current view where child views can be inserted.
* A viewport directive uses a [ViewContainer] to instantiate, insert, move, and destroy views at runtime.
* The [ViewContainer] is created as a result of `<template>` element, and represents a location in the current view
* where these actions are performed.
*
* ## Example
* Views are always created as children of the current [View], and as siblings of the `<template>` element. Thus a
* directive in a child view cannot inject the viewport directive that created it.
*
* Given folowing inline template, let's implement the `unless` behavior.
* Since viewport directives are common in Angular, and using the full `<template>` element syntax is wordy, Angular
* also supports a shorthand notation: `<li *foo="bar">` and `<li template="foo: bar">` are equivalent.
*
* Thus,
*
* ```
* <ul>
* <li *unless="expr"></li>
* <li *foo="bar" title="text"></li>
* </ul>
* ```
*
* Can be implemented using:
* Expands in use to:
*
* ```
* <ul>
* <template [foo]="bar">
* <li title="text"></li>
* </template>
* </ul>
* ```
*
* Notice that although the shorthand places `*foo="bar"` within the `<li>` element, the binding for the `Viewport`
* controller is correctly instantiated on the `<template>` element rather than the `<li>` element.
*
*
* ## Example
*
* Let's suppose we want to implement the `unless` behavior, to conditionally include a template.
*
* Here is a simple viewport directive that triggers on an `unless` selector:
*
* ```
* @Viewport({
@ -721,7 +775,7 @@ export class Decorator extends Directive {
* 'condition': 'unless'
* }
* })
* export class If {
* export class Unless {
* viewContainer: ViewContainer;
* prevCondition: boolean;
*
@ -742,6 +796,27 @@ export class Decorator extends Directive {
* }
* ```
*
* We can then use this `unless` selector in a template:
* ```
* <ul>
* <li *unless="expr"></li>
* </ul>
* ```
*
* Once the viewport instantiates the child view, the shorthand notation for the template expands and the result is:
*
* ```
* <ul>
* <template [unless]="exp">
* <li></li>
* </template>
* <li></li>
* </ul>
* ```
*
* Note also that although the `<li></li>` template still exists inside the `<template></template>`, the instantiated
* view occurs on the second `<li></li>` which is a sibling to the `<template>` element.
*
*
* @publicModule angular2/annotations
*/
@ -770,7 +845,7 @@ export class Viewport extends Directive {
//TODO(misko): turn into LifecycleEvent class once we switch to TypeScript;
/**
* Specify that a directive should be notified whenever a [View] that contains it is destroyed.
* Notify a directive whenever a [View] that contains it is destroyed.
*
* ## Example
*
@ -791,7 +866,7 @@ export const onDestroy = "onDestroy";
/**
* Specify that a directive should be notified when any of its bindings have changed.
* Notify a directive when any of its bindings have changed.
*
* ## Example:
*

View File

@ -1,7 +1,7 @@
import {ABSTRACT, CONST, Type} from 'angular2/src/facade/lang';
/**
* @publicModule angular2/angular2
* @publicModule angular2/annotations
*/
export class Template {
url:any; //string;

View File

@ -2,8 +2,40 @@ import {CONST} from 'angular2/src/facade/lang';
import {DependencyAnnotation} from 'angular2/di';
/**
* The directive can only be injected from the current element
* or from its parent.
* The directive can only be injected from the parent element.
*
* ## Example
*
* ```
* <div dependency="1">
* <div dependency="2" my-directive></div>
* </div>
* ```
*
* ```
* @Decorator({
* selector: '[dependency]',
* bind: {
* 'id':'dependency'
* }
* })
* class Dependency {
* id:string;
* }
*
*
* @Decorator({
* selector: '[my-directive]'
* })
* class Dependency {
* constructor(@Parent() dependency:Dependency) {
* expect(dependency.id).toEqual(1);
* };
* }
* ```
*
* In the above example the `@Parent()` annotation forces the injector to retrieve the dependency from the
* parent element (even thought the current element could resolve it).
*
* @publicModule angular2/annotations
*/
@ -15,8 +47,48 @@ export class Parent extends DependencyAnnotation {
}
/**
* The directive can only be injected from the current element
* or from its ancestor.
* The directive can only be injected from the ancestor (any element between parent element and shadow root).
*
*
* ## Example
*
* ```
* <div dependency="1">
* <div dependency="2">
* <div>
* <div dependency="3" my-directive></div>
* </div>
* </div>
* </div>
* ```
*
* ```
* @Decorator({
* selector: '[dependency]',
* bind: {
* 'id':'dependency'
* }
* })
* class Dependency {
* id:string;
* }
*
*
* @Decorator({
* selector: '[my-directive]'
* })
* class Dependency {
* constructor(@Ancestor() dependency:Dependency) {
* expect(dependency.id).toEqual(2);
* };
* }
* ```
*
* In the above example the `@Ancestor()` annotation forces the injector to retrieve the dependency from the
* first ancestor.
* - The current element `dependency="3"` is skipped
* - Next parent has no directives `<div>`
* - Next parent has the `Dependency` directive and so the dependency is satisfied.
*
* @publicModule angular2/annotations
*/

View File

@ -1,5 +1,5 @@
import {isPresent, isBlank, BaseException, assertionsEnabled, RegExpWrapper} from 'angular2/src/facade/lang';
import {List, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {List, MapWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {SelectorMatcher} from '../selector';
import {CssSelector} from '../selector';
@ -10,9 +10,6 @@ import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {isSpecialProperty} from './element_binder_builder';
import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
var PROPERTY_BINDING_REGEXP = RegExpWrapper.create('^ *([^\\s\\|]+)');
/**
@ -39,8 +36,8 @@ export class DirectiveParser extends CompileStep {
this._selectorMatcher = new SelectorMatcher();
for (var i=0; i<directives.length; i++) {
var directiveMetadata = directives[i];
selector=CssSelector.parse(directiveMetadata.annotation.selector);
this._selectorMatcher.addSelectable(selector, directiveMetadata);
selector = CssSelector.parse(directiveMetadata.annotation.selector);
this._selectorMatcher.addSelectables(selector, directiveMetadata);
}
}
@ -60,53 +57,15 @@ export class DirectiveParser extends CompileStep {
});
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
// only be present on <template> elements any more!
// only be present on <template> elements!
var isTemplateElement = DOM.isTemplateElement(current.element);
var matchedProperties; // StringMap - used in dev mode to store all properties that have been matched
this._selectorMatcher.match(cssSelector, (selector, directive) => {
matchedProperties = updateMatchedProperties(matchedProperties, selector, directive);
checkDirectiveValidity(directive, current, isTemplateElement);
current.addDirective(directive);
current.addDirective(checkDirectiveValidity(directive, current, isTemplateElement));
});
// raise error if some directives are missing
checkMissingDirectives(current, matchedProperties, isTemplateElement);
}
}
// calculate all the properties that are used or interpreted by all directives
// those properties correspond to the directive selectors and the directive bindings
function updateMatchedProperties(matchedProperties, selector, directive) {
if (assertionsEnabled()) {
var attrs = selector.attrs;
if (!isPresent(matchedProperties)) {
matchedProperties = StringMapWrapper.create();
}
if (isPresent(attrs)) {
for (var idx = 0; idx<attrs.length; idx+=2) {
// attribute name is stored on even indexes
StringMapWrapper.set(matchedProperties, dashCaseToCamelCase(attrs[idx]), true);
}
}
// some properties can be used by the directive, so we need to register them
if (isPresent(directive.annotation) && isPresent(directive.annotation.bind)) {
var bindMap = directive.annotation.bind;
StringMapWrapper.forEach(bindMap, (value, key) => {
// value is the name of the property that is interpreted
// e.g. 'myprop' or 'myprop | double' when a pipe is used to transform the property
// keep the property name and remove the pipe
var bindProp = RegExpWrapper.firstMatch(PROPERTY_BINDING_REGEXP, value);
if (isPresent(bindProp) && isPresent(bindProp[1])) {
StringMapWrapper.set(matchedProperties, dashCaseToCamelCase(bindProp[1]), true);
}
});
}
}
return matchedProperties;
}
// check if the directive is compatible with the current element
function checkDirectiveValidity(directive, current, isTemplateElement) {
var isComponent = directive.annotation instanceof Component || directive.annotation instanceof DynamicComponent;
@ -125,26 +84,6 @@ function checkDirectiveValidity(directive, current, isTemplateElement) {
} else if (isComponent && alreadyHasComponent) {
throw new BaseException(`Multiple component directives not allowed on the same element - check ${current.elementDescription}`);
}
}
// validates that there is no missing directive - dev mode only
function checkMissingDirectives(current, matchedProperties, isTemplateElement) {
if (assertionsEnabled()) {
var ppBindings=current.propertyBindings;
if (isPresent(ppBindings)) {
// check that each property corresponds to a real property or has been matched by a directive
MapWrapper.forEach(ppBindings, (expression, prop) => {
if (!DOM.hasProperty(current.element, prop) && !isSpecialProperty(prop)) {
if (!isPresent(matchedProperties) || !isPresent(StringMapWrapper.get(matchedProperties, prop))) {
throw new BaseException(`Missing directive to handle '${camelCaseToDashCase(prop)}' in ${current.elementDescription}`);
}
}
});
}
// template only store directives as attribute when they are not bound to expressions
// so we have to validate the expression case too (e.g. !if="condition")
if (isTemplateElement && !current.isViewRoot && !isPresent(current.viewportDirective)) {
throw new BaseException(`Missing directive to handle: ${current.elementDescription}`);
}
}
return directive;
}

View File

@ -15,23 +15,34 @@ import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
var DOT_REGEXP = RegExpWrapper.create('\\.');
const ARIA_PREFIX = 'aria';
var ariaSettersCache = StringMapWrapper.create();
const ATTRIBUTE_PREFIX = 'attr.';
var attributeSettersCache = StringMapWrapper.create();
function ariaSetterFactory(attrName:string) {
var setterFn = StringMapWrapper.get(ariaSettersCache, attrName);
var ariaAttrName;
function _isValidAttributeValue(attrName:string, value: any) {
if (attrName == "role") {
return isString(value);
} else {
return isPresent(value);
}
}
function attributeSetterFactory(attrName:string) {
var setterFn = StringMapWrapper.get(attributeSettersCache, attrName);
var dashCasedAttributeName;
if (isBlank(setterFn)) {
ariaAttrName = camelCaseToDashCase(attrName);
dashCasedAttributeName = camelCaseToDashCase(attrName);
setterFn = function(element, value) {
if (isPresent(value)) {
DOM.setAttribute(element, ariaAttrName, stringify(value));
if (_isValidAttributeValue(dashCasedAttributeName, value)) {
DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
} else {
DOM.removeAttribute(element, ariaAttrName);
DOM.removeAttribute(element, dashCasedAttributeName);
if (isPresent(value)) {
throw new BaseException("Invalid " + dashCasedAttributeName + " attribute, only string values are allowed, got '" + stringify(value) + "'");
}
}
};
StringMapWrapper.set(ariaSettersCache, attrName, setterFn);
StringMapWrapper.set(attributeSettersCache, attrName, setterFn);
}
return setterFn;
@ -82,25 +93,6 @@ function styleSetterFactory(styleName:string, stylesuffix:string) {
return setterFn;
}
const ROLE_ATTR = 'role';
function roleSetter(element, value) {
if (isString(value)) {
DOM.setAttribute(element, ROLE_ATTR, value);
} else {
DOM.removeAttribute(element, ROLE_ATTR);
if (isPresent(value)) {
throw new BaseException("Invalid role attribute, only string values are allowed, got '" + stringify(value) + "'");
}
}
}
// tells if an attribute is handled by the ElementBinderBuilder step
export function isSpecialProperty(propName:string) {
return StringWrapper.startsWith(propName, ARIA_PREFIX)
|| StringWrapper.startsWith(propName, CLASS_PREFIX)
|| StringWrapper.startsWith(propName, STYLE_PREFIX)
|| StringMapWrapper.contains(DOM.attrToPropMap, propName);
}
/**
* Creates the ElementBinders and adds watches to the
@ -188,29 +180,27 @@ export class ElementBinderBuilder extends CompileStep {
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
var setterFn, styleParts, styleSuffix;
if (StringWrapper.startsWith(property, ARIA_PREFIX)) {
setterFn = ariaSetterFactory(property);
} else if (StringWrapper.equals(property, ROLE_ATTR)) {
setterFn = roleSetter;
if (StringWrapper.startsWith(property, ATTRIBUTE_PREFIX)) {
setterFn = attributeSetterFactory(StringWrapper.substring(property, ATTRIBUTE_PREFIX.length));
} else if (StringWrapper.startsWith(property, CLASS_PREFIX)) {
setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length));
} else if (StringWrapper.startsWith(property, STYLE_PREFIX)) {
styleParts = StringWrapper.split(property, DOT_REGEXP);
styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : '';
setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix);
} else if (StringWrapper.equals(property, 'innerHtml')) {
setterFn = (element, value) => DOM.setInnerHTML(element, value);
} else {
property = this._resolvePropertyName(property);
//TODO(pk): special casing innerHtml, see: https://github.com/angular/angular/issues/789
if (StringWrapper.equals(property, 'innerHTML')) {
setterFn = (element, value) => DOM.setInnerHTML(element, value);
} else if (DOM.hasProperty(compileElement.element, property) || StringWrapper.equals(property, 'innerHtml')) {
setterFn = reflector.setter(property);
var propertySetterFn = reflector.setter(property);
setterFn = function(receiver, value) {
if (DOM.hasProperty(receiver, property)) {
return propertySetterFn(receiver, value);
}
}
}
if (isPresent(setterFn)) {
protoView.bindElementProperty(expression.ast, property, setterFn);
}
protoView.bindElementProperty(expression.ast, property, setterFn);
});
}

View File

@ -9,7 +9,9 @@ var _SELECTOR_REGEXP =
RegExpWrapper.create('(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])'); // "[name]", "[name=value]" or "[name*=value]"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]"
'(?:\\))|' + // ")"
'(\\s*,\\s*)'); // ","
/**
* A css selector contains an element name,
@ -21,7 +23,15 @@ export class CssSelector {
classNames:List;
attrs:List;
notSelector: CssSelector;
static parse(selector:string): CssSelector {
static parse(selector:string): List<CssSelector> {
var results = ListWrapper.create();
var _addResult = (res, cssSel) => {
if (isPresent(cssSel.notSelector) && isBlank(cssSel.element)
&& ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
cssSel.element = "*";
}
ListWrapper.push(res, cssSel);
}
var cssSelector = new CssSelector();
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
var match;
@ -43,13 +53,13 @@ export class CssSelector {
if (isPresent(match[4])) {
current.addAttribute(match[4], match[5]);
}
if (isPresent(match[6])) {
_addResult(results, cssSelector);
cssSelector = current = new CssSelector();
}
}
if (isPresent(cssSelector.notSelector) && isBlank(cssSelector.element)
&& ListWrapper.isEmpty(cssSelector.classNames) && ListWrapper.isEmpty(cssSelector.attrs)) {
cssSelector.element = "*";
}
return cssSelector;
_addResult(results, cssSelector);
return results;
}
constructor() {
@ -119,6 +129,7 @@ export class SelectorMatcher {
_classPartialMap:Map;
_attrValueMap:Map;
_attrValuePartialMap:Map;
_listContexts:List;
constructor() {
this._elementMap = MapWrapper.create();
this._elementPartialMap = MapWrapper.create();
@ -128,6 +139,19 @@ export class SelectorMatcher {
this._attrValueMap = MapWrapper.create();
this._attrValuePartialMap = MapWrapper.create();
this._listContexts = ListWrapper.create();
}
addSelectables(cssSelectors:List<CssSelector>, callbackCtxt) {
var listContext = null;
if (cssSelectors.length > 1) {
listContext= new SelectorListContext(cssSelectors);
ListWrapper.push(this._listContexts, listContext);
}
for (var i = 0; i < cssSelectors.length; i++) {
this.addSelectable(cssSelectors[i], callbackCtxt, listContext);
}
}
/**
@ -135,12 +159,12 @@ export class SelectorMatcher {
* @param cssSelector A css selector
* @param callbackCtxt An opaque object that will be given to the callback of the `match` function
*/
addSelectable(cssSelector:CssSelector, callbackCtxt) {
addSelectable(cssSelector, callbackCtxt, listContext: SelectorListContext) {
var matcher = this;
var element = cssSelector.element;
var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs;
var selectable = new SelectorContext(cssSelector, callbackCtxt);
var selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
if (isPresent(element)) {
@ -215,6 +239,10 @@ export class SelectorMatcher {
var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs;
for (var i = 0; i < this._listContexts.length; i++) {
this._listContexts[i].alreadyMatched = false;
}
result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) || result;
@ -282,26 +310,41 @@ export class SelectorMatcher {
}
class SelectorListContext {
selectors: List<CssSelector>;
alreadyMatched: boolean;
constructor(selectors:List<CssSelector>) {
this.selectors = selectors;
this.alreadyMatched = false;
}
}
// Store context to pass back selector and context when a selector is matched
class SelectorContext {
selector:CssSelector;
notSelector:CssSelector;
cbContext; // callback context
listContext: SelectorListContext;
constructor(selector:CssSelector, cbContext) {
constructor(selector:CssSelector, cbContext, listContext: SelectorListContext) {
this.selector = selector;
this.notSelector = selector.notSelector;
this.cbContext = cbContext;
this.listContext = listContext;
}
finalize(cssSelector: CssSelector, callback) {
var result = true;
if (isPresent(this.notSelector)) {
if (isPresent(this.notSelector) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
var notMatcher = new SelectorMatcher();
notMatcher.addSelectable(this.notSelector, null);
notMatcher.addSelectable(this.notSelector, null, null);
result = !notMatcher.match(cssSelector, null);
}
if (result && isPresent(callback)) {
if (result && isPresent(callback) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
if (isPresent(this.listContext)) {
this.listContext.alreadyMatched = true;
}
callback(this.selector, this.cbContext);
}
return result;

View File

@ -512,11 +512,13 @@ var _cssColonHostRe = RegExpWrapper.create('(' + _polyfillHost + _parenSuffix, '
var _cssColonHostContextRe = RegExpWrapper.create('(' + _polyfillHostContext + _parenSuffix, 'im');
var _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
var _shadowDOMSelectorsRe = [
RegExpWrapper.create('/shadow/'),
RegExpWrapper.create('/shadow-deep/'),
RegExpWrapper.create('>>>'),
RegExpWrapper.create('::shadow'),
RegExpWrapper.create('/deep/'),
RegExpWrapper.create('::content'),
// Deprecated selectors
RegExpWrapper.create('/deep/'), // former >>>
RegExpWrapper.create('/shadow-deep/'), // former /deep/
RegExpWrapper.create('/shadow/'), // former ::shadow
];
var _selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$';
var _polyfillHostRe = RegExpWrapper.create(_polyfillHost, 'im');

View File

@ -449,7 +449,7 @@ export class ProtoView {
lightDom = strategy.constructLightDom(view, childView, element);
strategy.attachTemplate(element, childView);
bindingPropagationConfig = new BindingPropagationConfig(changeDetector);
bindingPropagationConfig = new BindingPropagationConfig(childView.changeDetector);
ListWrapper.push(componentChildViews, childView);
}

View File

@ -130,7 +130,7 @@ export class ViewContainer {
var detachedView = this.get(atIndex);
ListWrapper.removeAt(this._views, atIndex);
if (isBlank(this._lightDom)) {
ViewContainer.removeViewNodesFromParent(this.templateElement.parentNode, detachedView);
ViewContainer.removeViewNodes(detachedView);
} else {
this._lightDom.redistribute();
}
@ -173,8 +173,11 @@ export class ViewContainer {
}
}
static removeViewNodesFromParent(parent, view) {
for (var i = view.nodes.length - 1; i >= 0; --i) {
static removeViewNodes(view) {
var len = view.nodes.length;
if (len == 0) return;
var parent = view.nodes[0].parentNode;
for (var i = len - 1; i >= 0; --i) {
DOM.removeChild(parent, view.nodes[i]);
}
}

View File

@ -5,12 +5,12 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
@Viewport({
selector: '[foreach][in]',
selector: '[for][of]',
bind: {
'iterableChanges': 'in | iterableDiff'
'iterableChanges': 'of | iterableDiff'
}
})
export class Foreach {
export class For {
viewContainer: ViewContainer;
constructor(viewContainer:ViewContainer) {
super();
@ -34,13 +34,13 @@ export class Foreach {
(movedRecord) => ListWrapper.push(recordViewTuples, new RecordViewTuple(movedRecord, null))
);
var insertTuples = Foreach.bulkRemove(recordViewTuples, this.viewContainer);
var insertTuples = For.bulkRemove(recordViewTuples, this.viewContainer);
changes.forEachAddedItem(
(addedRecord) => ListWrapper.push(insertTuples, new RecordViewTuple(addedRecord, null))
);
Foreach.bulkInsert(insertTuples, this.viewContainer);
For.bulkInsert(insertTuples, this.viewContainer);
for (var i = 0; i < insertTuples.length; i++) {
this.perViewChange(insertTuples[i].view, insertTuples[i].record);

View File

@ -52,7 +52,7 @@ export class Parse5DomAdapter extends DomAdapter {
}
};
var matcher = new SelectorMatcher();
matcher.addSelectable(CssSelector.parse(selector));
matcher.addSelectables(CssSelector.parse(selector));
_recursive(res, el, selector, matcher);
return res;
}
@ -64,7 +64,7 @@ export class Parse5DomAdapter extends DomAdapter {
var result = false;
if (matcher == null) {
matcher = new SelectorMatcher();
matcher.addSelectable(CssSelector.parse(selector));
matcher.addSelectables(CssSelector.parse(selector));
}
var cssSelector = new CssSelector();

View File

@ -1,58 +1,57 @@
import {Template, Component, Decorator, NgElement, Ancestor, onChange} from 'angular2/angular2';
import {Template, Component, Decorator, Ancestor, onChange, PropertySetter} from 'angular2/angular2';
import {Optional} from 'angular2/di';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {isBlank, isPresent, isString, CONST} from 'angular2/src/facade/lang';
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {ControlGroup, Control} from './model';
import * as validators from './validators';
import {Validators} from './validators';
@CONST()
export class ControlValueAccessor {
readValue(el){}
writeValue(el, value):void {}
}
//export interface ControlValueAccessor {
// writeValue(value):void{}
// set onChange(fn){}
//}
@CONST()
class DefaultControlValueAccessor extends ControlValueAccessor {
constructor() {
@Decorator({
selector: '[control]',
events: {
'change' : 'onChange($event.target.value)',
'input' : 'onChange($event.target.value)'
}
})
export class DefaultValueAccessor {
_setValueProperty:Function;
onChange:Function;
constructor(@PropertySetter('value') setValueProperty:Function) {
super();
this._setValueProperty = setValueProperty;
this.onChange = (_) => {};
}
readValue(el) {
return DOM.getValue(el);
}
writeValue(el, value):void {
DOM.setValue(el,value);
writeValue(value) {
this._setValueProperty(value);
}
}
@CONST()
class CheckboxControlValueAccessor extends ControlValueAccessor {
constructor() {
@Decorator({
selector: 'input[type=checkbox]', //should be input[type=checkbox][control]
// change the selector once https://github.com/angular/angular/issues/1025 is fixed
events: {
'change' : 'onChange($event.target.checked)'
}
})
export class CheckboxControlValueAccessor {
_setCheckedProperty:Function;
onChange:Function;
constructor(cd:ControlDirective, @PropertySetter('checked') setCheckedProperty:Function) {
super();
this._setCheckedProperty = setCheckedProperty;
this.onChange = (_) => {};
cd.valueAccessor = this; //ControlDirective should inject CheckboxControlDirective
}
readValue(el):boolean {
return DOM.getChecked(el);
}
writeValue(el, value:boolean):void {
DOM.setChecked(el, value);
}
}
var controlValueAccessors = {
"checkbox" : new CheckboxControlValueAccessor(),
"text" : new DefaultControlValueAccessor()
};
function controlValueAccessorFor(controlType:string):ControlValueAccessor {
var accessor = StringMapWrapper.get(controlValueAccessors, controlType);
if (isPresent(accessor)) {
return accessor;
} else {
return StringMapWrapper.get(controlValueAccessors, "text");
writeValue(value) {
this._setCheckedProperty(value);
}
}
@ -60,26 +59,22 @@ function controlValueAccessorFor(controlType:string):ControlValueAccessor {
lifecycle: [onChange],
selector: '[control]',
bind: {
'controlName' : 'control',
'type' : 'type'
'controlName' : 'control'
}
})
export class ControlDirective {
_groupDirective:ControlGroupDirective;
_el:NgElement;
controlName:string;
type:string;
valueAccessor:ControlValueAccessor;
valueAccessor:any; //ControlValueAccessor
validator:Function;
constructor(@Ancestor() groupDirective:ControlGroupDirective, el:NgElement) {
constructor(@Ancestor() groupDirective:ControlGroupDirective, valueAccessor:DefaultValueAccessor) {
this._groupDirective = groupDirective;
this._el = el;
this.controlName = null;
this.type = null;
this.validator = validators.nullValidator;
this.valueAccessor = valueAccessor;
this.validator = Validators.nullValidator;
}
// TODO: vsavkin this should be moved into the constructor once static bindings
@ -92,22 +87,18 @@ export class ControlDirective {
this._groupDirective.addDirective(this);
var c = this._control();
c.validator = validators.compose([c.validator, this.validator]);
if (isBlank(this.valueAccessor)) {
this.valueAccessor = controlValueAccessorFor(this.type);
}
c.validator = Validators.compose([c.validator, this.validator]);
this._updateDomValue();
DOM.on(this._el.domElement, "change", (_) => this._updateControlValue());
this._setUpUpdateControlValue();
}
_updateDomValue() {
this.valueAccessor.writeValue(this._el.domElement, this._control().value);
this.valueAccessor.writeValue(this._control().value);
}
_updateControlValue() {
this._control().updateValue(this.valueAccessor.readValue(this._el.domElement));
_setUpUpdateControlValue() {
this.valueAccessor.onChange = (newValue) => this._control().updateValue(newValue);
}
_control() {
@ -165,5 +156,5 @@ export class ControlGroupDirective {
}
export var FormDirectives = [
ControlGroupDirective, ControlDirective
ControlGroupDirective, ControlDirective, CheckboxControlValueAccessor, DefaultValueAccessor
];

View File

@ -1,6 +1,6 @@
import {isPresent} from 'angular2/src/facade/lang';
import {StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
import {nullValidator, controlGroupValidator} from './validators';
import {Validators} from './validators';
export const VALID = "VALID";
export const INVALID = "INVALID";
@ -26,7 +26,7 @@ export class AbstractControl {
_parent:ControlGroup;
validator:Function;
constructor(validator:Function = nullValidator) {
constructor(validator:Function) {
this.validator = validator;
this._updateNeeded = true;
this._pristine = true;
@ -76,7 +76,7 @@ export class AbstractControl {
}
export class Control extends AbstractControl {
constructor(value:any, validator:Function = nullValidator) {
constructor(value:any, validator:Function = Validators.nullValidator) {
super(validator);
this._value = value;
}
@ -101,7 +101,7 @@ export class ControlGroup extends AbstractControl {
controls;
optionals;
constructor(controls, optionals = null, validator:Function = controlGroupValidator) {
constructor(controls, optionals = null, validator:Function = Validators.group) {
super(validator);
this.controls = controls;
this.optionals = isPresent(optionals) ? optionals : {};

View File

@ -1,13 +1,12 @@
import {Decorator} from 'angular2/angular2';
import {ControlDirective} from 'angular2/forms';
import * as validators from 'angular2/forms';
import {ControlDirective, Validators} from 'angular2/forms';
@Decorator({
selector: '[required]'
})
export class RequiredValidatorDirective {
constructor(c:ControlDirective) {
c.validator = validators.compose([c.validator, validators.required]);
c.validator = Validators.compose([c.validator, Validators.required]);
}
}

View File

@ -3,35 +3,37 @@ import {List, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collectio
import * as modelModule from './model';
export function required(c:modelModule.Control) {
return isBlank(c.value) || c.value == "" ? {"required" : true} : null;
}
export class Validators {
static required(c:modelModule.Control) {
return isBlank(c.value) || c.value == "" ? {"required": true} : null;
}
export function nullValidator(c:modelModule.Control) {
return null;
}
static nullValidator(c:modelModule.Control) {
return null;
}
export function compose(validators:List<Function>):Function {
return function(c:modelModule.Control) {
var res = ListWrapper.reduce(validators, (res, validator) => {
var errors = validator(c);
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
}, {});
static compose(validators:List<Function>):Function {
return function (c:modelModule.Control) {
var res = ListWrapper.reduce(validators, (res, validator) => {
var errors = validator(c);
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
}, {});
return StringMapWrapper.isEmpty(res) ? null : res;
}
}
static group(c:modelModule.ControlGroup) {
var res = {};
StringMapWrapper.forEach(c.controls, (control, name) => {
if (c.contains(name) && isPresent(control.errors)) {
StringMapWrapper.forEach(control.errors, (value, error) => {
if (!StringMapWrapper.contains(res, error)) {
res[error] = [];
}
ListWrapper.push(res[error], control);
});
}
});
return StringMapWrapper.isEmpty(res) ? null : res;
}
}
export function controlGroupValidator(c:modelModule.ControlGroup) {
var res = {};
StringMapWrapper.forEach(c.controls, (control, name) => {
if (c.contains(name) && isPresent(control.errors)) {
StringMapWrapper.forEach(control.errors, (value, error) => {
if (! StringMapWrapper.contains(res, error)) {
res[error] = [];
}
ListWrapper.push(res[error], control);
});
}
});
return StringMapWrapper.isEmpty(res) ? null : res;
}

View File

@ -1,4 +1,4 @@
library angular2.src.transform.bind_generator.generator;
library angular2.transform.bind_generator.generator;
import 'dart:async';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.bind_generator.transformer;
library angular2.transform.bind_generator.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.bind_generator.visitor;
library angular2.transform.bind_generator.visitor;
import 'package:analyzer/analyzer.dart';
import 'package:angular2/src/transform/common/logging.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.asset_reader;
library angular2.transform.common.asset_reader;
import 'dart:async';
import 'dart:convert';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.formatter;
library angular2.transform.common.formatter;
import 'package:dart_style/dart_style.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.logging;
library angular2.transform.common.logging;
import 'package:barback/barback.dart';
import 'package:code_transformers/messages/build_logger.dart';
@ -10,6 +10,11 @@ void init(Transform t) {
_logger = new BuildLogger(t);
}
/// Sets [logger] directly. Used for testing - in general use [init].
void setLogger(BuildLogger logger) {
_logger = logger;
}
/// The logger the transformer should use for messaging.
BuildLogger get logger {
if (_logger == null) {

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.names;
library angular2.transform.common.names;
const SETUP_METHOD_NAME = 'setupReflection';
const REFLECTOR_VAR_NAME = 'reflector';

View File

@ -1,35 +0,0 @@
library angular2.src.transform.common.ng_data;
import 'dart:convert';
const NG_DATA_VERSION = 1;
class NgData extends Object {
int importOffset = 0;
int registerOffset = 0;
List<String> imports = [];
NgData();
factory NgData.fromJson(String json) {
var data = JSON.decode(json);
return new NgData()
..importOffset = data['importOffset']
..registerOffset = data['registerOffset']
..imports = data['imports'];
}
String toJson() {
return JSON.encode({
'version': NG_DATA_VERSION,
'importOffset': importOffset,
'registerOffset': registerOffset,
'imports': imports
});
}
@override
String toString() {
return '[NgData: ${toJson()}]';
}
}

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.options;
library angular2.transform.common.options;
const ENTRY_POINT_PARAM = 'entry_point';
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_point';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.parser;
library angular2.transform.common.parser;
import 'dart:async';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.registered_type;
library angular2.transform.common.registered_type;
import 'package:analyzer/analyzer.dart';
import 'package:angular2/src/transform/common/names.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common;
library angular2.transform.common;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/java_core.dart';

View File

@ -1,35 +1,45 @@
library angular2.src.transform.directive_linker.linker;
library angular2.transform.directive_linker.linker;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/ngdata.dart';
import 'package:angular2/src/transform/common/parser.dart';
import 'package:barback/barback.dart';
import 'package:code_transformers/assets.dart';
import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as path;
Future<String> linkNgDeps(Transform transform, String code, String path) async {
var commentIdx = code.lastIndexOf('//');
if (commentIdx < 0) return code;
Future<String> linkNgDeps(AssetReader reader, AssetId entryPoint) async {
var parser = new Parser(reader);
NgDeps ngDeps = await parser.parse(entryPoint);
var ngData = new NgData.fromJson(code.substring(commentIdx + 2));
if (ngDeps == null) return null;
if (ngDeps.imports.isEmpty) return ngDeps.code;
StringBuffer importBuf =
new StringBuffer(code.substring(0, ngData.importOffset));
StringBuffer declarationBuf = new StringBuffer(
code.substring(ngData.importOffset, ngData.registerOffset));
String tail = code.substring(ngData.registerOffset, commentIdx);
var allDeps = ngDeps.imports.toList()..addAll(ngDeps.exports);
var depList = await _processNgImports(
reader, entryPoint, allDeps.map((node) => node.uri.stringValue));
var ngDeps = await _processNgImports(transform, ngData.imports);
if (depList.isEmpty) return ngDeps.code;
for (var i = 0; i < ngDeps.length; ++i) {
importBuf.write('import \'${ngDeps[i]}\' as i${i};');
var importBuf = new StringBuffer();
var declarationBuf = new StringBuffer();
for (var i = 0; i < depList.length; ++i) {
importBuf.write('''
import '${depList[i]}' as i${i};
''');
declarationBuf.write('i${i}.${SETUP_METHOD_NAME}(${REFLECTOR_VAR_NAME});');
}
return '${importBuf}${declarationBuf}${tail}';
var code = ngDeps.code;
var importSeamIdx = ngDeps.imports.last.end;
var declarationSeamIdx = ngDeps.setupMethod.end - 1;
return '${code.substring(0, importSeamIdx)}'
'$importBuf'
'${code.substring(importSeamIdx, declarationSeamIdx)}'
'$declarationBuf'
'${code.substring(declarationSeamIdx)}';
}
String _toDepsUri(String importUri) =>
@ -40,17 +50,16 @@ bool _isNotDartImport(String importUri) {
}
Future<List<String>> _processNgImports(
Transform transform, List<String> imports) async {
AssetReader reader, AssetId entryPoint, Iterable<String> imports) {
final nullFuture = new Future.value(null);
var retVal = <String>[];
return Future
.wait(imports.where(_isNotDartImport).map(_toDepsUri).map((ngDepsUri) {
var importAsset = uriToAssetId(
transform.primaryInput.id, ngDepsUri, logger, null /* span */);
return transform.hasInput(importAsset).then((hasInput) {
if (hasInput) {
retVal.add(ngDepsUri);
}
var importAsset =
uriToAssetId(entryPoint, ngDepsUri, logger, null /* span */);
if (importAsset == entryPoint) return nullFuture;
return reader.hasInput(importAsset).then((hasInput) {
if (hasInput) retVal.add(ngDepsUri);
});
})).then((_) => retVal);
}

View File

@ -1,7 +1,8 @@
library angular2.src.transform.directive_linker.transformer;
library angular2.transform.directive_linker.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/formatter.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
@ -27,12 +28,11 @@ class DirectiveLinker extends Transformer {
log.init(transform);
try {
var assetCode = await transform.primaryInput.readAsString();
var assetPath = transform.primaryInput.id.path;
var transformedCode = await linkNgDeps(transform, assetCode, assetPath);
var formattedCode = formatter.format(transformedCode, uri: assetPath);
transform.addOutput(
new Asset.fromString(transform.primaryInput.id, formattedCode));
var assetId = transform.primaryInput.id;
var transformedCode =
await linkNgDeps(new AssetReader.fromTransform(transform), assetId);
var formattedCode = formatter.format(transformedCode, uri: assetId.path);
transform.addOutput(new Asset.fromString(assetId, formattedCode));
} catch (ex, stackTrace) {
log.logger.error('Linking ng directives failed.\n'
'Exception: $ex\n'

View File

@ -1,10 +1,9 @@
library angular2.src.transform.directive_processor;
library angular2.transform.directive_processor;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/ngdata.dart';
import 'package:angular2/src/transform/common/visitor_mixin.dart';
import 'package:path/path.dart' as path;
@ -42,7 +41,6 @@ class CreateNgDepsVisitor extends Object
final FactoryTransformVisitor _factoryVisitor;
final ParameterTransformVisitor _paramsVisitor;
final AnnotationsTransformVisitor _metaVisitor;
final NgData _ngData = new NgData();
/// The path to the file which we are parsing.
final String importPath;
@ -73,19 +71,16 @@ class CreateNgDepsVisitor extends Object
_writeImport();
wroteImport = true;
}
_ngData.imports.add(node.uri.stringValue);
return node.accept(_copyVisitor);
}
@override
Object visitExportDirective(ExportDirective node) {
_ngData.imports.add(node.uri.stringValue);
return node.accept(_copyVisitor);
}
void _openFunctionWrapper() {
// TODO(kegluneq): Use a [PrintWriter] with a length getter.
_ngData.importOffset = writer.toString().length;
writer.print('bool _visited = false;'
'void ${SETUP_METHOD_NAME}(${REFLECTOR_VAR_NAME}) {'
'if (_visited) return; _visited = true;');
@ -95,10 +90,7 @@ class CreateNgDepsVisitor extends Object
if (foundNgDirectives) {
writer.print(';');
}
// TODO(kegluneq): Use a [PrintWriter] with a length getter.
_ngData.registerOffset = writer.toString().length;
writer.print('}');
writer.print('// ${_ngData.toJson()}');
}
ConstructorDeclaration _getCtor(ClassDeclaration node) {

View File

@ -1,4 +1,4 @@
library angular2.src.transform.directive_processor.transformer;
library angular2.transform.directive_processor.transformer;
import 'dart:async';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.directive_processor;
library angular2.transform.directive_processor;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/java_core.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'dart:collection' show Queue;
import 'package:analyzer/src/generated/ast.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
@ -55,7 +55,7 @@ abstract class DirectiveRegistry {
const setupReflectionMethodName = 'setupReflection';
const _libraryDeclaration = '''
library angular2.src.transform.generated;
library angular2.transform.generated;
''';
const _reflectorImport = '''

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'package:barback/barback.dart';
import 'package:analyzer/src/generated/element.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.reflection_remover.ast_tester;
library angular2.transform.reflection_remover.ast_tester;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.reflection_remover.codegen;
library angular2.transform.reflection_remover.codegen;
import 'package:analyzer/src/generated/ast.dart';
import 'package:barback/barback.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.reflection_remover.remove_reflection_capabilities;
library angular2.transform.reflection_remover.remove_reflection_capabilities;
import 'package:analyzer/analyzer.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.reflection_remover.rewriter;
library angular2.transform.reflection_remover.rewriter;
import 'package:analyzer/src/generated/ast.dart';
import 'package:angular2/src/transform/common/logging.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.reflection_remover.transformer;
library angular2.transform.reflection_remover.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/logging.dart' as log;

View File

@ -1,4 +1,4 @@
library angular2.src.transform.template_compiler.generator;
library angular2.transform.template_compiler.generator;
import 'dart:async';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.template_compiler.recording_reflection_capabilities;
library angular2.transform.template_compiler.recording_reflection_capabilities;
import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'package:angular2/src/reflection/types.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.template_compiler.transformer;
library angular2.transform.template_compiler.transformer;
import 'dart:async';

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'package:barback/barback.dart';
import 'package:dart_style/dart_style.dart';

View File

@ -1,11 +1,11 @@
import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
import {ArrayChanges} from 'angular2/src/change_detection/pipes/array_changes';
import {IterableChanges} from 'angular2/src/change_detection/pipes/iterable_changes';
import {NumberWrapper} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {TestIterable} from './iterable';
import {arrayChangesAsString} from './util';
import {iterableChangesAsString} from './util';
// todo(vicb): UnmodifiableListView / frozen object when implemented
export function main() {
@ -15,7 +15,7 @@ export function main() {
var l;
beforeEach(() => {
changes = new ArrayChanges();
changes = new IterableChanges();
});
afterEach(() => {
@ -23,30 +23,30 @@ export function main() {
});
it('should support list and iterables', () => {
expect(ArrayChanges.supportsObj([])).toBeTruthy();
expect(ArrayChanges.supportsObj(new TestIterable())).toBeTruthy();
expect(ArrayChanges.supportsObj(MapWrapper.create())).toBeFalsy();
expect(ArrayChanges.supportsObj(null)).toBeFalsy();
expect(IterableChanges.supportsObj([])).toBeTruthy();
expect(IterableChanges.supportsObj(new TestIterable())).toBeTruthy();
expect(IterableChanges.supportsObj(MapWrapper.create())).toBeFalsy();
expect(IterableChanges.supportsObj(null)).toBeFalsy();
});
it('should support iterables', () => {
l = new TestIterable();
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: []
}));
l.list = [1];
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['1[null->0]'],
additions: ['1[null->0]']
}));
l.list = [2, 1];
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['2[null->0]', '1[0->1]'],
previous: ['1[0->1]'],
additions: ['2[null->0]'],
@ -57,20 +57,20 @@ export function main() {
it('should detect additions', () => {
l = [];
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: []
}));
ListWrapper.push(l, 'a');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a[null->0]'],
additions: ['a[null->0]']
}));
ListWrapper.push(l, 'b');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'b[null->1]'],
previous: ['a'],
additions: ['b[null->1]']
@ -83,7 +83,7 @@ export function main() {
l = [1, 0];
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['1[null->0]', '0[0->1]'],
previous: ['0[0->1]'],
additions: ['1[null->0]'],
@ -92,7 +92,7 @@ export function main() {
l = [2, 1, 0];
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['2[null->0]', '1[0->1]', '0[1->2]'],
previous: ['1[0->1]', '0[1->2]'],
additions: ['2[null->0]'],
@ -108,7 +108,7 @@ export function main() {
ListWrapper.push(l, 2);
ListWrapper.push(l, 1);
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['2[1->0]', '1[0->1]'],
previous: ['1[0->1]', '2[1->0]'],
moves: ['2[1->0]', '1[0->1]']
@ -122,7 +122,7 @@ export function main() {
ListWrapper.removeAt(l, 1);
ListWrapper.insert(l, 0, 'b');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['b[1->0]', 'a[0->1]', 'c'],
previous: ['a[0->1]', 'b[1->0]', 'c'],
moves: ['b[1->0]', 'a[0->1]']
@ -131,7 +131,7 @@ export function main() {
ListWrapper.removeAt(l, 1);
ListWrapper.push(l, 'a');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['b', 'c[2->1]', 'a[1->2]'],
previous: ['b', 'a[1->2]', 'c[2->1]'],
moves: ['c[2->1]', 'a[1->2]']
@ -144,14 +144,14 @@ export function main() {
ListWrapper.push(l, 'a');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a[null->0]'],
additions: ['a[null->0]']
}));
ListWrapper.push(l, 'b');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'b[null->1]'],
previous: ['a'],
additions: ['b[null->1]']
@ -160,7 +160,7 @@ export function main() {
ListWrapper.push(l, 'c');
ListWrapper.push(l, 'd');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'b', 'c[null->2]', 'd[null->3]'],
previous: ['a', 'b'],
additions: ['c[null->2]', 'd[null->3]']
@ -168,7 +168,7 @@ export function main() {
ListWrapper.removeAt(l, 2);
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'b', 'd[3->2]'],
previous: ['a', 'b', 'c[2->null]', 'd[3->2]'],
moves: ['d[3->2]'],
@ -181,7 +181,7 @@ export function main() {
ListWrapper.push(l, 'b');
ListWrapper.push(l, 'a');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['d[2->0]', 'c[null->1]', 'b[1->2]', 'a[0->3]'],
previous: ['a[0->3]', 'b[1->2]', 'd[2->0]'],
additions: ['c[null->1]'],
@ -197,7 +197,7 @@ export function main() {
var oo = 'oo';
ListWrapper.set(l, 1, b + oo);
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'boo'],
previous: ['a', 'boo']
}));
@ -207,7 +207,7 @@ export function main() {
l = [NumberWrapper.NaN];
changes.check(l);
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: [NumberWrapper.NaN],
previous: [NumberWrapper.NaN]
}));
@ -219,7 +219,7 @@ export function main() {
ListWrapper.insert(l, 0, 'foo');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['foo[null->0]', 'NaN[0->1]', 'NaN[1->2]'],
previous: ['NaN[0->1]', 'NaN[1->2]'],
additions: ['foo[null->0]'],
@ -233,7 +233,7 @@ export function main() {
ListWrapper.removeAt(l, 1);
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'c[2->1]'],
previous: ['a', 'b[1->null]', 'c[2->1]'],
moves: ['c[2->1]'],
@ -242,7 +242,7 @@ export function main() {
ListWrapper.insert(l, 1, 'b');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'b[null->1]', 'c[1->2]'],
previous: ['a', 'c[1->2]'],
additions: ['b[null->1]'],
@ -256,7 +256,7 @@ export function main() {
ListWrapper.removeAt(l, 0);
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'a', 'b[3->2]', 'b[4->3]'],
previous: ['a', 'a', 'a[2->null]', 'b[3->2]', 'b[4->3]'],
moves: ['b[3->2]', 'b[4->3]'],
@ -270,7 +270,7 @@ export function main() {
ListWrapper.insert(l, 0, 'b');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['b[2->0]', 'a[0->1]', 'a[1->2]', 'b', 'b[null->4]'],
previous: ['a[0->1]', 'a[1->2]', 'b[2->0]', 'b'],
additions: ['b[null->4]'],
@ -287,7 +287,7 @@ export function main() {
ListWrapper.push(l, 'a');
ListWrapper.push(l, 'c');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['b[1->0]', 'a[0->1]', 'c'],
previous: ['a[0->1]', 'b[1->0]', 'c'],
moves: ['b[1->0]', 'a[0->1]']

View File

@ -1,6 +1,6 @@
import {isBlank} from 'angular2/src/facade/lang';
export function arrayChangesAsString({collection, previous, additions, moves, removals}) {
export function iterableChangesAsString({collection, previous, additions, moves, removals}) {
if (isBlank(collection)) collection = [];
if (isBlank(previous)) previous = [];
if (isBlank(additions)) additions = [];

View File

@ -2,6 +2,7 @@ import {
AsyncTestCompleter,
beforeEach,
ddescribe,
xdescribe,
describe,
el,
expect,
@ -116,7 +117,7 @@ export function main() {
}));
it('should consume binding to aria-* attributes', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MyComp, new Template({inline: '<div [aria-label]="ctxProp"></div>'}));
tplResolver.setTemplate(MyComp, new Template({inline: '<div [attr.aria-label]="ctxProp"></div>'}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
@ -185,6 +186,20 @@ export function main() {
});
}));
it('should ignore bindings to unknown properties', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MyComp, new Template({inline: '<div unknown="{{ctxProp}}"></div>'}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
ctx.ctxProp = 'Some value';
cd.detectChanges();
expect(DOM.hasProperty(view.nodes[0], 'unknown')).toBeFalsy();
async.done();
});
}));
it('should consume directive watch expression change.', inject([AsyncTestCompleter], (async) => {
var tpl =
'<div>' +
@ -436,30 +451,57 @@ export function main() {
})
}));
it('should provide binding configuration config to the component', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MyComp, new Template({
inline: '<push-cmp #cmp></push-cmp>',
directives: [[[PushBasedComp]]]
describe("BindingPropagationConfig", () => {
it("can be used to disable the change detection of the component's template",
inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MyComp, new Template({
inline: '<push-cmp #cmp></push-cmp>',
directives: [[[PushBasedComp]]]
}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
var cmp = view.locals.get('cmp');
cd.detectChanges();
expect(cmp.numberOfChecks).toEqual(1);
cd.detectChanges();
expect(cmp.numberOfChecks).toEqual(1);
cmp.propagate();
cd.detectChanges();
expect(cmp.numberOfChecks).toEqual(2);
async.done();
})
}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
it('should not affect updating properties on the component', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MyComp, new Template({
inline: '<push-cmp [prop]="ctxProp" #cmp></push-cmp>',
directives: [[[PushBasedComp]]]
}));
var cmp = view.locals.get('cmp');
compiler.compile(MyComp).then((pv) => {
createView(pv);
cd.detectChanges();
expect(cmp.numberOfChecks).toEqual(1);
var cmp = view.locals.get('cmp');
cd.detectChanges();
expect(cmp.numberOfChecks).toEqual(1);
ctx.ctxProp = "one";
cd.detectChanges();
expect(cmp.prop).toEqual("one");
cmp.propagate();
ctx.ctxProp = "two";
cd.detectChanges();
expect(cmp.prop).toEqual("two");
cd.detectChanges();
expect(cmp.numberOfChecks).toEqual(2);
async.done();
})
}));
async.done();
})
}));
});
it('should create a component that injects a @Parent', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MyComp, new Template({
@ -566,61 +608,60 @@ export function main() {
}));
});
if (assertionsEnabled()) {
// Disabled until a solution is found, refs:
// - https://github.com/angular/angular/issues/776
// - https://github.com/angular/angular/commit/81f3f32
xdescribe('Missing directive checks', () => {
function expectCompileError(inlineTpl, errMessage, done) {
tplResolver.setTemplate(MyComp, new Template({inline: inlineTpl}));
PromiseWrapper.then(compiler.compile(MyComp),
(value) => {
throw new BaseException("Test failure: should not have come here as an exception was expected");
},
(err) => {
expect(err.message).toEqual(errMessage);
done();
}
);
if (assertionsEnabled()) {
function expectCompileError(inlineTpl, errMessage, done) {
tplResolver.setTemplate(MyComp, new Template({inline: inlineTpl}));
PromiseWrapper.then(compiler.compile(MyComp),
(value) => {
throw new BaseException("Test failure: should not have come here as an exception was expected");
},
(err) => {
expect(err.message).toEqual(errMessage);
done();
}
);
}
it('should raise an error if no directive is registered for a template with template bindings', inject([AsyncTestCompleter], (async) => {
expectCompileError(
'<div><div template="if: foo"></div></div>',
'Missing directive to handle \'if\' in <div template="if: foo">',
() => async.done()
);
}));
it('should raise an error for missing template directive (1)', inject([AsyncTestCompleter], (async) => {
expectCompileError(
'<div><template foo></template></div>',
'Missing directive to handle: <template foo>',
() => async.done()
);
}));
it('should raise an error for missing template directive (2)', inject([AsyncTestCompleter], (async) => {
expectCompileError(
'<div><template *if="condition"></template></div>',
'Missing directive to handle: <template *if="condition">',
() => async.done()
);
}));
it('should raise an error for missing template directive (3)', inject([AsyncTestCompleter], (async) => {
expectCompileError(
'<div *if="condition"></div>',
'Missing directive to handle \'if\' in MyComp: <div *if="condition">',
() => async.done()
);
}));
}
});
it('should raise an error if no directive is registered for an unsupported DOM property', inject([AsyncTestCompleter], (async) => {
expectCompileError(
'<div [some-prop]="foo"></div>',
'Missing directive to handle \'some-prop\' in MyComp: <div [some-prop]="foo">',
() => async.done()
);
}));
it('should raise an error if no directive is registered for a template with template bindings', inject([AsyncTestCompleter], (async) => {
expectCompileError(
'<div><div template="if: foo"></div></div>',
'Missing directive to handle \'if\' in <div template="if: foo">',
() => async.done()
);
}));
it('should raise an error for missing template directive (1)', inject([AsyncTestCompleter], (async) => {
expectCompileError(
'<div><template foo></template></div>',
'Missing directive to handle: <template foo>',
() => async.done()
);
}));
it('should raise an error for missing template directive (2)', inject([AsyncTestCompleter], (async) => {
expectCompileError(
'<div><template *if="condition"></template></div>',
'Missing directive to handle: <template *if="condition">',
() => async.done()
);
}));
it('should raise an error for missing template directive (3)', inject([AsyncTestCompleter], (async) => {
expectCompileError(
'<div *if="condition"></div>',
'Missing directive to handle \'if\' in MyComp: <div *if="condition">',
() => async.done()
);
}));
}
});
}
@ -659,11 +700,17 @@ class MyDir {
}
}
@Component({selector: 'push-cmp'})
@Component({
selector: 'push-cmp',
bind: {
'prop': 'prop'
}
})
@Template({inline: '{{field}}'})
class PushBasedComp {
numberOfChecks:number;
bpc:BindingPropagationConfig;
prop;
constructor(bpc:BindingPropagationConfig) {
this.numberOfChecks = 0;

View File

@ -218,7 +218,7 @@ export function main() {
it('should bind to aria-* attributes when exp evaluates to strings', () => {
var propertyBindings = MapWrapper.createFromStringMap({
'aria-label': 'prop1'
'attr.aria-label': 'prop1'
});
var pipeline = createPipeline({propertyBindings: propertyBindings});
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
@ -243,7 +243,7 @@ export function main() {
it('should bind to aria-* attributes when exp evaluates to booleans', () => {
var propertyBindings = MapWrapper.createFromStringMap({
'aria-busy': 'prop1'
'attr.aria-busy': 'prop1'
});
var pipeline = createPipeline({propertyBindings: propertyBindings});
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
@ -264,7 +264,7 @@ export function main() {
it('should bind to ARIA role attribute', () => {
var propertyBindings = MapWrapper.createFromStringMap({
'role': 'prop1'
'attr.role': 'prop1'
});
var pipeline = createPipeline({propertyBindings: propertyBindings});
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
@ -289,7 +289,7 @@ export function main() {
it('should throw for a non-string ARIA role', () => {
var propertyBindings = MapWrapper.createFromStringMap({
'role': 'prop1'
'attr.role': 'prop1'
});
var pipeline = createPipeline({propertyBindings: propertyBindings});
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
@ -303,6 +303,31 @@ export function main() {
}).toThrowError("Invalid role attribute, only string values are allowed, got '1'");
});
it('should bind to any attribute', () => {
var propertyBindings = MapWrapper.createFromStringMap({
'attr.foo-bar': 'prop1'
});
var pipeline = createPipeline({propertyBindings: propertyBindings});
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
var pv = results[0].inheritedProtoView;
expect(pv.elementBinders[0].hasElementPropertyBindings).toBe(true);
instantiateView(pv);
evalContext.prop1 = 'baz';
changeDetector.detectChanges();
expect(DOM.getAttribute(view.nodes[0], 'foo-bar')).toEqual('baz');
evalContext.prop1 = 123;
changeDetector.detectChanges();
expect(DOM.getAttribute(view.nodes[0], 'foo-bar')).toEqual('123');
evalContext.prop1 = null;
changeDetector.detectChanges();
expect(DOM.getAttribute(view.nodes[0], 'foo-bar')).toBeNull();
});
it('should bind class with a dot', () => {
var propertyBindings = MapWrapper.createFromStringMap({
'class.bar': 'prop1',

View File

@ -23,162 +23,181 @@ export function main() {
});
it('should select by element name case insensitive', () => {
matcher.addSelectable(s1 = CssSelector.parse('someTag'), 1);
matcher.addSelectables(s1 = CssSelector.parse('someTag'), 1);
expect(matcher.match(CssSelector.parse('SOMEOTHERTAG'), selectableCollector)).toEqual(false);
expect(matcher.match(CssSelector.parse('SOMEOTHERTAG')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('SOMETAG'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]);
expect(matcher.match(CssSelector.parse('SOMETAG')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1]);
});
it('should select by class name case insensitive', () => {
matcher.addSelectable(s1 = CssSelector.parse('.someClass'), 1);
matcher.addSelectable(s2 = CssSelector.parse('.someClass.class2'), 2);
matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1);
matcher.addSelectables(s2 = CssSelector.parse('.someClass.class2'), 2);
expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS'), selectableCollector)).toEqual(false);
expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('.SOMECLASS'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]);
expect(matcher.match(CssSelector.parse('.SOMECLASS')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1]);
reset();
expect(matcher.match(CssSelector.parse('.someClass.class2'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2]);
expect(matcher.match(CssSelector.parse('.someClass.class2')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1,s2[0],2]);
});
it('should select by attr name case insensitive independent of the value', () => {
matcher.addSelectable(s1 = CssSelector.parse('[someAttr]'), 1);
matcher.addSelectable(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1);
matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]'), selectableCollector)).toEqual(false);
expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR]'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]);
expect(matcher.match(CssSelector.parse('[SOMEATTR]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1]);
reset();
expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]);
expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1]);
reset();
expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2]);
expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1,s2[0],2]);
});
it('should select by attr name only once if the value is from the DOM', () => {
matcher.addSelectable(s1 = CssSelector.parse('[some-decor]'), 1);
matcher.addSelectables(s1 = CssSelector.parse('[some-decor]'), 1);
var elementSelector = new CssSelector();
var element = el('<div attr></div>');
var empty = DOM.getAttribute(element, 'attr');
elementSelector.addAttribute('some-decor', empty);
matcher.match(elementSelector, selectableCollector);
expect(matched).toEqual([s1,1]);
expect(matched).toEqual([s1[0],1]);
});
it('should select by attr name and value case insensitive', () => {
matcher.addSelectable(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
matcher.addSelectables(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]'), selectableCollector)).toEqual(false);
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]);
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1]);
});
it('should select by element name, class name and attribute name with value', () => {
matcher.addSelectable(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
matcher.addSelectables(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
expect(matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]'), selectableCollector)).toEqual(false);
expect(matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('someTag.someOtherClass[someOtherAttr]'), selectableCollector)).toEqual(false);
expect(matcher.match(CssSelector.parse('someTag.someOtherClass[someOtherAttr]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('someTag.someClass[someOtherAttr]'), selectableCollector)).toEqual(false);
expect(matcher.match(CssSelector.parse('someTag.someClass[someOtherAttr]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr]'), selectableCollector)).toEqual(false);
expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]);
expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1]);
});
it('should select independent of the order in the css selector', () => {
matcher.addSelectable(s1 = CssSelector.parse('[someAttr].someClass'), 1);
matcher.addSelectable(s2 = CssSelector.parse('.someClass[someAttr]'), 2);
matcher.addSelectable(s3 = CssSelector.parse('.class1.class2'), 3);
matcher.addSelectable(s4 = CssSelector.parse('.class2.class1'), 4);
matcher.addSelectables(s1 = CssSelector.parse('[someAttr].someClass'), 1);
matcher.addSelectables(s2 = CssSelector.parse('.someClass[someAttr]'), 2);
matcher.addSelectables(s3 = CssSelector.parse('.class1.class2'), 3);
matcher.addSelectables(s4 = CssSelector.parse('.class2.class1'), 4);
expect(matcher.match(CssSelector.parse('[someAttr].someClass'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2]);
expect(matcher.match(CssSelector.parse('[someAttr].someClass')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1,s2[0],2]);
reset();
expect(matcher.match(CssSelector.parse('.someClass[someAttr]'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2]);
expect(matcher.match(CssSelector.parse('.someClass[someAttr]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1,s2[0],2]);
reset();
expect(matcher.match(CssSelector.parse('.class1.class2'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s3,3,s4,4]);
expect(matcher.match(CssSelector.parse('.class1.class2')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s3[0],3,s4[0],4]);
reset();
expect(matcher.match(CssSelector.parse('.class2.class1'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s4,4,s3,3]);
expect(matcher.match(CssSelector.parse('.class2.class1')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s4[0],4,s3[0],3]);
});
it('should not select with a matching :not selector', () => {
matcher.addSelectable(CssSelector.parse('p:not(.someClass)'), 1);
matcher.addSelectable(CssSelector.parse('p:not([someAttr])'), 2);
matcher.addSelectable(CssSelector.parse(':not(.someClass)'), 3);
matcher.addSelectable(CssSelector.parse(':not(p)'), 4);
matcher.addSelectable(CssSelector.parse(':not(p[someAttr])'), 5);
matcher.addSelectables(CssSelector.parse('p:not(.someClass)'), 1);
matcher.addSelectables(CssSelector.parse('p:not([someAttr])'), 2);
matcher.addSelectables(CssSelector.parse(':not(.someClass)'), 3);
matcher.addSelectables(CssSelector.parse(':not(p)'), 4);
matcher.addSelectables(CssSelector.parse(':not(p[someAttr])'), 5);
expect(matcher.match(CssSelector.parse('p.someClass[someAttr]'), selectableCollector)).toEqual(false);
expect(matcher.match(CssSelector.parse('p.someClass[someAttr]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
});
it('should select with a non matching :not selector', () => {
matcher.addSelectable(s1 = CssSelector.parse('p:not(.someClass)'), 1);
matcher.addSelectable(s2 = CssSelector.parse('p:not(.someOtherClass[someAttr])'), 2);
matcher.addSelectable(s3 = CssSelector.parse(':not(.someClass)'), 3);
matcher.addSelectable(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4);
matcher.addSelectables(s1 = CssSelector.parse('p:not(.someClass)'), 1);
matcher.addSelectables(s2 = CssSelector.parse('p:not(.someOtherClass[someAttr])'), 2);
matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3);
matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4);
expect(matcher.match(CssSelector.parse('p[someOtherAttr].someOtherClass'), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2,s3,3,s4,4]);
expect(matcher.match(CssSelector.parse('p[someOtherAttr].someOtherClass')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1,s2[0],2,s3[0],3,s4[0],4]);
});
it('should select with one match in a list', () => {
matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1);
expect(matcher.match(CssSelector.parse('textbox')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[1],1]);
reset();
expect(matcher.match(CssSelector.parse('input[type=text]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1]);
});
it('should not select twice with two matches in a list', () => {
matcher.addSelectables(s1 = CssSelector.parse('input, .someClass'), 1);
expect(matcher.match(CssSelector.parse('input.someclass')[0], selectableCollector)).toEqual(true);
expect(matched.length).toEqual(2);
expect(matched).toEqual([s1[0],1]);
});
});
describe('CssSelector.parse', () => {
it('should detect element names', () => {
var cssSelector = CssSelector.parse('sometag');
var cssSelector = CssSelector.parse('sometag')[0];
expect(cssSelector.element).toEqual('sometag');
expect(cssSelector.toString()).toEqual('sometag');
});
it('should detect class names', () => {
var cssSelector = CssSelector.parse('.someClass');
var cssSelector = CssSelector.parse('.someClass')[0];
expect(cssSelector.classNames).toEqual(['someclass']);
expect(cssSelector.toString()).toEqual('.someclass');
});
it('should detect attr names', () => {
var cssSelector = CssSelector.parse('[attrname]');
var cssSelector = CssSelector.parse('[attrname]')[0];
expect(cssSelector.attrs).toEqual(['attrname', '']);
expect(cssSelector.toString()).toEqual('[attrname]');
});
it('should detect attr values', () => {
var cssSelector = CssSelector.parse('[attrname=attrvalue]');
var cssSelector = CssSelector.parse('[attrname=attrvalue]')[0];
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
expect(cssSelector.toString()).toEqual('[attrname=attrvalue]');
});
it('should detect multiple parts', () => {
var cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass');
var cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass')[0];
expect(cssSelector.element).toEqual('sometag');
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
expect(cssSelector.classNames).toEqual(['someclass']);
@ -187,7 +206,7 @@ export function main() {
});
it('should detect :not', () => {
var cssSelector = CssSelector.parse('sometag:not([attrname=attrvalue].someclass)');
var cssSelector = CssSelector.parse('sometag:not([attrname=attrvalue].someclass)')[0];
expect(cssSelector.element).toEqual('sometag');
expect(cssSelector.attrs.length).toEqual(0);
expect(cssSelector.classNames.length).toEqual(0);
@ -201,7 +220,7 @@ export function main() {
});
it('should detect :not without truthy', () => {
var cssSelector = CssSelector.parse(':not([attrname=attrvalue].someclass)');
var cssSelector = CssSelector.parse(':not([attrname=attrvalue].someclass)')[0];
expect(cssSelector.element).toEqual("*");
var notSelector = cssSelector.notSelector;
@ -213,8 +232,31 @@ export function main() {
it('should throw when nested :not', () => {
expect(() => {
CssSelector.parse('sometag:not(:not([attrname=attrvalue].someclass))')
CssSelector.parse('sometag:not(:not([attrname=attrvalue].someclass))')[0];
}).toThrowError('Nesting :not is not allowed in a selector');
});
it('should detect lists of selectors', () => {
var cssSelectors = CssSelector.parse('.someclass,[attrname=attrvalue], sometag');
expect(cssSelectors.length).toEqual(3);
expect(cssSelectors[0].classNames).toEqual(['someclass']);
expect(cssSelectors[1].attrs).toEqual(['attrname', 'attrvalue']);
expect(cssSelectors[2].element).toEqual('sometag');
});
it('should detect lists of selectors with :not', () => {
var cssSelectors = CssSelector.parse('input[type=text], :not(textarea), textbox:not(.special)');
expect(cssSelectors.length).toEqual(3);
expect(cssSelectors[0].element).toEqual('input');
expect(cssSelectors[0].attrs).toEqual(['type', 'text']);
expect(cssSelectors[1].element).toEqual('*');
expect(cssSelectors[1].notSelector.element).toEqual('textarea');
expect(cssSelectors[2].element).toEqual('textbox');
expect(cssSelectors[2].notSelector.classNames).toEqual(['special']);
});
});
}

View File

@ -108,5 +108,10 @@ export function main() {
var css = s('x /deep/ y {}', 'a');
expect(css).toEqual('x[a] y[a] {}');
});
it('should handle >>>', () => {
var css = s('x >>> y {}', 'a');
expect(css).toEqual('x[a] y[a] {}');
});
});
}

View File

@ -25,12 +25,12 @@ import {Decorator, Component, Viewport} from 'angular2/src/core/annotations/anno
import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock';
import {Foreach} from 'angular2/src/directives/foreach';
import {For} from 'angular2/src/directives/for';
import {bind} from 'angular2/di';
export function main() {
describe('foreach', () => {
describe('for', () => {
var view, cd, compiler, component, tplResolver;
beforeEachBindings(() => [
@ -52,13 +52,13 @@ export function main() {
function compileWithTemplate(html) {
var template = new Template({
inline: html,
directives: [Foreach]
directives: [For]
});
tplResolver.setTemplate(TestComponent, template);
return compiler.compile(TestComponent);
}
var TEMPLATE = '<div><copy-me template="foreach #item in items">{{item.toString()}};</copy-me></div>';
var TEMPLATE = '<div><copy-me template="for #item of items">{{item.toString()}};</copy-me></div>';
it('should reflect initial elements', inject([AsyncTestCompleter], (async) => {
compileWithTemplate(TEMPLATE).then((pv) => {
@ -124,8 +124,8 @@ export function main() {
});
}));
it('should iterate over an array of objects', () => {
compileWithTemplate('<ul><li template="foreach #item in items">{{item["name"]}};</li></ul>').then((pv) => {
it('should iterate over an array of objects', inject([AsyncTestCompleter], (async) => {
compileWithTemplate('<ul><li template="for #item of items">{{item["name"]}};</li></ul>').then((pv) => {
createView(pv);
// INIT
@ -145,11 +145,12 @@ export function main() {
cd.detectChanges();
expect(DOM.getText(view.nodes[0])).toEqual('shyam;');
async.done();
});
});
}));
it('should gracefully handle nulls', inject([AsyncTestCompleter], (async) => {
compileWithTemplate('<ul><li template="foreach #item in null">{{item}};</li></ul>').then((pv) => {
compileWithTemplate('<ul><li template="for #item of null">{{item}};</li></ul>').then((pv) => {
createView(pv);
cd.detectChanges();
expect(DOM.getText(view.nodes[0])).toEqual('');
@ -199,10 +200,13 @@ export function main() {
it('should repeat over nested arrays', inject([AsyncTestCompleter], (async) => {
compileWithTemplate(
'<div><div template="foreach #item in items">' +
'<div template="foreach #subitem in item">' +
'{{subitem}}-{{item.length}};' +
'</div>|</div></div>'
'<div>'+
'<div template="for #item of items">' +
'<div template="for #subitem of item">' +
'{{subitem}}-{{item.length}};' +
'</div>|'+
'</div>'+
'</div>'
).then((pv) => {
createView(pv);
component.items = [['a', 'b'], ['c']];
@ -210,13 +214,38 @@ export function main() {
cd.detectChanges();
cd.detectChanges();
expect(DOM.getText(view.nodes[0])).toEqual('a-2;b-2;|c-1;|');
component.items = [['e'], ['f', 'g']];
cd.detectChanges();
expect(DOM.getText(view.nodes[0])).toEqual('e-1;|f-2;g-2;|');
async.done();
});
}));
it('should repeat over nested arrays with no intermediate element', inject([AsyncTestCompleter], (async) => {
compileWithTemplate(
'<div><template [for] #item [of]="items">' +
'<div template="for #subitem of item">' +
'{{subitem}}-{{item.length}};' +
'</div></template></div>'
).then((pv) => {
createView(pv);
component.items = [['a', 'b'], ['c']];
cd.detectChanges();
expect(DOM.getText(view.nodes[0])).toEqual('a-2;b-2;c-1;');
component.items = [['e'], ['f', 'g']];
cd.detectChanges();
expect(DOM.getText(view.nodes[0])).toEqual('e-1;f-2;g-2;');
async.done();
});
}));
it('should display indices correctly', inject([AsyncTestCompleter], (async) => {
var INDEX_TEMPLATE =
'<div><copy-me template="foreach: var item in items; var i=index">{{i.toString()}}</copy-me></div>';
'<div><copy-me template="for: var item of items; var i=index">{{i.toString()}}</copy-me></div>';
compileWithTemplate(INDEX_TEMPLATE).then((pv) => {
createView(pv);
component.items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

View File

@ -116,6 +116,39 @@ export function main() {
});
}));
it('should handle nested if correctly', inject([AsyncTestCompleter], (async) => {
compileWithTemplate('<div><template [if]="booleanCondition"><copy-me *if="nestedBooleanCondition">hello</copy-me></template></div>').then((pv) => {
createView(pv);
component.booleanCondition = false;
cd.detectChanges();
expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(0);
expect(DOM.getText(view.nodes[0])).toEqual('');
component.booleanCondition = true;
cd.detectChanges();
expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1);
expect(DOM.getText(view.nodes[0])).toEqual('hello');
component.nestedBooleanCondition = false;
cd.detectChanges();
expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(0);
expect(DOM.getText(view.nodes[0])).toEqual('');
component.nestedBooleanCondition = true;
cd.detectChanges();
expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1);
expect(DOM.getText(view.nodes[0])).toEqual('hello');
component.booleanCondition = false;
cd.detectChanges();
expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(0);
expect(DOM.getText(view.nodes[0])).toEqual('');
async.done();
});
}));
it('should update several nodes with if', inject([AsyncTestCompleter], (async) => {
var templateString =
'<div>' +
@ -195,11 +228,13 @@ export function main() {
@Component({selector: 'test-cmp'})
class TestComponent {
booleanCondition: boolean;
nestedBooleanCondition: boolean;
numberCondition: number;
stringCondition: string;
functionCondition: Function;
constructor() {
this.booleanCondition = true;
this.nestedBooleanCondition = true;
this.numberCondition = 1;
this.stringCondition = "foo";
this.functionCondition = function(s, n){

View File

@ -1,6 +1,5 @@
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib';
import {Control, FormBuilder} from 'angular2/forms';
import * as validations from 'angular2/forms';
import {Control, FormBuilder, Validators} from 'angular2/forms';
export function main() {
describe("Form Builder", () => {
@ -21,21 +20,21 @@ export function main() {
it("should create controls from an array", () => {
var g = b.group({
"login": ["some value"],
"password": ["some value", validations.required]
"password": ["some value", Validators.required]
});
expect(g.controls["login"].value).toEqual("some value");
expect(g.controls["password"].value).toEqual("some value");
expect(g.controls["password"].validator).toEqual(validations.required);
expect(g.controls["password"].validator).toEqual(Validators.required);
});
it("should use controls", () => {
var g = b.group({
"login": b.control("some value", validations.required)
"login": b.control("some value", Validators.required)
});
expect(g.controls["login"].value).toEqual("some value");
expect(g.controls["login"].validator).toBe(validations.required);
expect(g.controls["login"].validator).toBe(Validators.required);
});
it("should create groups with optional controls", () => {
@ -49,17 +48,17 @@ export function main() {
it("should create groups with a custom validator", () => {
var g = b.group({
"login": "some value"
}, {"validator": validations.nullValidator});
}, {"validator": Validators.nullValidator});
expect(g.validator).toBe(validations.nullValidator);
expect(g.validator).toBe(Validators.nullValidator);
});
it("should use default validators when no validators are provided", () => {
var g = b.group({
"login": "some value"
});
expect(g.controls["login"].validator).toBe(validations.nullValidator);
expect(g.validator).toBe(validations.controlGroupValidator);
expect(g.controls["login"].validator).toBe(Validators.nullValidator);
expect(g.validator).toBe(Validators.group);
});
});
}

View File

@ -24,16 +24,17 @@ import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mappe
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver';
import {CssProcessor} from 'angular2/src/core/compiler/css_processor';
import {EventManager, DomEventsPlugin} from 'angular2/src/core/events/event_manager';
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock';
import {Injector} from 'angular2/di';
import {Component, Decorator, Template} from 'angular2/angular2';
import {Component, Decorator, Template, PropertySetter} from 'angular2/angular2';
import {ControlGroupDirective, ControlDirective, Control, ControlGroup, OptionalControl,
ControlValueAccessor, RequiredValidatorDirective} from 'angular2/forms';
import * as validators from 'angular2/src/forms/validators';
ControlValueAccessor, RequiredValidatorDirective, CheckboxControlValueAccessor,
DefaultValueAccessor, Validators} from 'angular2/forms';
export function main() {
function detectChanges(view) {
@ -58,35 +59,37 @@ export function main() {
tplResolver.setTemplate(componentType, new Template({
inline: template,
directives: [ControlGroupDirective, ControlDirective, WrappedValue, RequiredValidatorDirective]
directives: [ControlGroupDirective, ControlDirective, WrappedValue, RequiredValidatorDirective,
CheckboxControlValueAccessor, DefaultValueAccessor]
}));
compiler.compile(componentType).then((pv) => {
var view = pv.instantiate(null, null);
var eventManager = new EventManager([new DomEventsPlugin()], new FakeVmTurnZone());
var view = pv.instantiate(null, eventManager);
view.hydrate(new Injector([]), null, null, context, null);
detectChanges(view);
callback(view);
});
}
describe("integration tests", () => {
it("should initialize DOM elements with the given form object", inject([AsyncTestCompleter], (async) => {
var ctx = new MyComp(new ControlGroup({
"login": new Control("loginValue")
}));
if (DOM.supportsDOMEvents()) {
describe("integration tests", () => {
it("should initialize DOM elements with the given form object", inject([AsyncTestCompleter], (async) => {
var ctx = new MyComp(new ControlGroup({
"login": new Control("loginValue")
}));
var t = `<div [control-group]="form">
var t = `<div [control-group]="form">
<input type="text" control="login">
</div>`;
compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input")
expect(input.value).toEqual("loginValue");
async.done();
});
}));
compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input")
expect(input.value).toEqual("loginValue");
async.done();
});
}));
if (DOM.supportsDOMEvents()) {
it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({
"login": new Control("oldValue")
@ -107,55 +110,110 @@ export function main() {
async.done();
});
}));
}
it("should update DOM elements when rebinding the control group", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({
"login": new Control("oldValue")
});
var ctx = new MyComp(form);
it("should update DOM elements when rebinding the control group", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({
"login": new Control("oldValue")
});
var ctx = new MyComp(form);
var t = `<div [control-group]="form">
var t = `<div [control-group]="form">
<input type="text" control="login">
</div>`;
compile(MyComp, t, ctx, (view) => {
ctx.form = new ControlGroup({
"login": new Control("newValue")
compile(MyComp, t, ctx, (view) => {
ctx.form = new ControlGroup({
"login": new Control("newValue")
});
detectChanges(view);
var input = queryView(view, "input")
expect(input.value).toEqual("newValue");
async.done();
});
detectChanges(view);
}));
var input = queryView(view, "input")
expect(input.value).toEqual("newValue");
async.done();
});
}));
it("should update DOM element when rebinding the control name", inject([AsyncTestCompleter], (async) => {
var ctx = new MyComp(new ControlGroup({
"one": new Control("one"),
"two": new Control("two")
}), "one");
it("should update DOM element when rebinding the control name", inject([AsyncTestCompleter], (async) => {
var ctx = new MyComp(new ControlGroup({
"one": new Control("one"),
"two": new Control("two")
}), "one");
var t = `<div [control-group]="form">
var t = `<div [control-group]="form">
<input type="text" [control]="name">
</div>`;
compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input")
expect(input.value).toEqual("one");
compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input")
expect(input.value).toEqual("one");
ctx.name = "two";
detectChanges(view);
ctx.name = "two";
detectChanges(view);
expect(input.value).toEqual("two");
async.done();
});
}));
expect(input.value).toEqual("two");
async.done();
});
}));
if (DOM.supportsDOMEvents()) {
describe("different control types", () => {
it("should support type=checkbox", inject([AsyncTestCompleter], (async) => {
it("should support <input type=text>", inject([AsyncTestCompleter], (async) => {
var ctx = new MyComp(new ControlGroup({"text": new Control("old")}));
var t = `<div [control-group]="form">
<input type="text" control="text">
</div>`;
compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input")
expect(input.value).toEqual("old");
input.value = "new";
dispatchEvent(input, "input");
expect(ctx.form.value).toEqual({"text": "new"});
async.done();
});
}));
it("should support <input> without type", inject([AsyncTestCompleter], (async) => {
var ctx = new MyComp(new ControlGroup({"text": new Control("old")}));
var t = `<div [control-group]="form">
<input control="text">
</div>`;
compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input")
expect(input.value).toEqual("old");
input.value = "new";
dispatchEvent(input, "input");
expect(ctx.form.value).toEqual({"text": "new"});
async.done();
});
}));
it("should support <textarea>", inject([AsyncTestCompleter], (async) => {
var ctx = new MyComp(new ControlGroup({"text": new Control('old')}));
var t = `<div [control-group]="form">
<textarea control="text"></textarea>
</div>`;
compile(MyComp, t, ctx, (view) => {
var textarea = queryView(view, "textarea")
expect(textarea.value).toEqual("old");
textarea.value = "new";
dispatchEvent(textarea, "input");
expect(ctx.form.value).toEqual({"text": 'new'});
async.done();
});
}));
it("should support <type=checkbox>", inject([AsyncTestCompleter], (async) => {
var ctx = new MyComp(new ControlGroup({"checkbox": new Control(true)}));
var t = `<div [control-group]="form">
@ -169,31 +227,12 @@ export function main() {
input.checked = false;
dispatchEvent(input, "change");
expect(ctx.form.value).toEqual({"checkbox" : false});
expect(ctx.form.value).toEqual({"checkbox": false});
async.done();
});
}));
it("should support textarea", inject([AsyncTestCompleter], (async) => {
var ctx = new MyComp(new ControlGroup({"text": new Control('old')}));
var t = `<div [control-group]="form">
<textarea control="text"></textarea>
</div>`;
compile(MyComp, t, ctx, (view) => {
var textarea = queryView(view, "textarea")
expect(textarea.value).toEqual("old");
textarea.value = "new";
dispatchEvent(textarea, "change");
expect(ctx.form.value).toEqual({"text" : 'new'});
async.done();
});
}));
it("should support select", inject([AsyncTestCompleter], (async) => {
it("should support <select>", inject([AsyncTestCompleter], (async) => {
var ctx = new MyComp(new ControlGroup({"city": new Control("SF")}));
var t = `<div [control-group]="form">
@ -212,7 +251,7 @@ export function main() {
select.value = 'NYC';
dispatchEvent(select, "change");
expect(ctx.form.value).toEqual({"city" : 'NYC'});
expect(ctx.form.value).toEqual({"city": 'NYC'});
expect(sfOption.selected).toBe(false);
async.done();
});
@ -232,7 +271,7 @@ export function main() {
input.value = "!bb!";
dispatchEvent(input, "change");
expect(ctx.form.value).toEqual({"name" : "bb"});
expect(ctx.form.value).toEqual({"name": "bb"});
async.done();
});
}));
@ -261,7 +300,7 @@ export function main() {
}));
it("should use validators defined in the model", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({"login": new Control("aa", validators.required)});
var form = new ControlGroup({"login": new Control("aa", Validators.required)});
var ctx = new MyComp(form);
var t = `<div [control-group]="form">
@ -281,31 +320,29 @@ export function main() {
});
}));
});
}
describe("nested forms", () => {
it("should init DOM with the given form object",inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({
"nested": new ControlGroup({
"login": new Control("value")
})
});
var ctx = new MyComp(form);
describe("nested forms", () => {
it("should init DOM with the given form object", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({
"nested": new ControlGroup({
"login": new Control("value")
})
});
var ctx = new MyComp(form);
var t = `<div [control-group]="form">
var t = `<div [control-group]="form">
<div control-group="nested">
<input type="text" control="login">
</div>
</div>`;
compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input")
expect(input.value).toEqual("value");
async.done();
});
}));
compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input")
expect(input.value).toEqual("value");
async.done();
});
}));
if (DOM.supportsDOMEvents()) {
it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({
"nested": new ControlGroup({
@ -326,13 +363,13 @@ export function main() {
input.value = "updatedValue";
dispatchEvent(input, "change");
expect(form.value).toEqual({"nested" : {"login" : "updatedValue"}});
expect(form.value).toEqual({"nested": {"login": "updatedValue"}});
async.done();
});
}));
}
});
});
});
}
}
@Component({
@ -348,21 +385,42 @@ class MyComp {
}
}
class WrappedValueAccessor extends ControlValueAccessor {
readValue(el){
return el.value.substring(1, el.value.length - 1);
}
writeValue(el, value):void {
el.value = `!${value}!`;
}
}
@Decorator({
selector:'[wrapped-value]'
selector:'[wrapped-value]',
events: {
'change' : 'handleOnChange($event.target.value)'
}
})
class WrappedValue {
constructor(cd:ControlDirective) {
cd.valueAccessor = new WrappedValueAccessor();
_setProperty:Function;
onChange:Function;
constructor(cd:ControlDirective, @PropertySetter('value') setProperty:Function) {
super();
this._setProperty = setProperty;
cd.valueAccessor = this;
}
writeValue(value) {
this._setProperty(`!${value}!`);
}
handleOnChange(value) {
this.onChange(value.substring(1, value.length - 1));
}
}
class FakeVmTurnZone extends VmTurnZone {
constructor() {
super({enableLongStackTrace: false});
}
run(fn) {
fn();
}
runOutsideAngular(fn) {
fn();
}
}

View File

@ -1,24 +1,23 @@
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib';
import {ControlGroup, Control, OptionalControl} from 'angular2/forms';
import * as validations from 'angular2/forms';
import {ControlGroup, Control, OptionalControl, Validators} from 'angular2/forms';
export function main() {
describe("Form Model", () => {
describe("Control", () => {
describe("validator", () => {
it("should run validator with the initial value", () => {
var c = new Control("value", validations.required);
var c = new Control("value", Validators.required);
expect(c.valid).toEqual(true);
});
it("should rerun the validator when the value changes", () => {
var c = new Control("value", validations.required);
var c = new Control("value", Validators.required);
c.updateValue(null);
expect(c.valid).toEqual(false);
});
it("should return errors", () => {
var c = new Control(null, validations.required);
var c = new Control(null, Validators.required);
expect(c.errors).toEqual({"required" : true});
});
});
@ -83,7 +82,7 @@ export function main() {
describe("validator", () => {
it("should run the validator with the initial value (valid)", () => {
var g = new ControlGroup({
"one": new Control('value', validations.required)
"one": new Control('value', Validators.required)
});
expect(g.valid).toEqual(true);
@ -92,7 +91,7 @@ export function main() {
});
it("should run the validator with the initial value (invalid)", () => {
var one = new Control(null, validations.required);
var one = new Control(null, Validators.required);
var g = new ControlGroup({"one": one});
expect(g.valid).toEqual(false);
@ -101,7 +100,7 @@ export function main() {
});
it("should run the validator with the value changes", () => {
var c = new Control(null, validations.required);
var c = new Control(null, Validators.required);
var g = new ControlGroup({"one": c});
c.updateValue("some value");
@ -174,10 +173,10 @@ export function main() {
expect(group.value).toEqual({"required" : "requiredValue", "optional" : "optionalValue"});
});
it("should not run validations on an inactive component", () => {
it("should not run Validators on an inactive component", () => {
var group = new ControlGroup({
"required": new Control("requiredValue", validations.required),
"optional": new Control("", validations.required)
"required": new Control("requiredValue", Validators.required),
"optional": new Control("", Validators.required)
}, {
"optional": false
});

View File

@ -1,5 +1,5 @@
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib';
import {ControlGroup, Control, required, compose, controlGroupValidator, nullValidator} from 'angular2/forms';
import {ControlGroup, Control, Validators} from 'angular2/forms';
export function main() {
function validator(key:string, error:any){
@ -13,31 +13,31 @@ export function main() {
describe("Validators", () => {
describe("required", () => {
it("should error on an empty string", () => {
expect(required(new Control(""))).toEqual({"required" : true});
expect(Validators.required(new Control(""))).toEqual({"required" : true});
});
it("should error on null", () => {
expect(required(new Control(null))).toEqual({"required" : true});
expect(Validators.required(new Control(null))).toEqual({"required" : true});
});
it("should not error on a non-empty string", () => {
expect(required(new Control("not empty"))).toEqual(null);
expect(Validators.required(new Control("not empty"))).toEqual(null);
});
});
describe("compose", () => {
it("should collect errors from all the validators", () => {
var c = compose([validator("a", true), validator("b", true)]);
var c = Validators.compose([validator("a", true), validator("b", true)]);
expect(c(new Control(""))).toEqual({"a" : true, "b" : true});
});
it("should run validators left to right", () => {
var c = compose([validator("a", 1), validator("a", 2)]);
var c = Validators.compose([validator("a", 1), validator("a", 2)]);
expect(c(new Control(""))).toEqual({"a" : 2});
});
it("should return null when no errors", () => {
var c = compose([nullValidator, nullValidator]);
var c = Validators.compose([Validators.nullValidator, Validators.nullValidator]);
expect(c(new Control(""))).toEqual(null);
});
});
@ -48,7 +48,7 @@ export function main() {
var two = new Control("one", validator("b", true));
var g = new ControlGroup({"one" : one, "two" : two});
expect(controlGroupValidator(g)).toEqual({
expect(Validators.group(g)).toEqual({
"a" : [one],
"b" : [two]
});
@ -59,7 +59,7 @@ export function main() {
var two = new Control("two");
var g = new ControlGroup({"one" : one, "two" : two});
expect(controlGroupValidator(g)).toEqual({
expect(Validators.group(g)).toEqual({
"a": [one]
});
});
@ -69,7 +69,7 @@ export function main() {
"one" : new Control("one")
});
expect(controlGroupValidator(g)).toEqual(null);
expect(Validators.group(g)).toEqual(null);
});
});
});

View File

@ -7,7 +7,7 @@ import 'package:angular2/src/transform/common/formatter.dart';
import 'package:code_transformers/tests.dart';
import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as path;
import 'package:unittest/unittest.dart';
import 'package:guinness/guinness.dart';
import 'package:unittest/vm_config.dart';
import '../common/read_file.dart';
@ -17,7 +17,7 @@ var formatter = new DartFormatter();
void allTests() {
var reader = new TestAssetReader();
test('should generate a setter for a `bind` property in an annotation.',
it('should generate a setter for a `bind` property in an annotation.',
() async {
var inputPath = 'bind_generator/basic_bind_files/bar.ng_deps.dart';
var expected = formatter.format(
@ -25,10 +25,10 @@ void allTests() {
var output = formatter
.format(await createNgSetters(reader, new AssetId('a', inputPath)));
expect(output, equals(expected));
expect(output).toEqual(expected);
});
test('should generate a single setter when multiple annotations bind to the '
it('should generate a single setter when multiple annotations bind to the '
'same property.', () async {
var inputPath =
'bind_generator/duplicate_bind_name_files/soup.ng_deps.dart';
@ -37,6 +37,6 @@ void allTests() {
var output = formatter
.format(await createNgSetters(reader, new AssetId('a', inputPath)));
expect(output, equals(expected));
expect(output).toEqual(expected);
});
}

View File

@ -0,0 +1,23 @@
library angular2.test.transform.common.logger;
import 'package:code_transformers/messages/build_logger.dart';
class NullLogger implements BuildLogger {
const NullLogger();
void info(String message, {AssetId asset, SourceSpan span}) {}
void fine(String message, {AssetId asset, SourceSpan span}) {}
void warning(String message, {AssetId asset, SourceSpan span}) {}
void error(String message, {AssetId asset, SourceSpan span}) {
throw new NullLoggerError(message, asset, span);
}
Future writeOutput() => null;
Future addLogFilesFromAsset(AssetId id, [int nextNumber = 1]) => null;
}
class NullLoggerError extends Error {
final String message;
final AssetId asset;
final SourceSpan span;
NullLoggerError(message, asset, span);
}

View File

@ -0,0 +1,52 @@
library angular2.test.transform.directive_linker.all_tests;
import 'package:barback/barback.dart';
import 'package:angular2/src/transform/common/logging.dart' hide init;
import 'package:angular2/src/transform/common/formatter.dart' hide init;
import 'package:angular2/src/transform/directive_linker/linker.dart';
import 'package:code_transformers/tests.dart';
import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as path;
import 'package:guinness/guinness.dart';
import 'package:unittest/vm_config.dart';
import '../common/logger.dart';
import '../common/read_file.dart';
var formatter = new DartFormatter();
void allTests() {
var reader = new TestAssetReader();
setLogger(new NullLogger());
it('should ensure that dependencies are property chained.', () async {
for (var inputPath in [
'bar.ng_deps.dart',
'foo.ng_deps.dart',
'index.ng_deps.dart'
]) {
var expected =
readFile('directive_linker/simple_files/expected/$inputPath');
inputPath = 'directive_linker/simple_files/$inputPath';
var actual = formatter
.format(await linkNgDeps(reader, new AssetId('a', inputPath)));
expect(actual).toEqual(expected);
}
});
it('should ensure that exported dependencies are property chained.',
() async {
for (var inputPath in [
'bar.ng_deps.dart',
'foo.ng_deps.dart',
'index.ng_deps.dart'
]) {
var expected =
readFile('directive_linker/simple_export_files/expected/$inputPath');
inputPath = 'directive_linker/simple_export_files/$inputPath';
var actual = formatter
.format(await linkNgDeps(reader, new AssetId('a', inputPath)));
expect(actual).toEqual(expected);
}
});
}

View File

@ -0,0 +1,17 @@
library bar;
import 'bar.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
export 'foo.dart';
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(MyComponent, {
'factory': () => new MyComponent(),
'parameters': const [],
'annotations': const [const Component(selector: '[soup]')]
});
}

View File

@ -0,0 +1,20 @@
library bar;
import 'bar.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
import 'foo.ng_deps.dart' as i0;
export 'foo.dart';
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(MyComponent, {
'factory': () => new MyComponent(),
'parameters': const [],
'annotations': const [const Component(selector: '[soup]')]
});
i0.setupReflection(reflector);
}

View File

@ -3,7 +3,7 @@ library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'bar.dart';
import 'a:web/bar.ng_deps.dart' as i0;
import 'bar.ng_deps.dart' as i0;
bool _visited = false;
void setupReflection(reflector) {

View File

@ -0,0 +1,16 @@
library foo;
import 'foo.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(DependencyComponent, {
'factory': () => new DependencyComponent(),
'parameters': const [],
'annotations': const [const Component(selector: '[salad]')]
});
}

View File

@ -4,7 +4,8 @@ import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'bar.dart';
void main() {
reflector.reflectionCapabilities = new ReflectionCapabilities();
bootstrap(MyComponent);
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
}

View File

@ -0,0 +1,20 @@
library bar;
import 'bar.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
import 'foo.dart' as dep;
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(MyComponent, {
'factory': () => new MyComponent(),
'parameters': const [],
'annotations': const [
const Component(
selector: '[soup]', services: const [dep.DependencyComponent])
]
});
}

View File

@ -0,0 +1,16 @@
library foo;
import 'foo.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(DependencyComponent, {
'factory': () => new DependencyComponent(),
'parameters': const [],
'annotations': const [const Component(selector: '[salad]')]
});
}

View File

@ -0,0 +1,13 @@
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'bar.dart';
import 'bar.ng_deps.dart' as i0;
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
i0.setupReflection(reflector);
}

View File

@ -0,0 +1,16 @@
library foo;
import 'foo.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(DependencyComponent, {
'factory': () => new DependencyComponent(),
'parameters': const [],
'annotations': const [const Component(selector: '[salad]')]
});
}

View File

@ -0,0 +1,11 @@
library web_foo;
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'bar.dart';
bool _visited = false;
void setupReflection(reflector) {
if (_visited) return;
_visited = true;
}

View File

@ -7,7 +7,7 @@ import 'package:angular2/src/transform/common/formatter.dart';
import 'package:code_transformers/tests.dart';
import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as path;
import 'package:unittest/unittest.dart';
import 'package:guinness/guinness.dart';
import 'package:unittest/vm_config.dart';
import '../common/read_file.dart';
@ -15,12 +15,12 @@ import '../common/read_file.dart';
var formatter = new DartFormatter();
void allTests() {
test('should preserve parameter annotations as const instances.', () {
it('should preserve parameter annotations as const instances.', () {
var inputPath = 'parameter_metadata/soup.dart';
var expected = _readFile('parameter_metadata/expected/soup.ng_deps.dart');
var output =
formatter.format(createNgDeps(_readFile(inputPath), inputPath));
expect(output, equals(expected));
expect(output).toEqual(expected);
});
}

View File

@ -14,4 +14,4 @@ void setupReflection(reflector) {
'parameters': const [const [Tasty, String], const [const Inject(Salt)]],
'annotations': const [const Component(selector: '[soup]')]
});
} // {"version":1,"importOffset":104,"registerOffset":451,"imports":["package:angular2/src/core/annotations/annotations.dart"]}
}

View File

@ -5,7 +5,7 @@ import 'package:angular2/src/dom/html5lib_adapter.dart';
import 'package:angular2/transformer.dart';
import 'package:code_transformers/tests.dart';
import 'package:dart_style/dart_style.dart';
import 'package:unittest/unittest.dart';
import 'package:guinness/guinness.dart';
import '../common/read_file.dart';
@ -96,17 +96,6 @@ void allTests() {
outputs: {
'a|web/bar.ng_deps.dart':
'two_annotations_files/expected/bar.ng_deps.dart'
}),
new IntegrationTestConfig(
'should ensure that dependencies are property chained.',
inputs: {
'a|web/index.dart': 'chained_deps_files/index.dart',
'a|web/foo.dart': 'chained_deps_files/foo.dart',
'a|web/bar.dart': 'chained_deps_files/bar.dart'
},
outputs: {
'a|web/bar.ng_deps.dart': 'chained_deps_files/expected/bar.ng_deps.dart',
'a|web/foo.ng_deps.dart': 'chained_deps_files/expected/foo.ng_deps.dart'
})
];

View File

@ -1,9 +0,0 @@
library bar;
import 'package:angular2/src/core/annotations/annotations.dart';
import 'foo.dart' as dep;
@Component(selector: '[soup]', services: const [dep.DependencyComponent])
class MyComponent {
MyComponent();
}

View File

@ -1,8 +0,0 @@
library foo;
import 'package:angular2/src/core/annotations/annotations.dart';
@Component(selector: '[salad]')
class DependencyComponent {
DependencyComponent();
}

View File

@ -3,7 +3,7 @@ library angular2.test.transform.reflection_remover;
import 'package:analyzer/analyzer.dart';
import 'package:angular2/src/transform/reflection_remover/codegen.dart';
import 'package:angular2/src/transform/reflection_remover/rewriter.dart';
import 'package:unittest/unittest.dart';
import 'package:guinness/guinness.dart';
import 'reflection_remover_files/expected/index.dart' as expected;
import '../common/read_file.dart';
@ -11,11 +11,11 @@ import '../common/read_file.dart';
void allTests() {
var codegen = new Codegen('web/index.dart', 'web/index.ng_deps.dart');
test('should remove uses of mirrors & insert calls to generated code.', () {
it('should remove uses of mirrors & insert calls to generated code.', () {
var code =
readFile('reflection_remover/reflection_remover_files/index.dart');
var output =
new Rewriter(code, codegen).rewrite(parseCompilationUnit(code));
expect(output, equals(expected.code));
expect(output).toEqual(expected.code);
});
}

View File

@ -6,7 +6,7 @@ import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/formatter.dart';
import 'package:angular2/src/transform/template_compiler/generator.dart';
import 'package:dart_style/dart_style.dart';
import 'package:unittest/unittest.dart';
import 'package:guinness/guinness.dart';
import '../common/read_file.dart';
@ -16,7 +16,7 @@ void allTests() {
Html5LibDomAdapter.makeCurrent();
AssetReader reader = new TestAssetReader();
test('should parse simple expressions in inline templates.', () async {
it('should parse simple expressions in inline templates.', () async {
var inputPath =
'template_compiler/inline_expression_files/hello.ng_deps.dart';
var expected = readFile(
@ -24,16 +24,16 @@ void allTests() {
var output = await processTemplates(reader, new AssetId('a', inputPath));
output = formatter.format(output);
expected = formatter.format(expected);
expect(output, equals(expected));
expect(output).toEqual(expected);
});
test('should parse simple methods in inline templates.', () async {
it('should parse simple methods in inline templates.', () async {
var inputPath = 'template_compiler/inline_method_files/hello.ng_deps.dart';
var expected = readFile(
'template_compiler/inline_method_files/expected/hello.ng_deps.dart');
var output = await processTemplates(reader, new AssetId('a', inputPath));
output = formatter.format(output);
expected = formatter.format(expected);
expect(output, equals(expected));
expect(output).toEqual(expected);
});
}

View File

@ -1,4 +1,4 @@
library examples.src.hello_world.index_common_dart;
library examples.hello_world.index_common_dart;
import 'hello.dart';
import 'package:angular2/angular2.dart'

View File

@ -1,4 +1,4 @@
library examples.src.hello_world.index_common_dart;
library examples.hello_world.index_common_dart;
import 'hello.dart';
import 'package:angular2/angular2.dart'

View File

@ -1,4 +1,4 @@
library examples.src.hello_world.index_common_dart;
library examples.hello_world.index_common_dart;
import 'hello.dart';
import 'package:angular2/angular2.dart'

View File

@ -1,4 +1,4 @@
library examples.src.hello_world.index_common_dart;
library examples.hello_world.index_common_dart;
import 'hello.dart';
import 'package:angular2/angular2.dart'

View File

@ -1,9 +1,11 @@
library angular2.test.transform;
import 'package:unittest/unittest.dart';
import 'package:guinness/guinness.dart';
import 'package:unittest/unittest.dart' hide expect;
import 'package:unittest/vm_config.dart';
import 'bind_generator/all_tests.dart' as bindGenerator;
import 'directive_linker/all_tests.dart' as directiveLinker;
import 'directive_processor/all_tests.dart' as directiveProcessor;
import 'integration/all_tests.dart' as integration;
import 'reflection_remover/all_tests.dart' as reflectionRemover;
@ -11,9 +13,12 @@ import 'template_compiler/all_tests.dart' as templateCompiler;
main() {
useVMConfiguration();
group('Bind Generator', bindGenerator.allTests);
group('Directive Processor', directiveProcessor.allTests);
group('Reflection Remover', reflectionRemover.allTests);
group('Template Compiler', templateCompiler.allTests);
describe('Bind Generator', bindGenerator.allTests);
describe('Directive Linker', directiveLinker.allTests);
describe('Directive Processor', directiveProcessor.allTests);
describe('Reflection Remover', reflectionRemover.allTests);
describe('Template Compiler', templateCompiler.allTests);
// NOTE(kegluneq): These use `code_transformers#testPhases`, which is not
// designed to work with `guinness`.
group('Transformer Pipeline', integration.allTests);
}

View File

@ -78,6 +78,9 @@ function setupReflector() {
"value0": (a,v) => a.value0 = v, "value1": (a,v) => a.value1 = v,
"value2": (a,v) => a.value2 = v, "value3": (a,v) => a.value3 = v, "value4": (a,v) => a.value4 = v,
"attr0": (a,v) => a.attr0 = v, "attr1": (a,v) => a.attr1 = v,
"attr2": (a,v) => a.attr2 = v, "attr3": (a,v) => a.attr3 = v, "attr4": (a,v) => a.attr4 = v,
"prop": (a,v) => a.prop = v
});
}

Some files were not shown because too many files have changed in this diff Show More