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 ```shell
# Install Angular project dependencies (package.json) # Install Angular project dependencies (package.json)
npm install 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 **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 (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 particular `gulp` and `protractor` commands. If you prefer, you can drop this path prefix by either:
globally installing these two packages as follows:
*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 gulp` (you might need to prefix this command with `sudo`)
* `npm install -g protractor` (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 ## Build commands
@ -133,22 +132,35 @@ $(npm bin)/gulp clean
## Running Tests Locally ## 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 * `npm test`: full test suite for both JS and Dart versions of Angular. These are the same tests as
watches the test files for changes and re-runs tests when files are updated). those run on Travis.
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**. 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 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.js/ci`
* `$(npm bin)/gulp test.unit.cjs/ci`
* `$(npm bin)/gulp test.unit.dart/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 **Note**: If you want to only run a single test you can alter the test you wish to run by changing
to run by changing `it` to `iit` or `describe` to `ddescribe`. This will only `it` to `iit` or `describe` to `ddescribe`. This will only run that individual test and make it
run that individual test and make it much easier to debug. `xit` and `xdescribe` much easier to debug. `xit` and `xdescribe` can also be useful to exclude a test and a group of
can also be useful to exclude a test and a group of tests respectively. tests respectively.
**Note** for transpiler tests: The karma preprocessor is setup in a way so that after every test **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 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. 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 `debugger;` statement is needed because WebStorm will stop in a transpiled file. Breakpoints in
the original source files are not supported at the moment. 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: You can build this example as either JS or Dart app:
* JS: * JS:
* `$(npm bin)/gulp build.js.dev`, and
* `$(npm bin)/gulp serve.js.dev`, and * `$(npm bin)/gulp serve.js.dev`, and
* open `localhost:8000/examples/src/hello_world/` in Chrome. * open `localhost:8000/examples/src/hello_world/` in Chrome.
* Dart: * 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 // Configure ids and paths
.config(function(computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES) { .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); log.silly('tree: ' + tree.constructor.name + ' - ' + tree.location.start.line);
while (this.currentComment && while (this.currentComment &&
this.currentComment.range.end.offset < tree.location.start.offset) { this.currentComment.range.end.offset < tree.location.start.offset) {
log.silly('comment: ' + this.currentComment.range.start.line + ' - ' + var commentText = this.currentComment.range.toString();
this.currentComment.range.end.line + ' : ' +
this.currentComment.range.toString()); // Only store the comment if it is JSDOC style (e.g. /** some comment */)
tree.commentBefore = this.currentComment; if (/^\/\*\*([\w\W]*)\*\/$/.test(commentText)) {
this.currentComment.treeAfter = tree; 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.index++;
this.currentComment = this.comments[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')) .processor(require('./processors/filterPublicDocs'))
.config(function(parseTagsProcessor) {
parseTagsProcessor.tagDefinitions.push({ name: 'publicModule' });
})
.config(function(processClassDocs, filterPublicDocs, EXPORT_DOC_TYPES) { .config(function(processClassDocs, filterPublicDocs, EXPORT_DOC_TYPES) {
processClassDocs.ignorePrivateMembers = true; processClassDocs.ignorePrivateMembers = true;
filterPublicDocs.docTypes = EXPORT_DOC_TYPES; filterPublicDocs.docTypes = EXPORT_DOC_TYPES;

View File

@ -1,5 +1,6 @@
var gulp = require('gulp'); var gulp = require('gulp');
var gulpPlugins = require('gulp-load-plugins')(); var gulpPlugins = require('gulp-load-plugins')();
var shell = require('gulp-shell');
var runSequence = require('run-sequence'); var runSequence = require('run-sequence');
var madge = require('madge'); var madge = require('madge');
var merge = require('merge'); var merge = require('merge');
@ -26,6 +27,7 @@ var runServerDartTests = require('./tools/build/run_server_dart_tests');
var transformCJSTests = require('./tools/build/transformCJSTests'); var transformCJSTests = require('./tools/build/transformCJSTests');
var util = require('./tools/build/util'); 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); var DART_SDK = require('./tools/build/dartdetect')(gulp);
// ----------------------- // -----------------------
// configuration // configuration
@ -431,7 +433,11 @@ gulp.task('build/multicopy.dart', copy.multicopy(gulp, gulpPlugins, {
// ------------ // ------------
// pubspec // 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, dir: CONFIG.dest.dart,
command: DART_SDK.PUB command: DART_SDK.PUB
})); }));
@ -588,6 +594,22 @@ createDocsTasks(true);
createDocsTasks(false); 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 // karma tests
// These tests run in the browser and are allowed to access // These tests run in the browser and are allowed to access
// HTML DOM APIs. // HTML DOM APIs.

View File

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

View File

@ -2,6 +2,7 @@ export * from './src/core/annotations/visibility';
export * from './src/core/compiler/interfaces'; export * from './src/core/compiler/interfaces';
export * from './src/core/annotations/template'; export * from './src/core/annotations/template';
export * from './src/core/application'; export * from './src/core/application';
export * from './src/core/annotations/di';
export * from './src/core/compiler/compiler'; 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/if';
export * from './src/directives/non_bindable'; export * from './src/directives/non_bindable';
export * from './src/directives/switch'; export * from './src/directives/switch';

View File

@ -187,7 +187,7 @@ Example:
<pre> <pre>
``` ```
<ul> <ul>
<li template="foreach: #item in items"> <li template="for: #item of items">
{{item}} {{item}}
</li> </li>
</ul> </ul>
@ -201,8 +201,8 @@ Example:
<pre> <pre>
``` ```
<ul> <ul>
<template def-foreach:"item" <template def-for:"item"
bind-foreach-in="items"> bind-for-in="items">
<li> <li>
{{item}} {{item}}
</li> </li>
@ -221,8 +221,8 @@ Example:
<pre> <pre>
``` ```
<template #foreach="item" <template #for="item"
[foreach-in]="items"> [for-in]="items">
_some_content_to_repeat_ _some_content_to_repeat_
</template> </template>
``` ```
@ -234,8 +234,8 @@ Example:
Example: Example:
<pre> <pre>
``` ```
<template def-foreach="item" <template def-for="item"
bind-foreach-in="items"> bind-for-in="items">
_some_content_to_repeat_ _some_content_to_repeat_
</template> </template>
``` ```
@ -408,22 +408,22 @@ NOTE: Only Viewport directives can be placed on the template element. (Decorator
### Template Microsyntax ### Template Microsyntax
Often times it is necessary to encode a lot of different bindings into a template to control how the instantiation 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 #foo=form>
</form> </form>
<ul> <ul>
<template foreach #person [in]="people" #i="index"> <template for #person [in]="people" #i="index">
<li>{{i}}. {{person}}<li> <li>{{i}}. {{person}}<li>
</template> </template>
</ul> </ul>
``` ```
Where: Where:
* `foreach` triggers the foreach directive. * `for` triggers the for directive.
* `[in]="people"` binds an iterable object to the `foreach` controller. * `[in]="people"` binds an iterable object to the `for` controller.
* `#person` exports the implicit `foreach` item. * `#person` exports the implicit `for` item.
* `#i=index` exports item index as `i`. * `#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 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> <ul>
<li template="foreach; #person; in=people; #i=index;">{{i}}. {{person}}<li> <li template="for; #person; in=people; #i=index;">{{i}}. {{person}}<li>
</ul> </ul>
``` ```
@ -441,19 +441,28 @@ which allows us to further shorten the text.
``` ```
<ul> <ul>
<li template="foreach #person in people #i=index">{{i}}. {{person}}<li> <li template="for #person of people #i=index">{{i}}. {{person}}<li>
</ul> </ul>
``` ```
We can also optionally use `var` instead of `#` and add `:` to `foreach` which creates the following recommended We can also optionally use `var` instead of `#` and add `:` to `for` which creates the following recommended
microsyntax for `foreach`. microsyntax for `for`.
``` ```
<ul> <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> </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 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. 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 bind: { // List which properties need to be bound
text: 'tooltip' // - DOM element tooltip property should be text: 'tooltip' // - DOM element tooltip property should be
}, // mapped to the directive text property. }, // 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 mouseover: 'show' // - Invoke the show() method every time
} // the mouseover event is fired. } // the mouseover event is fired.
}) })

View File

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

View File

@ -7,58 +7,62 @@ import {Injectable} from 'angular2/di';
/** /**
* Directives allow you to attach behavior to elements in the DOM. * 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]. * or [Viewport].
* *
* A directive consists of a single directive annotation and a controller class. When the directive's [selector] matches * 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: * elements in the DOM, the following steps occur:
* *
* 1. For each directive, the [ElementInjector] attempts to resolve the directive's constructor arguments. * 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 * ## Understanding How Injection Works
* *
* There are three stages of injection resolution. * There are three stages of injection resolution.
* - *Pre-existing Injectors*: * - *Pre-existing Injectors*:
* - The terminal [Injector] cannot resolve dependencies. It either throws an error or, if the dependency was * - The terminal [Injector] cannot resolve dependencies. It either throws an error or, if the dependency was
* specified as `@Optional`, returns `null`. * specified as `@Optional`, returns `null`.
* - The primordial injector resolves browser singleton resources, such as: cookies, title, location, and others. * - 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. * 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. * 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 * When a template is instantiated, it also must instantiate the corresponding directives in a depth-first order. The
* have it, it delegates to the parent injector. * 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]: * 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 * 1. Dependencies on the current element
* 2. Dependencies on component injectors and their parents until it encounters the root component * 2. Dependencies on element injectors and their parents until it encounters a Shadow DOM boundary
* 3. Dependencies on pre-existing injectors * 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 can delegate to the parent *
* The [ElementInjector] can inject other directives, element-specific special objects, or it can delegate to the parent
* injector. * injector.
* *
* To inject other directives, declare the constructor parameter as: * To inject other directives, declare the constructor parameter as:
* - `directive:DirectiveType`: a directive on the current element only * - `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?] * - `@Ancestor() directive:DirectiveType`: any directive that matches the type between the current element and the
* - `@Parent() d:Type`: any directive that matches the type on a direct parent element only * Shadow DOM root. Current Element is not included in the resolution, therefor even if it could resolve it, it will
* - `@Children query:Query<Type>`: A live collection of direct child directives * be ignored.
* - `@Descendants query:Query<Type>`: A live collection of any child directives * - `@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: * To inject element-specific special objects, declare the constructor parameter as:
* - `element: NgElement` to obtain a DOM element (DEPRECATED: replacment coming) * - `element: NgElement` to obtain a DOM element (DEPRECATED: replacment coming)
* - `viewContainer: ViewContainer` to control child template instantiation, for [Viewport] directives only * - `viewContainer: ViewContainer` to control child template instantiation, for [Viewport] directives only
* - `bindingPropagation: BindingPropagation` to control change detection in a more granular way * - `bindingPropagation: BindingPropagation` to control change detection in a more granular way.
* *
* ## Example * ## Example
* *
* The following example demonstrates how dependency injection resolves constructor arguments in practice. * The following example demonstrates how dependency injection resolves constructor arguments in practice.
* *
* *
* Assume this HTML structure: * Assume this HTML template:
* *
* ``` * ```
* <div dependency="1"> * <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... * Let's step through the different ways in which `MyDirective` could be declared...
* *
*
* ### No injection * ### 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]' }) * @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 * This directive would be instantiated with no dependencies.
* 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?]
* *
* ### Component-level injection * ### 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 * ### Injecting a directive from the current element
* *
* Directives can inject other directives declared on 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]' }) * @Decorator({ selector: '[my-directive]' })
* class MyDirective { * class MyDirective {
* constructor(dependency: Dependency) { * 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 * ### 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 * `@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]' }) * @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 * ### 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. * Directives can inject other directives declared on any ancestor element (in the current Shadow DOM), i.e. on the
* By definition, a directive with an `@Ancestor` annotation does not attempt to resolve dependencies for the current * parent element and its parents. By definition, a directive with an `@Ancestor` annotation does not attempt to
* element, even if this would satisfy the dependency. [TODO: did I get the subject/verb right? ] * 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]' }) * @Decorator({ selector: '[my-directive]' })
* class MyDirective { * 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 * ### Injecting a live collection of direct child directives [PENDING IMPLEMENTATION]
* which can than be filled once the data is needed. *
* 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]' }) * @Decorator({ selector: '[my-directive]' })
* class MyDirective { * class MyDirective {
* constructor(@Children() dependencys:Query<Maker>) { * constructor(@Children() dependencies:Query<Maker>) {
* // dependencys will eventuall contain: [4, 6]
* // this will upbate if children are added/removed/moved,
* // for example by having for or if.
* } * }
* } * }
* ``` * ```
* *
* 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]' }) * @Decorator({ selector: '[my-directive]' })
* class MyDirective { * class MyDirective {
* constructor(@Children() dependencys:Query<Maker>) { * constructor(@Children() dependencies: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.
* } * }
* } * }
* ``` * ```
* *
* This directive would be instantiated with a Query which would contain `Dependency` 4, 5 and 6.
* *
* ### Optional injection * ### Optional injection
* *
* Finally there may be times when we would like to inject a component which may or may not be there. For this * The normal behavior of directives is to return an error when a specified dependency cannot be resolved. If you
* use case angular supports `@Optional` injection. * 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]' }) * @Decorator({ selector: '[my-directive]' })
* class MyDirective { * class MyDirective {
* constructor(@Optional() @Ancestor() form:Form) { * constructor(@Optional() dependency:Dependency) {
* // this will search for a Form directive above itself,
* // and inject null if not found
* } * }
* } * }
* ``` * ```
* *
* 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 * @publicModule angular2/annotations
*/ */
@ABSTRACT() @ABSTRACT()
@ -235,13 +240,16 @@ export class Directive extends Injectable {
* The CSS selector that triggers the instantiation of a directive. * 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. * 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. * `selector` may be declared as one of the following:
* - `.class` select by class name. *
* - `[attribute]` select by attribute name. * - `element-name`: select by element name.
* - `[attribute=value]` select by attribute name and value. * - `.class`: select by class name.
* - `:not(sub_selector)` select only if the element does not match the `sub_selector`. * - `[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 * ## Example
* *
@ -270,7 +278,8 @@ export class Directive extends Injectable {
* - `bindingProperty` specifies the DOM property where the value is read from. * - `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 * 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 * ## 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({ * @Decorator({
* selector: '[tooltip]', * selector: '[tooltip]',
* bind: { * bind: {
* 'tooltipText': 'tooltip' * 'text': 'tooltip'
* } * }
* }) * })
* class Tooltip { * class Tooltip {
* set tooltipText(text) { * set text(text) {
* // This will get called every time the 'tooltip' binding changes with the new value. * // 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 * ```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 * Whenever the `someExpression` expression changes, the `bind` declaration instructs Angular to update the
* `Tooltip`'s `tooltipText` property. * `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: * ## 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({ * @Decorator({
* selector: '[class-set]', * 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 * ```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 * In this case, the two pipes compose as if they were inlined: `someExpression | somePipe | keyValDiff`.
* 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.
* *
*/ */
bind:any; // StringMap 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: * The `events` property defines a set of `event` to `method` key-value pairs:
* *
* - `event1` specifies the DOM event that the directive listens to. * - `event1`: the DOM event that the directive listens to.
* - `onMethod1` specifies the method to execute when the event occurs. * - `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 * ## Syntax
@ -366,7 +378,7 @@ export class Directive extends Injectable {
* ``` * ```
* @Directive({ * @Directive({
* events: { * events: {
* 'event1': 'onMethod1', * 'event1': 'onMethod1(arguments)',
* ... * ...
* } * }
* } * }
@ -374,20 +386,24 @@ export class Directive extends Injectable {
* *
* ## Basic Event Binding: * ## 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({ * @Decorator({
* selector: 'input', * selector: 'input',
* events: { * events: {
* 'change': 'onChange' * 'change': 'onChange($event)'
* } * }
* }) * })
* class InputDecorator { * class InputDecorator {
* onChange(event:Event) { * 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 events:any; // StringMap
@ -420,6 +436,8 @@ export class Directive extends Injectable {
/** /**
* Returns true if a directive participates in a given [LifecycleEvent]. * Returns true if a directive participates in a given [LifecycleEvent].
*
* See: [onChange], [onDestroy] for details.
*/ */
hasLifecycleHook(hook:string):boolean { hasLifecycleHook(hook:string):boolean {
return isPresent(this.lifecycle) ? ListWrapper.contains(this.lifecycle, hook) : false; 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 * Each angular component requires a single `@Component` and at least one `@Template` annotation. This allows Angular to
* an `@Component` and at least one `@Template` annotation (see [Template] for more datails.) Components instances are * encapsulate state information and templates. These form the fundamental reusable building blocks for developing an
* used as the context for evaluation of the Shadow DOM view. * application. There can only be one component per DOM element.
* *
* Restrictions: * When a component is instantiated, Angular
* - Thre can anly be one component per DOM element. * - 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 * ## 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 * @publicModule angular2/annotations
*/ */
export class Component extends Directive { 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 * The [services] defined in the Component annotation allow you to configure a set of bindings for the component's
* and its children. Injectables are defined as a list of [Binding]s, (or as [Type]s as short hand). These bindings * injector.
* 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).
* *
* ## Example * When a component is instantiated, Angular creates a new child Injector, which is configured with the bindings in
* // Example of a class which we would like to inject. * the Component [services] annotation. The injectable objects then become available for injection to the component
* class Greeter { * itself and any of the directives in the component's template, i.e. they are not available to the directives which
* salutation:string; * are children in the component's light DOM.
*
* 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;
* }
* }
* *
* *
* 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; services:List;
@ -549,27 +556,34 @@ export class Component extends Directive {
} }
/** /**
* DynamicComponents allow loading child components impretivly. * Directive used for dynamically loading components.
* *
* A Component can be made of other compontents. This recursive nature must be resolved synchronously during the * Regular angular components are statically resolved. DynamicComponent allows to you resolve a component at runtime
* component template processing. This means that all templates are resolved synchronously. This prevents lazy loading * instead by providing a placeholder into which a regular angular component can be dynamically loaded. Once loaded,
* of code or delayed binding of views to the components. * the dynamically-loaded component becomes permanent and cannot be changed.
* *
* 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.
*
*
* ## Example * ## 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({ * @DynamicComponent({
* selector: 'dynamic-comp' * selector: 'dynamic-comp'
* }) * })
* class DynamicComp { * class DynamicComp {
* done; * helloCmp:HelloCmp;
* constructor(loader:PrivateComponentLoader, location:PrivateComponentLocation) { * constructor(loader:PrivateComponentLoader, location:PrivateComponentLocation) {
* this.done = loader.load(HelloCmp, location); * loader.load(HelloCmp, location).then((helloCmp) => {
* this.helloCmp = helloCmp;
* });
* } * }
* } * }
* *
* @Component({ * @Component({
* selector: 'hello-cmp' * selector: 'hello-cmp'
* }) * })
@ -582,11 +596,17 @@ export class Component extends Directive {
* this.greeting = "hello"; * this.greeting = "hello";
* } * }
* } * }
* * ```
* *
*
*
* @publicModule angular2/annotations * @publicModule angular2/annotations
*/ */
export class DynamicComponent extends Directive { export class DynamicComponent extends Directive {
/**
* Same as [Component.services].
*/
// TODO(vsankin): Please extract into AbstractComponent
services:any; //List; services:any; //List;
@CONST() @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: * Decorators:
* - are simplest form of [Directive]s. * - 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: * Decorators differ from [Component]s in that they:
* - can have any number of decorators per element * - can have multiple decorators per element
* - do not create their own evaluation context * - do not create their own evaluation context
* - do not have template (and therefor do not create Shadow DOM) * - do not have template (and therefor do not create Shadow DOM)
* *
*
* ## Example * ## Example
* *
* Let's say we would like to add tool-tip behavior to any alement. * Here we use a decorator directive to simply define basic tool-tip behavior.
*
* ```
* <div tooltip="some text here"></div>
* ```
*
* We could have a decorator directive like so:
* *
* ``` * ```
* @Decorator({ * @Decorator({
@ -643,8 +661,8 @@ export class DynamicComponent extends Directive {
* 'text': 'tooltip' * 'text': 'tooltip'
* }, * },
* event: { * event: {
* 'onmouseenter': 'onMouseEnter', * 'onmouseenter': 'onMouseEnter()',
* 'onmouseleave': 'onMouseLeave' * 'onmouseleave': 'onMouseLeave()'
* } * }
* }) * })
* class Tooltip{ * 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 * @publicModule angular2/annotations
*/ */
export class Decorator extends Directive { 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; compileChildren: boolean;
@CONST() @CONST()
constructor({ constructor({
selector, 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 * A viewport directive uses a [ViewContainer] to instantiate, insert, move, and destroy views at runtime.
* current view where child views can be inserted. * 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> * <ul>
* <li *unless="expr"></li> * <li *foo="bar" title="text"></li>
* </ul> * </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({ * @Viewport({
@ -721,7 +775,7 @@ export class Decorator extends Directive {
* 'condition': 'unless' * 'condition': 'unless'
* } * }
* }) * })
* export class If { * export class Unless {
* viewContainer: ViewContainer; * viewContainer: ViewContainer;
* prevCondition: boolean; * 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 * @publicModule angular2/annotations
*/ */
@ -770,7 +845,7 @@ export class Viewport extends Directive {
//TODO(misko): turn into LifecycleEvent class once we switch to TypeScript; //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 * ## 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: * ## Example:
* *

View File

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

View File

@ -2,8 +2,40 @@ import {CONST} from 'angular2/src/facade/lang';
import {DependencyAnnotation} from 'angular2/di'; import {DependencyAnnotation} from 'angular2/di';
/** /**
* The directive can only be injected from the current element * The directive can only be injected from the parent element.
* or from its parent. *
* ## 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 * @publicModule angular2/annotations
*/ */
@ -15,8 +47,48 @@ export class Parent extends DependencyAnnotation {
} }
/** /**
* The directive can only be injected from the current element * The directive can only be injected from the ancestor (any element between parent element and shadow root).
* or from its ancestor. *
*
* ## 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 * @publicModule angular2/annotations
*/ */

View File

@ -1,5 +1,5 @@
import {isPresent, isBlank, BaseException, assertionsEnabled, RegExpWrapper} from 'angular2/src/facade/lang'; 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 {DOM} from 'angular2/src/dom/dom_adapter';
import {SelectorMatcher} from '../selector'; import {SelectorMatcher} from '../selector';
import {CssSelector} from '../selector'; import {CssSelector} from '../selector';
@ -10,9 +10,6 @@ import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control'; import {CompileControl} from './compile_control';
import {isSpecialProperty} from './element_binder_builder';
import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
var PROPERTY_BINDING_REGEXP = RegExpWrapper.create('^ *([^\\s\\|]+)'); var PROPERTY_BINDING_REGEXP = RegExpWrapper.create('^ *([^\\s\\|]+)');
/** /**
@ -39,8 +36,8 @@ export class DirectiveParser extends CompileStep {
this._selectorMatcher = new SelectorMatcher(); this._selectorMatcher = new SelectorMatcher();
for (var i=0; i<directives.length; i++) { for (var i=0; i<directives.length; i++) {
var directiveMetadata = directives[i]; var directiveMetadata = directives[i];
selector=CssSelector.parse(directiveMetadata.annotation.selector); selector = CssSelector.parse(directiveMetadata.annotation.selector);
this._selectorMatcher.addSelectable(selector, directiveMetadata); 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 // 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 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) => { this._selectorMatcher.match(cssSelector, (selector, directive) => {
matchedProperties = updateMatchedProperties(matchedProperties, selector, directive); current.addDirective(checkDirectiveValidity(directive, current, isTemplateElement));
checkDirectiveValidity(directive, current, isTemplateElement);
current.addDirective(directive);
}); });
// 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 // check if the directive is compatible with the current element
function checkDirectiveValidity(directive, current, isTemplateElement) { function checkDirectiveValidity(directive, current, isTemplateElement) {
var isComponent = directive.annotation instanceof Component || directive.annotation instanceof DynamicComponent; var isComponent = directive.annotation instanceof Component || directive.annotation instanceof DynamicComponent;
@ -125,26 +84,6 @@ function checkDirectiveValidity(directive, current, isTemplateElement) {
} else if (isComponent && alreadyHasComponent) { } else if (isComponent && alreadyHasComponent) {
throw new BaseException(`Multiple component directives not allowed on the same element - check ${current.elementDescription}`); 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 return directive;
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}`);
}
}
} }

View File

@ -15,23 +15,34 @@ import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
var DOT_REGEXP = RegExpWrapper.create('\\.'); var DOT_REGEXP = RegExpWrapper.create('\\.');
const ARIA_PREFIX = 'aria'; const ATTRIBUTE_PREFIX = 'attr.';
var ariaSettersCache = StringMapWrapper.create(); var attributeSettersCache = StringMapWrapper.create();
function ariaSetterFactory(attrName:string) { function _isValidAttributeValue(attrName:string, value: any) {
var setterFn = StringMapWrapper.get(ariaSettersCache, attrName); if (attrName == "role") {
var ariaAttrName; return isString(value);
} else {
return isPresent(value);
}
}
function attributeSetterFactory(attrName:string) {
var setterFn = StringMapWrapper.get(attributeSettersCache, attrName);
var dashCasedAttributeName;
if (isBlank(setterFn)) { if (isBlank(setterFn)) {
ariaAttrName = camelCaseToDashCase(attrName); dashCasedAttributeName = camelCaseToDashCase(attrName);
setterFn = function(element, value) { setterFn = function(element, value) {
if (isPresent(value)) { if (_isValidAttributeValue(dashCasedAttributeName, value)) {
DOM.setAttribute(element, ariaAttrName, stringify(value)); DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
} else { } 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; return setterFn;
@ -82,25 +93,6 @@ function styleSetterFactory(styleName:string, stylesuffix:string) {
return setterFn; 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 * Creates the ElementBinders and adds watches to the
@ -188,29 +180,27 @@ export class ElementBinderBuilder extends CompileStep {
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => { MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
var setterFn, styleParts, styleSuffix; var setterFn, styleParts, styleSuffix;
if (StringWrapper.startsWith(property, ARIA_PREFIX)) { if (StringWrapper.startsWith(property, ATTRIBUTE_PREFIX)) {
setterFn = ariaSetterFactory(property); setterFn = attributeSetterFactory(StringWrapper.substring(property, ATTRIBUTE_PREFIX.length));
} else if (StringWrapper.equals(property, ROLE_ATTR)) {
setterFn = roleSetter;
} else if (StringWrapper.startsWith(property, CLASS_PREFIX)) { } else if (StringWrapper.startsWith(property, CLASS_PREFIX)) {
setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length)); setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length));
} else if (StringWrapper.startsWith(property, STYLE_PREFIX)) { } else if (StringWrapper.startsWith(property, STYLE_PREFIX)) {
styleParts = StringWrapper.split(property, DOT_REGEXP); styleParts = StringWrapper.split(property, DOT_REGEXP);
styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : ''; styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : '';
setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix); setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix);
} else if (StringWrapper.equals(property, 'innerHtml')) {
setterFn = (element, value) => DOM.setInnerHTML(element, value);
} else { } else {
property = this._resolvePropertyName(property); property = this._resolvePropertyName(property);
//TODO(pk): special casing innerHtml, see: https://github.com/angular/angular/issues/789 var propertySetterFn = reflector.setter(property);
if (StringWrapper.equals(property, 'innerHTML')) { setterFn = function(receiver, value) {
setterFn = (element, value) => DOM.setInnerHTML(element, value); if (DOM.hasProperty(receiver, property)) {
} else if (DOM.hasProperty(compileElement.element, property) || StringWrapper.equals(property, 'innerHtml')) { return propertySetterFn(receiver, value);
setterFn = reflector.setter(property); }
} }
} }
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(" RegExpWrapper.create('(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag" '([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class" '(?:\\.([-\\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, * A css selector contains an element name,
@ -21,7 +23,15 @@ export class CssSelector {
classNames:List; classNames:List;
attrs:List; attrs:List;
notSelector: CssSelector; 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 cssSelector = new CssSelector();
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector); var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
var match; var match;
@ -43,13 +53,13 @@ export class CssSelector {
if (isPresent(match[4])) { if (isPresent(match[4])) {
current.addAttribute(match[4], match[5]); current.addAttribute(match[4], match[5]);
} }
if (isPresent(match[6])) {
_addResult(results, cssSelector);
cssSelector = current = new CssSelector();
}
} }
if (isPresent(cssSelector.notSelector) && isBlank(cssSelector.element) _addResult(results, cssSelector);
&& ListWrapper.isEmpty(cssSelector.classNames) && ListWrapper.isEmpty(cssSelector.attrs)) { return results;
cssSelector.element = "*";
}
return cssSelector;
} }
constructor() { constructor() {
@ -119,6 +129,7 @@ export class SelectorMatcher {
_classPartialMap:Map; _classPartialMap:Map;
_attrValueMap:Map; _attrValueMap:Map;
_attrValuePartialMap:Map; _attrValuePartialMap:Map;
_listContexts:List;
constructor() { constructor() {
this._elementMap = MapWrapper.create(); this._elementMap = MapWrapper.create();
this._elementPartialMap = MapWrapper.create(); this._elementPartialMap = MapWrapper.create();
@ -128,6 +139,19 @@ export class SelectorMatcher {
this._attrValueMap = MapWrapper.create(); this._attrValueMap = MapWrapper.create();
this._attrValuePartialMap = 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 cssSelector A css selector
* @param callbackCtxt An opaque object that will be given to the callback of the `match` function * @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 matcher = this;
var element = cssSelector.element; var element = cssSelector.element;
var classNames = cssSelector.classNames; var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs; var attrs = cssSelector.attrs;
var selectable = new SelectorContext(cssSelector, callbackCtxt); var selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
if (isPresent(element)) { if (isPresent(element)) {
@ -215,6 +239,10 @@ export class SelectorMatcher {
var classNames = cssSelector.classNames; var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs; 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._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
result = this._matchPartial(this._elementPartialMap, 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 // Store context to pass back selector and context when a selector is matched
class SelectorContext { class SelectorContext {
selector:CssSelector; selector:CssSelector;
notSelector:CssSelector; notSelector:CssSelector;
cbContext; // callback context cbContext; // callback context
listContext: SelectorListContext;
constructor(selector:CssSelector, cbContext) { constructor(selector:CssSelector, cbContext, listContext: SelectorListContext) {
this.selector = selector; this.selector = selector;
this.notSelector = selector.notSelector; this.notSelector = selector.notSelector;
this.cbContext = cbContext; this.cbContext = cbContext;
this.listContext = listContext;
} }
finalize(cssSelector: CssSelector, callback) { finalize(cssSelector: CssSelector, callback) {
var result = true; var result = true;
if (isPresent(this.notSelector)) { if (isPresent(this.notSelector) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
var notMatcher = new SelectorMatcher(); var notMatcher = new SelectorMatcher();
notMatcher.addSelectable(this.notSelector, null); notMatcher.addSelectable(this.notSelector, null, null);
result = !notMatcher.match(cssSelector, 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); callback(this.selector, this.cbContext);
} }
return result; return result;

View File

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

View File

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

View File

@ -130,7 +130,7 @@ export class ViewContainer {
var detachedView = this.get(atIndex); var detachedView = this.get(atIndex);
ListWrapper.removeAt(this._views, atIndex); ListWrapper.removeAt(this._views, atIndex);
if (isBlank(this._lightDom)) { if (isBlank(this._lightDom)) {
ViewContainer.removeViewNodesFromParent(this.templateElement.parentNode, detachedView); ViewContainer.removeViewNodes(detachedView);
} else { } else {
this._lightDom.redistribute(); this._lightDom.redistribute();
} }
@ -173,8 +173,11 @@ export class ViewContainer {
} }
} }
static removeViewNodesFromParent(parent, view) { static removeViewNodes(view) {
for (var i = view.nodes.length - 1; i >= 0; --i) { 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]); 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'; import {ListWrapper} from 'angular2/src/facade/collection';
@Viewport({ @Viewport({
selector: '[foreach][in]', selector: '[for][of]',
bind: { bind: {
'iterableChanges': 'in | iterableDiff' 'iterableChanges': 'of | iterableDiff'
} }
}) })
export class Foreach { export class For {
viewContainer: ViewContainer; viewContainer: ViewContainer;
constructor(viewContainer:ViewContainer) { constructor(viewContainer:ViewContainer) {
super(); super();
@ -34,13 +34,13 @@ export class Foreach {
(movedRecord) => ListWrapper.push(recordViewTuples, new RecordViewTuple(movedRecord, null)) (movedRecord) => ListWrapper.push(recordViewTuples, new RecordViewTuple(movedRecord, null))
); );
var insertTuples = Foreach.bulkRemove(recordViewTuples, this.viewContainer); var insertTuples = For.bulkRemove(recordViewTuples, this.viewContainer);
changes.forEachAddedItem( changes.forEachAddedItem(
(addedRecord) => ListWrapper.push(insertTuples, new RecordViewTuple(addedRecord, null)) (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++) { for (var i = 0; i < insertTuples.length; i++) {
this.perViewChange(insertTuples[i].view, insertTuples[i].record); this.perViewChange(insertTuples[i].view, insertTuples[i].record);

View File

@ -52,7 +52,7 @@ export class Parse5DomAdapter extends DomAdapter {
} }
}; };
var matcher = new SelectorMatcher(); var matcher = new SelectorMatcher();
matcher.addSelectable(CssSelector.parse(selector)); matcher.addSelectables(CssSelector.parse(selector));
_recursive(res, el, selector, matcher); _recursive(res, el, selector, matcher);
return res; return res;
} }
@ -64,7 +64,7 @@ export class Parse5DomAdapter extends DomAdapter {
var result = false; var result = false;
if (matcher == null) { if (matcher == null) {
matcher = new SelectorMatcher(); matcher = new SelectorMatcher();
matcher.addSelectable(CssSelector.parse(selector)); matcher.addSelectables(CssSelector.parse(selector));
} }
var cssSelector = new CssSelector(); 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 {Optional} from 'angular2/di';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {isBlank, isPresent, isString, CONST} from 'angular2/src/facade/lang'; import {isBlank, isPresent, isString, CONST} from 'angular2/src/facade/lang';
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {ControlGroup, Control} from './model'; import {ControlGroup, Control} from './model';
import * as validators from './validators'; import {Validators} from './validators';
@CONST() //export interface ControlValueAccessor {
export class ControlValueAccessor { // writeValue(value):void{}
readValue(el){} // set onChange(fn){}
writeValue(el, value):void {} //}
}
@CONST() @Decorator({
class DefaultControlValueAccessor extends ControlValueAccessor { selector: '[control]',
constructor() { events: {
'change' : 'onChange($event.target.value)',
'input' : 'onChange($event.target.value)'
}
})
export class DefaultValueAccessor {
_setValueProperty:Function;
onChange:Function;
constructor(@PropertySetter('value') setValueProperty:Function) {
super(); super();
this._setValueProperty = setValueProperty;
this.onChange = (_) => {};
} }
readValue(el) { writeValue(value) {
return DOM.getValue(el); this._setValueProperty(value);
}
writeValue(el, value):void {
DOM.setValue(el,value);
} }
} }
@CONST() @Decorator({
class CheckboxControlValueAccessor extends ControlValueAccessor { selector: 'input[type=checkbox]', //should be input[type=checkbox][control]
constructor() { // 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(); super();
this._setCheckedProperty = setCheckedProperty;
this.onChange = (_) => {};
cd.valueAccessor = this; //ControlDirective should inject CheckboxControlDirective
} }
readValue(el):boolean { writeValue(value) {
return DOM.getChecked(el); this._setCheckedProperty(value);
}
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");
} }
} }
@ -60,26 +59,22 @@ function controlValueAccessorFor(controlType:string):ControlValueAccessor {
lifecycle: [onChange], lifecycle: [onChange],
selector: '[control]', selector: '[control]',
bind: { bind: {
'controlName' : 'control', 'controlName' : 'control'
'type' : 'type'
} }
}) })
export class ControlDirective { export class ControlDirective {
_groupDirective:ControlGroupDirective; _groupDirective:ControlGroupDirective;
_el:NgElement;
controlName:string; controlName:string;
type:string; valueAccessor:any; //ControlValueAccessor
valueAccessor:ControlValueAccessor;
validator:Function; validator:Function;
constructor(@Ancestor() groupDirective:ControlGroupDirective, el:NgElement) { constructor(@Ancestor() groupDirective:ControlGroupDirective, valueAccessor:DefaultValueAccessor) {
this._groupDirective = groupDirective; this._groupDirective = groupDirective;
this._el = el;
this.controlName = null; this.controlName = null;
this.type = null; this.valueAccessor = valueAccessor;
this.validator = validators.nullValidator; this.validator = Validators.nullValidator;
} }
// TODO: vsavkin this should be moved into the constructor once static bindings // TODO: vsavkin this should be moved into the constructor once static bindings
@ -92,22 +87,18 @@ export class ControlDirective {
this._groupDirective.addDirective(this); this._groupDirective.addDirective(this);
var c = this._control(); var c = this._control();
c.validator = validators.compose([c.validator, this.validator]); c.validator = Validators.compose([c.validator, this.validator]);
if (isBlank(this.valueAccessor)) {
this.valueAccessor = controlValueAccessorFor(this.type);
}
this._updateDomValue(); this._updateDomValue();
DOM.on(this._el.domElement, "change", (_) => this._updateControlValue()); this._setUpUpdateControlValue();
} }
_updateDomValue() { _updateDomValue() {
this.valueAccessor.writeValue(this._el.domElement, this._control().value); this.valueAccessor.writeValue(this._control().value);
} }
_updateControlValue() { _setUpUpdateControlValue() {
this._control().updateValue(this.valueAccessor.readValue(this._el.domElement)); this.valueAccessor.onChange = (newValue) => this._control().updateValue(newValue);
} }
_control() { _control() {
@ -165,5 +156,5 @@ export class ControlGroupDirective {
} }
export var FormDirectives = [ export var FormDirectives = [
ControlGroupDirective, ControlDirective ControlGroupDirective, ControlDirective, CheckboxControlValueAccessor, DefaultValueAccessor
]; ];

View File

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

View File

@ -1,13 +1,12 @@
import {Decorator} from 'angular2/angular2'; import {Decorator} from 'angular2/angular2';
import {ControlDirective} from 'angular2/forms'; import {ControlDirective, Validators} from 'angular2/forms';
import * as validators from 'angular2/forms';
@Decorator({ @Decorator({
selector: '[required]' selector: '[required]'
}) })
export class RequiredValidatorDirective { export class RequiredValidatorDirective {
constructor(c:ControlDirective) { 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'; import * as modelModule from './model';
export function required(c:modelModule.Control) { export class Validators {
return isBlank(c.value) || c.value == "" ? {"required" : true} : null; static required(c:modelModule.Control) {
} return isBlank(c.value) || c.value == "" ? {"required": true} : null;
}
export function nullValidator(c:modelModule.Control) { static nullValidator(c:modelModule.Control) {
return null; return null;
} }
export function compose(validators:List<Function>):Function { static compose(validators:List<Function>):Function {
return function(c:modelModule.Control) { return function (c:modelModule.Control) {
var res = ListWrapper.reduce(validators, (res, validator) => { var res = ListWrapper.reduce(validators, (res, validator) => {
var errors = validator(c); var errors = validator(c);
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res; 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; 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'; 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 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart'; 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:analyzer/analyzer.dart';
import 'package:angular2/src/transform/common/logging.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:async';
import 'dart:convert'; 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'; 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:barback/barback.dart';
import 'package:code_transformers/messages/build_logger.dart'; import 'package:code_transformers/messages/build_logger.dart';
@ -10,6 +10,11 @@ void init(Transform t) {
_logger = new BuildLogger(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. /// The logger the transformer should use for messaging.
BuildLogger get logger { BuildLogger get logger {
if (_logger == null) { 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 SETUP_METHOD_NAME = 'setupReflection';
const REFLECTOR_VAR_NAME = 'reflector'; 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 ENTRY_POINT_PARAM = 'entry_point';
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_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'; 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:analyzer/analyzer.dart';
import 'package:angular2/src/transform/common/names.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/analyzer.dart';
import 'package:analyzer/src/generated/java_core.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 '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/logging.dart';
import 'package:angular2/src/transform/common/names.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:barback/barback.dart';
import 'package:code_transformers/assets.dart'; import 'package:code_transformers/assets.dart';
import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
Future<String> linkNgDeps(Transform transform, String code, String path) async { Future<String> linkNgDeps(AssetReader reader, AssetId entryPoint) async {
var commentIdx = code.lastIndexOf('//'); var parser = new Parser(reader);
if (commentIdx < 0) return code; 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 = var allDeps = ngDeps.imports.toList()..addAll(ngDeps.exports);
new StringBuffer(code.substring(0, ngData.importOffset)); var depList = await _processNgImports(
StringBuffer declarationBuf = new StringBuffer( reader, entryPoint, allDeps.map((node) => node.uri.stringValue));
code.substring(ngData.importOffset, ngData.registerOffset));
String tail = code.substring(ngData.registerOffset, commentIdx);
var ngDeps = await _processNgImports(transform, ngData.imports); if (depList.isEmpty) return ngDeps.code;
for (var i = 0; i < ngDeps.length; ++i) { var importBuf = new StringBuffer();
importBuf.write('import \'${ngDeps[i]}\' as i${i};'); 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});'); 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) => String _toDepsUri(String importUri) =>
@ -40,17 +50,16 @@ bool _isNotDartImport(String importUri) {
} }
Future<List<String>> _processNgImports( 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>[]; var retVal = <String>[];
return Future return Future
.wait(imports.where(_isNotDartImport).map(_toDepsUri).map((ngDepsUri) { .wait(imports.where(_isNotDartImport).map(_toDepsUri).map((ngDepsUri) {
var importAsset = uriToAssetId( var importAsset =
transform.primaryInput.id, ngDepsUri, logger, null /* span */); uriToAssetId(entryPoint, ngDepsUri, logger, null /* span */);
return transform.hasInput(importAsset).then((hasInput) { if (importAsset == entryPoint) return nullFuture;
if (hasInput) { return reader.hasInput(importAsset).then((hasInput) {
retVal.add(ngDepsUri); if (hasInput) retVal.add(ngDepsUri);
}
}); });
})).then((_) => retVal); })).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 '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/formatter.dart';
import 'package:angular2/src/transform/common/logging.dart' as log; import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart'; import 'package:angular2/src/transform/common/names.dart';
@ -27,12 +28,11 @@ class DirectiveLinker extends Transformer {
log.init(transform); log.init(transform);
try { try {
var assetCode = await transform.primaryInput.readAsString(); var assetId = transform.primaryInput.id;
var assetPath = transform.primaryInput.id.path; var transformedCode =
var transformedCode = await linkNgDeps(transform, assetCode, assetPath); await linkNgDeps(new AssetReader.fromTransform(transform), assetId);
var formattedCode = formatter.format(transformedCode, uri: assetPath); var formattedCode = formatter.format(transformedCode, uri: assetId.path);
transform.addOutput( transform.addOutput(new Asset.fromString(assetId, formattedCode));
new Asset.fromString(transform.primaryInput.id, formattedCode));
} catch (ex, stackTrace) { } catch (ex, stackTrace) {
log.logger.error('Linking ng directives failed.\n' log.logger.error('Linking ng directives failed.\n'
'Exception: $ex\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/analyzer.dart';
import 'package:analyzer/src/generated/java_core.dart'; import 'package:analyzer/src/generated/java_core.dart';
import 'package:angular2/src/transform/common/logging.dart'; import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.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:angular2/src/transform/common/visitor_mixin.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
@ -42,7 +41,6 @@ class CreateNgDepsVisitor extends Object
final FactoryTransformVisitor _factoryVisitor; final FactoryTransformVisitor _factoryVisitor;
final ParameterTransformVisitor _paramsVisitor; final ParameterTransformVisitor _paramsVisitor;
final AnnotationsTransformVisitor _metaVisitor; final AnnotationsTransformVisitor _metaVisitor;
final NgData _ngData = new NgData();
/// The path to the file which we are parsing. /// The path to the file which we are parsing.
final String importPath; final String importPath;
@ -73,19 +71,16 @@ class CreateNgDepsVisitor extends Object
_writeImport(); _writeImport();
wroteImport = true; wroteImport = true;
} }
_ngData.imports.add(node.uri.stringValue);
return node.accept(_copyVisitor); return node.accept(_copyVisitor);
} }
@override @override
Object visitExportDirective(ExportDirective node) { Object visitExportDirective(ExportDirective node) {
_ngData.imports.add(node.uri.stringValue);
return node.accept(_copyVisitor); return node.accept(_copyVisitor);
} }
void _openFunctionWrapper() { void _openFunctionWrapper() {
// TODO(kegluneq): Use a [PrintWriter] with a length getter. // TODO(kegluneq): Use a [PrintWriter] with a length getter.
_ngData.importOffset = writer.toString().length;
writer.print('bool _visited = false;' writer.print('bool _visited = false;'
'void ${SETUP_METHOD_NAME}(${REFLECTOR_VAR_NAME}) {' 'void ${SETUP_METHOD_NAME}(${REFLECTOR_VAR_NAME}) {'
'if (_visited) return; _visited = true;'); 'if (_visited) return; _visited = true;');
@ -95,10 +90,7 @@ class CreateNgDepsVisitor extends Object
if (foundNgDirectives) { if (foundNgDirectives) {
writer.print(';'); writer.print(';');
} }
// TODO(kegluneq): Use a [PrintWriter] with a length getter.
_ngData.registerOffset = writer.toString().length;
writer.print('}'); writer.print('}');
writer.print('// ${_ngData.toJson()}');
} }
ConstructorDeclaration _getCtor(ClassDeclaration node) { 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'; 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/analyzer.dart';
import 'package:analyzer/src/generated/java_core.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 'dart:collection' show Queue;
import 'package:analyzer/src/generated/ast.dart'; 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/ast.dart';
import 'package:analyzer/src/generated/element.dart'; import 'package:analyzer/src/generated/element.dart';
@ -55,7 +55,7 @@ abstract class DirectiveRegistry {
const setupReflectionMethodName = 'setupReflection'; const setupReflectionMethodName = 'setupReflection';
const _libraryDeclaration = ''' const _libraryDeclaration = '''
library angular2.src.transform.generated; library angular2.transform.generated;
'''; ''';
const _reflectorImport = ''' 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/ast.dart';
import 'package:analyzer/src/generated/element.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/ast.dart';
import 'package:analyzer/src/generated/element.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:barback/barback.dart';
import 'package:analyzer/src/generated/element.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/ast.dart';
import 'package:analyzer/src/generated/element.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/ast.dart';
import 'package:analyzer/src/generated/element.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:analyzer/src/generated/ast.dart';
import 'package:barback/barback.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'; 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:analyzer/src/generated/ast.dart';
import 'package:angular2/src/transform/common/logging.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 'dart:async';
import 'package:angular2/src/transform/common/logging.dart' as log; 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'; 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/reflection_capabilities.dart';
import 'package:angular2/src/reflection/types.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'; import 'dart:async';

View File

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

View File

@ -1,6 +1,6 @@
import {isBlank} from 'angular2/src/facade/lang'; 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(collection)) collection = [];
if (isBlank(previous)) previous = []; if (isBlank(previous)) previous = [];
if (isBlank(additions)) additions = []; if (isBlank(additions)) additions = [];

View File

@ -2,6 +2,7 @@ import {
AsyncTestCompleter, AsyncTestCompleter,
beforeEach, beforeEach,
ddescribe, ddescribe,
xdescribe,
describe, describe,
el, el,
expect, expect,
@ -116,7 +117,7 @@ export function main() {
})); }));
it('should consume binding to aria-* attributes', inject([AsyncTestCompleter], (async) => { 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) => { compiler.compile(MyComp).then((pv) => {
createView(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) => { it('should consume directive watch expression change.', inject([AsyncTestCompleter], (async) => {
var tpl = var tpl =
'<div>' + '<div>' +
@ -436,30 +451,57 @@ export function main() {
}) })
})); }));
it('should provide binding configuration config to the component', inject([AsyncTestCompleter], (async) => { describe("BindingPropagationConfig", () => {
tplResolver.setTemplate(MyComp, new Template({ it("can be used to disable the change detection of the component's template",
inline: '<push-cmp #cmp></push-cmp>', inject([AsyncTestCompleter], (async) => {
directives: [[[PushBasedComp]]]
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) => { it('should not affect updating properties on the component', inject([AsyncTestCompleter], (async) => {
createView(pv); 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(); var cmp = view.locals.get('cmp');
expect(cmp.numberOfChecks).toEqual(1);
cd.detectChanges(); ctx.ctxProp = "one";
expect(cmp.numberOfChecks).toEqual(1); cd.detectChanges();
expect(cmp.prop).toEqual("one");
cmp.propagate(); ctx.ctxProp = "two";
cd.detectChanges();
expect(cmp.prop).toEqual("two");
cd.detectChanges(); async.done();
expect(cmp.numberOfChecks).toEqual(2); })
async.done(); }));
}) });
}));
it('should create a component that injects a @Parent', inject([AsyncTestCompleter], (async) => { it('should create a component that injects a @Parent', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MyComp, new Template({ 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) { if (assertionsEnabled()) {
tplResolver.setTemplate(MyComp, new Template({inline: inlineTpl}));
PromiseWrapper.then(compiler.compile(MyComp), function expectCompileError(inlineTpl, errMessage, done) {
(value) => { tplResolver.setTemplate(MyComp, new Template({inline: inlineTpl}));
throw new BaseException("Test failure: should not have come here as an exception was expected"); PromiseWrapper.then(compiler.compile(MyComp),
}, (value) => {
(err) => { throw new BaseException("Test failure: should not have come here as an exception was expected");
expect(err.message).toEqual(errMessage); },
done(); (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}}'}) @Template({inline: '{{field}}'})
class PushBasedComp { class PushBasedComp {
numberOfChecks:number; numberOfChecks:number;
bpc:BindingPropagationConfig; bpc:BindingPropagationConfig;
prop;
constructor(bpc:BindingPropagationConfig) { constructor(bpc:BindingPropagationConfig) {
this.numberOfChecks = 0; this.numberOfChecks = 0;

View File

@ -218,7 +218,7 @@ export function main() {
it('should bind to aria-* attributes when exp evaluates to strings', () => { it('should bind to aria-* attributes when exp evaluates to strings', () => {
var propertyBindings = MapWrapper.createFromStringMap({ var propertyBindings = MapWrapper.createFromStringMap({
'aria-label': 'prop1' 'attr.aria-label': 'prop1'
}); });
var pipeline = createPipeline({propertyBindings: propertyBindings}); var pipeline = createPipeline({propertyBindings: propertyBindings});
var results = pipeline.process(el('<div viewroot prop-binding></div>')); 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', () => { it('should bind to aria-* attributes when exp evaluates to booleans', () => {
var propertyBindings = MapWrapper.createFromStringMap({ var propertyBindings = MapWrapper.createFromStringMap({
'aria-busy': 'prop1' 'attr.aria-busy': 'prop1'
}); });
var pipeline = createPipeline({propertyBindings: propertyBindings}); var pipeline = createPipeline({propertyBindings: propertyBindings});
var results = pipeline.process(el('<div viewroot prop-binding></div>')); var results = pipeline.process(el('<div viewroot prop-binding></div>'));
@ -264,7 +264,7 @@ export function main() {
it('should bind to ARIA role attribute', () => { it('should bind to ARIA role attribute', () => {
var propertyBindings = MapWrapper.createFromStringMap({ var propertyBindings = MapWrapper.createFromStringMap({
'role': 'prop1' 'attr.role': 'prop1'
}); });
var pipeline = createPipeline({propertyBindings: propertyBindings}); var pipeline = createPipeline({propertyBindings: propertyBindings});
var results = pipeline.process(el('<div viewroot prop-binding></div>')); 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', () => { it('should throw for a non-string ARIA role', () => {
var propertyBindings = MapWrapper.createFromStringMap({ var propertyBindings = MapWrapper.createFromStringMap({
'role': 'prop1' 'attr.role': 'prop1'
}); });
var pipeline = createPipeline({propertyBindings: propertyBindings}); var pipeline = createPipeline({propertyBindings: propertyBindings});
var results = pipeline.process(el('<div viewroot prop-binding></div>')); 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'"); }).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', () => { it('should bind class with a dot', () => {
var propertyBindings = MapWrapper.createFromStringMap({ var propertyBindings = MapWrapper.createFromStringMap({
'class.bar': 'prop1', 'class.bar': 'prop1',

View File

@ -23,162 +23,181 @@ export function main() {
}); });
it('should select by element name case insensitive', () => { 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(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('SOMETAG'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('SOMETAG')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
}); });
it('should select by class name case insensitive', () => { it('should select by class name case insensitive', () => {
matcher.addSelectable(s1 = CssSelector.parse('.someClass'), 1); matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1);
matcher.addSelectable(s2 = CssSelector.parse('.someClass.class2'), 2); 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(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('.SOMECLASS'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('.SOMECLASS')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
reset(); reset();
expect(matcher.match(CssSelector.parse('.someClass.class2'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('.someClass.class2')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2]); expect(matched).toEqual([s1[0],1,s2[0],2]);
}); });
it('should select by attr name case insensitive independent of the value', () => { it('should select by attr name case insensitive independent of the value', () => {
matcher.addSelectable(s1 = CssSelector.parse('[someAttr]'), 1); matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1);
matcher.addSelectable(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2); 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(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR]'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('[SOMEATTR]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
reset(); reset();
expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
reset(); reset();
expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2]); expect(matched).toEqual([s1[0],1,s2[0],2]);
}); });
it('should select by attr name only once if the value is from the DOM', () => { 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 elementSelector = new CssSelector();
var element = el('<div attr></div>'); var element = el('<div attr></div>');
var empty = DOM.getAttribute(element, 'attr'); var empty = DOM.getAttribute(element, 'attr');
elementSelector.addAttribute('some-decor', empty); elementSelector.addAttribute('some-decor', empty);
matcher.match(elementSelector, selectableCollector); 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', () => { 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(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
}); });
it('should select by element name, class name and attribute name with value', () => { 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(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(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(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(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
}); });
it('should select independent of the order in the css selector', () => { it('should select independent of the order in the css selector', () => {
matcher.addSelectable(s1 = CssSelector.parse('[someAttr].someClass'), 1); matcher.addSelectables(s1 = CssSelector.parse('[someAttr].someClass'), 1);
matcher.addSelectable(s2 = CssSelector.parse('.someClass[someAttr]'), 2); matcher.addSelectables(s2 = CssSelector.parse('.someClass[someAttr]'), 2);
matcher.addSelectable(s3 = CssSelector.parse('.class1.class2'), 3); matcher.addSelectables(s3 = CssSelector.parse('.class1.class2'), 3);
matcher.addSelectable(s4 = CssSelector.parse('.class2.class1'), 4); matcher.addSelectables(s4 = CssSelector.parse('.class2.class1'), 4);
expect(matcher.match(CssSelector.parse('[someAttr].someClass'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('[someAttr].someClass')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2]); expect(matched).toEqual([s1[0],1,s2[0],2]);
reset(); reset();
expect(matcher.match(CssSelector.parse('.someClass[someAttr]'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('.someClass[someAttr]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2]); expect(matched).toEqual([s1[0],1,s2[0],2]);
reset(); reset();
expect(matcher.match(CssSelector.parse('.class1.class2'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('.class1.class2')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s3,3,s4,4]); expect(matched).toEqual([s3[0],3,s4[0],4]);
reset(); reset();
expect(matcher.match(CssSelector.parse('.class2.class1'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('.class2.class1')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s4,4,s3,3]); expect(matched).toEqual([s4[0],4,s3[0],3]);
}); });
it('should not select with a matching :not selector', () => { it('should not select with a matching :not selector', () => {
matcher.addSelectable(CssSelector.parse('p:not(.someClass)'), 1); matcher.addSelectables(CssSelector.parse('p:not(.someClass)'), 1);
matcher.addSelectable(CssSelector.parse('p:not([someAttr])'), 2); matcher.addSelectables(CssSelector.parse('p:not([someAttr])'), 2);
matcher.addSelectable(CssSelector.parse(':not(.someClass)'), 3); matcher.addSelectables(CssSelector.parse(':not(.someClass)'), 3);
matcher.addSelectable(CssSelector.parse(':not(p)'), 4); matcher.addSelectables(CssSelector.parse(':not(p)'), 4);
matcher.addSelectable(CssSelector.parse(':not(p[someAttr])'), 5); 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([]); expect(matched).toEqual([]);
}); });
it('should select with a non matching :not selector', () => { it('should select with a non matching :not selector', () => {
matcher.addSelectable(s1 = CssSelector.parse('p:not(.someClass)'), 1); matcher.addSelectables(s1 = CssSelector.parse('p:not(.someClass)'), 1);
matcher.addSelectable(s2 = CssSelector.parse('p:not(.someOtherClass[someAttr])'), 2); matcher.addSelectables(s2 = CssSelector.parse('p:not(.someOtherClass[someAttr])'), 2);
matcher.addSelectable(s3 = CssSelector.parse(':not(.someClass)'), 3); matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3);
matcher.addSelectable(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4); matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4);
expect(matcher.match(CssSelector.parse('p[someOtherAttr].someOtherClass'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('p[someOtherAttr].someOtherClass')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2,s3,3,s4,4]); 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', () => { describe('CssSelector.parse', () => {
it('should detect element names', () => { it('should detect element names', () => {
var cssSelector = CssSelector.parse('sometag'); var cssSelector = CssSelector.parse('sometag')[0];
expect(cssSelector.element).toEqual('sometag'); expect(cssSelector.element).toEqual('sometag');
expect(cssSelector.toString()).toEqual('sometag'); expect(cssSelector.toString()).toEqual('sometag');
}); });
it('should detect class names', () => { it('should detect class names', () => {
var cssSelector = CssSelector.parse('.someClass'); var cssSelector = CssSelector.parse('.someClass')[0];
expect(cssSelector.classNames).toEqual(['someclass']); expect(cssSelector.classNames).toEqual(['someclass']);
expect(cssSelector.toString()).toEqual('.someclass'); expect(cssSelector.toString()).toEqual('.someclass');
}); });
it('should detect attr names', () => { it('should detect attr names', () => {
var cssSelector = CssSelector.parse('[attrname]'); var cssSelector = CssSelector.parse('[attrname]')[0];
expect(cssSelector.attrs).toEqual(['attrname', '']); expect(cssSelector.attrs).toEqual(['attrname', '']);
expect(cssSelector.toString()).toEqual('[attrname]'); expect(cssSelector.toString()).toEqual('[attrname]');
}); });
it('should detect attr values', () => { 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.attrs).toEqual(['attrname', 'attrvalue']);
expect(cssSelector.toString()).toEqual('[attrname=attrvalue]'); expect(cssSelector.toString()).toEqual('[attrname=attrvalue]');
}); });
it('should detect multiple parts', () => { 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.element).toEqual('sometag');
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']); expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
expect(cssSelector.classNames).toEqual(['someclass']); expect(cssSelector.classNames).toEqual(['someclass']);
@ -187,7 +206,7 @@ export function main() {
}); });
it('should detect :not', () => { 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.element).toEqual('sometag');
expect(cssSelector.attrs.length).toEqual(0); expect(cssSelector.attrs.length).toEqual(0);
expect(cssSelector.classNames.length).toEqual(0); expect(cssSelector.classNames.length).toEqual(0);
@ -201,7 +220,7 @@ export function main() {
}); });
it('should detect :not without truthy', () => { 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("*"); expect(cssSelector.element).toEqual("*");
var notSelector = cssSelector.notSelector; var notSelector = cssSelector.notSelector;
@ -213,8 +232,31 @@ export function main() {
it('should throw when nested :not', () => { it('should throw when nested :not', () => {
expect(() => { 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'); }).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'); var css = s('x /deep/ y {}', 'a');
expect(css).toEqual('x[a] 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 {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'; import {bind} from 'angular2/di';
export function main() { export function main() {
describe('foreach', () => { describe('for', () => {
var view, cd, compiler, component, tplResolver; var view, cd, compiler, component, tplResolver;
beforeEachBindings(() => [ beforeEachBindings(() => [
@ -52,13 +52,13 @@ export function main() {
function compileWithTemplate(html) { function compileWithTemplate(html) {
var template = new Template({ var template = new Template({
inline: html, inline: html,
directives: [Foreach] directives: [For]
}); });
tplResolver.setTemplate(TestComponent, template); tplResolver.setTemplate(TestComponent, template);
return compiler.compile(TestComponent); 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) => { it('should reflect initial elements', inject([AsyncTestCompleter], (async) => {
compileWithTemplate(TEMPLATE).then((pv) => { compileWithTemplate(TEMPLATE).then((pv) => {
@ -124,8 +124,8 @@ export function main() {
}); });
})); }));
it('should iterate over an array of objects', () => { it('should iterate over an array of objects', inject([AsyncTestCompleter], (async) => {
compileWithTemplate('<ul><li template="foreach #item in items">{{item["name"]}};</li></ul>').then((pv) => { compileWithTemplate('<ul><li template="for #item of items">{{item["name"]}};</li></ul>').then((pv) => {
createView(pv); createView(pv);
// INIT // INIT
@ -145,11 +145,12 @@ export function main() {
cd.detectChanges(); cd.detectChanges();
expect(DOM.getText(view.nodes[0])).toEqual('shyam;'); expect(DOM.getText(view.nodes[0])).toEqual('shyam;');
async.done();
}); });
}); }));
it('should gracefully handle nulls', inject([AsyncTestCompleter], (async) => { 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); createView(pv);
cd.detectChanges(); cd.detectChanges();
expect(DOM.getText(view.nodes[0])).toEqual(''); expect(DOM.getText(view.nodes[0])).toEqual('');
@ -199,10 +200,13 @@ export function main() {
it('should repeat over nested arrays', inject([AsyncTestCompleter], (async) => { it('should repeat over nested arrays', inject([AsyncTestCompleter], (async) => {
compileWithTemplate( compileWithTemplate(
'<div><div template="foreach #item in items">' + '<div>'+
'<div template="foreach #subitem in item">' + '<div template="for #item of items">' +
'{{subitem}}-{{item.length}};' + '<div template="for #subitem of item">' +
'</div>|</div></div>' '{{subitem}}-{{item.length}};' +
'</div>|'+
'</div>'+
'</div>'
).then((pv) => { ).then((pv) => {
createView(pv); createView(pv);
component.items = [['a', 'b'], ['c']]; component.items = [['a', 'b'], ['c']];
@ -210,13 +214,38 @@ export function main() {
cd.detectChanges(); cd.detectChanges();
cd.detectChanges(); cd.detectChanges();
expect(DOM.getText(view.nodes[0])).toEqual('a-2;b-2;|c-1;|'); 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(); async.done();
}); });
})); }));
it('should display indices correctly', inject([AsyncTestCompleter], (async) => { it('should display indices correctly', inject([AsyncTestCompleter], (async) => {
var INDEX_TEMPLATE = 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) => { compileWithTemplate(INDEX_TEMPLATE).then((pv) => {
createView(pv); createView(pv);
component.items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 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) => { it('should update several nodes with if', inject([AsyncTestCompleter], (async) => {
var templateString = var templateString =
'<div>' + '<div>' +
@ -195,11 +228,13 @@ export function main() {
@Component({selector: 'test-cmp'}) @Component({selector: 'test-cmp'})
class TestComponent { class TestComponent {
booleanCondition: boolean; booleanCondition: boolean;
nestedBooleanCondition: boolean;
numberCondition: number; numberCondition: number;
stringCondition: string; stringCondition: string;
functionCondition: Function; functionCondition: Function;
constructor() { constructor() {
this.booleanCondition = true; this.booleanCondition = true;
this.nestedBooleanCondition = true;
this.numberCondition = 1; this.numberCondition = 1;
this.stringCondition = "foo"; this.stringCondition = "foo";
this.functionCondition = function(s, n){ 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 {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib';
import {Control, FormBuilder} from 'angular2/forms'; import {Control, FormBuilder, Validators} from 'angular2/forms';
import * as validations from 'angular2/forms';
export function main() { export function main() {
describe("Form Builder", () => { describe("Form Builder", () => {
@ -21,21 +20,21 @@ export function main() {
it("should create controls from an array", () => { it("should create controls from an array", () => {
var g = b.group({ var g = b.group({
"login": ["some value"], "login": ["some value"],
"password": ["some value", validations.required] "password": ["some value", Validators.required]
}); });
expect(g.controls["login"].value).toEqual("some value"); expect(g.controls["login"].value).toEqual("some value");
expect(g.controls["password"].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", () => { it("should use controls", () => {
var g = b.group({ 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"].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", () => { it("should create groups with optional controls", () => {
@ -49,17 +48,17 @@ export function main() {
it("should create groups with a custom validator", () => { it("should create groups with a custom validator", () => {
var g = b.group({ var g = b.group({
"login": "some value" "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", () => { it("should use default validators when no validators are provided", () => {
var g = b.group({ var g = b.group({
"login": "some value" "login": "some value"
}); });
expect(g.controls["login"].validator).toBe(validations.nullValidator); expect(g.controls["login"].validator).toBe(Validators.nullValidator);
expect(g.validator).toBe(validations.controlGroupValidator); 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 {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver'; import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver';
import {CssProcessor} from 'angular2/src/core/compiler/css_processor'; 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 {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock';
import {Injector} from 'angular2/di'; 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, import {ControlGroupDirective, ControlDirective, Control, ControlGroup, OptionalControl,
ControlValueAccessor, RequiredValidatorDirective} from 'angular2/forms'; ControlValueAccessor, RequiredValidatorDirective, CheckboxControlValueAccessor,
DefaultValueAccessor, Validators} from 'angular2/forms';
import * as validators from 'angular2/src/forms/validators';
export function main() { export function main() {
function detectChanges(view) { function detectChanges(view) {
@ -58,35 +59,37 @@ export function main() {
tplResolver.setTemplate(componentType, new Template({ tplResolver.setTemplate(componentType, new Template({
inline: template, inline: template,
directives: [ControlGroupDirective, ControlDirective, WrappedValue, RequiredValidatorDirective] directives: [ControlGroupDirective, ControlDirective, WrappedValue, RequiredValidatorDirective,
CheckboxControlValueAccessor, DefaultValueAccessor]
})); }));
compiler.compile(componentType).then((pv) => { 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); view.hydrate(new Injector([]), null, null, context, null);
detectChanges(view); detectChanges(view);
callback(view); callback(view);
}); });
} }
describe("integration tests", () => { if (DOM.supportsDOMEvents()) {
it("should initialize DOM elements with the given form object", inject([AsyncTestCompleter], (async) => { describe("integration tests", () => {
var ctx = new MyComp(new ControlGroup({ it("should initialize DOM elements with the given form object", inject([AsyncTestCompleter], (async) => {
"login": new Control("loginValue") 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"> <input type="text" control="login">
</div>`; </div>`;
compile(MyComp, t, ctx, (view) => { compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input") var input = queryView(view, "input")
expect(input.value).toEqual("loginValue"); expect(input.value).toEqual("loginValue");
async.done(); async.done();
}); });
})); }));
if (DOM.supportsDOMEvents()) {
it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => { it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({ var form = new ControlGroup({
"login": new Control("oldValue") "login": new Control("oldValue")
@ -107,55 +110,110 @@ export function main() {
async.done(); async.done();
}); });
})); }));
}
it("should update DOM elements when rebinding the control group", inject([AsyncTestCompleter], (async) => { it("should update DOM elements when rebinding the control group", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({ var form = new ControlGroup({
"login": new Control("oldValue") "login": new Control("oldValue")
}); });
var ctx = new MyComp(form); var ctx = new MyComp(form);
var t = `<div [control-group]="form"> var t = `<div [control-group]="form">
<input type="text" control="login"> <input type="text" control="login">
</div>`; </div>`;
compile(MyComp, t, ctx, (view) => { compile(MyComp, t, ctx, (view) => {
ctx.form = new ControlGroup({ ctx.form = new ControlGroup({
"login": new Control("newValue") "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") it("should update DOM element when rebinding the control name", inject([AsyncTestCompleter], (async) => {
expect(input.value).toEqual("newValue"); var ctx = new MyComp(new ControlGroup({
async.done(); "one": new Control("one"),
}); "two": new Control("two")
})); }), "one");
it("should update DOM element when rebinding the control name", inject([AsyncTestCompleter], (async) => { var t = `<div [control-group]="form">
var ctx = new MyComp(new ControlGroup({
"one": new Control("one"),
"two": new Control("two")
}), "one");
var t = `<div [control-group]="form">
<input type="text" [control]="name"> <input type="text" [control]="name">
</div>`; </div>`;
compile(MyComp, t, ctx, (view) => { compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input") var input = queryView(view, "input")
expect(input.value).toEqual("one"); expect(input.value).toEqual("one");
ctx.name = "two"; ctx.name = "two";
detectChanges(view); detectChanges(view);
expect(input.value).toEqual("two"); expect(input.value).toEqual("two");
async.done(); async.done();
}); });
})); }));
if (DOM.supportsDOMEvents()) {
describe("different control types", () => { 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 ctx = new MyComp(new ControlGroup({"checkbox": new Control(true)}));
var t = `<div [control-group]="form"> var t = `<div [control-group]="form">
@ -169,31 +227,12 @@ export function main() {
input.checked = false; input.checked = false;
dispatchEvent(input, "change"); dispatchEvent(input, "change");
expect(ctx.form.value).toEqual({"checkbox" : false}); expect(ctx.form.value).toEqual({"checkbox": false});
async.done(); async.done();
}); });
})); }));
it("should support textarea", inject([AsyncTestCompleter], (async) => { it("should support <select>", 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) => {
var ctx = new MyComp(new ControlGroup({"city": new Control("SF")})); var ctx = new MyComp(new ControlGroup({"city": new Control("SF")}));
var t = `<div [control-group]="form"> var t = `<div [control-group]="form">
@ -212,7 +251,7 @@ export function main() {
select.value = 'NYC'; select.value = 'NYC';
dispatchEvent(select, "change"); dispatchEvent(select, "change");
expect(ctx.form.value).toEqual({"city" : 'NYC'}); expect(ctx.form.value).toEqual({"city": 'NYC'});
expect(sfOption.selected).toBe(false); expect(sfOption.selected).toBe(false);
async.done(); async.done();
}); });
@ -232,7 +271,7 @@ export function main() {
input.value = "!bb!"; input.value = "!bb!";
dispatchEvent(input, "change"); dispatchEvent(input, "change");
expect(ctx.form.value).toEqual({"name" : "bb"}); expect(ctx.form.value).toEqual({"name": "bb"});
async.done(); async.done();
}); });
})); }));
@ -261,7 +300,7 @@ export function main() {
})); }));
it("should use validators defined in the model", inject([AsyncTestCompleter], (async) => { 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 ctx = new MyComp(form);
var t = `<div [control-group]="form"> var t = `<div [control-group]="form">
@ -281,31 +320,29 @@ export function main() {
}); });
})); }));
}); });
}
describe("nested forms", () => { describe("nested forms", () => {
it("should init DOM with the given form object",inject([AsyncTestCompleter], (async) => { it("should init DOM with the given form object", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({ var form = new ControlGroup({
"nested": new ControlGroup({ "nested": new ControlGroup({
"login": new Control("value") "login": new Control("value")
}) })
}); });
var ctx = new MyComp(form); var ctx = new MyComp(form);
var t = `<div [control-group]="form"> var t = `<div [control-group]="form">
<div control-group="nested"> <div control-group="nested">
<input type="text" control="login"> <input type="text" control="login">
</div> </div>
</div>`; </div>`;
compile(MyComp, t, ctx, (view) => { compile(MyComp, t, ctx, (view) => {
var input = queryView(view, "input") var input = queryView(view, "input")
expect(input.value).toEqual("value"); expect(input.value).toEqual("value");
async.done(); async.done();
}); });
})); }));
if (DOM.supportsDOMEvents()) {
it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => { it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => {
var form = new ControlGroup({ var form = new ControlGroup({
"nested": new ControlGroup({ "nested": new ControlGroup({
@ -326,13 +363,13 @@ export function main() {
input.value = "updatedValue"; input.value = "updatedValue";
dispatchEvent(input, "change"); dispatchEvent(input, "change");
expect(form.value).toEqual({"nested" : {"login" : "updatedValue"}}); expect(form.value).toEqual({"nested": {"login": "updatedValue"}});
async.done(); async.done();
}); });
})); }));
} });
}); });
}); }
} }
@Component({ @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({ @Decorator({
selector:'[wrapped-value]' selector:'[wrapped-value]',
events: {
'change' : 'handleOnChange($event.target.value)'
}
}) })
class WrappedValue { class WrappedValue {
constructor(cd:ControlDirective) { _setProperty:Function;
cd.valueAccessor = new WrappedValueAccessor(); 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 {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib';
import {ControlGroup, Control, OptionalControl} from 'angular2/forms'; import {ControlGroup, Control, OptionalControl, Validators} from 'angular2/forms';
import * as validations from 'angular2/forms';
export function main() { export function main() {
describe("Form Model", () => { describe("Form Model", () => {
describe("Control", () => { describe("Control", () => {
describe("validator", () => { describe("validator", () => {
it("should run validator with the initial value", () => { 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); expect(c.valid).toEqual(true);
}); });
it("should rerun the validator when the value changes", () => { 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); c.updateValue(null);
expect(c.valid).toEqual(false); expect(c.valid).toEqual(false);
}); });
it("should return errors", () => { it("should return errors", () => {
var c = new Control(null, validations.required); var c = new Control(null, Validators.required);
expect(c.errors).toEqual({"required" : true}); expect(c.errors).toEqual({"required" : true});
}); });
}); });
@ -83,7 +82,7 @@ export function main() {
describe("validator", () => { describe("validator", () => {
it("should run the validator with the initial value (valid)", () => { it("should run the validator with the initial value (valid)", () => {
var g = new ControlGroup({ var g = new ControlGroup({
"one": new Control('value', validations.required) "one": new Control('value', Validators.required)
}); });
expect(g.valid).toEqual(true); expect(g.valid).toEqual(true);
@ -92,7 +91,7 @@ export function main() {
}); });
it("should run the validator with the initial value (invalid)", () => { 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}); var g = new ControlGroup({"one": one});
expect(g.valid).toEqual(false); expect(g.valid).toEqual(false);
@ -101,7 +100,7 @@ export function main() {
}); });
it("should run the validator with the value changes", () => { 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}); var g = new ControlGroup({"one": c});
c.updateValue("some value"); c.updateValue("some value");
@ -174,10 +173,10 @@ export function main() {
expect(group.value).toEqual({"required" : "requiredValue", "optional" : "optionalValue"}); 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({ var group = new ControlGroup({
"required": new Control("requiredValue", validations.required), "required": new Control("requiredValue", Validators.required),
"optional": new Control("", validations.required) "optional": new Control("", Validators.required)
}, { }, {
"optional": false "optional": false
}); });

View File

@ -1,5 +1,5 @@
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib'; 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() { export function main() {
function validator(key:string, error:any){ function validator(key:string, error:any){
@ -13,31 +13,31 @@ export function main() {
describe("Validators", () => { describe("Validators", () => {
describe("required", () => { describe("required", () => {
it("should error on an empty string", () => { 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", () => { 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", () => { 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", () => { describe("compose", () => {
it("should collect errors from all the validators", () => { 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}); expect(c(new Control(""))).toEqual({"a" : true, "b" : true});
}); });
it("should run validators left to right", () => { 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}); expect(c(new Control(""))).toEqual({"a" : 2});
}); });
it("should return null when no errors", () => { 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); expect(c(new Control(""))).toEqual(null);
}); });
}); });
@ -48,7 +48,7 @@ export function main() {
var two = new Control("one", validator("b", true)); var two = new Control("one", validator("b", true));
var g = new ControlGroup({"one" : one, "two" : two}); var g = new ControlGroup({"one" : one, "two" : two});
expect(controlGroupValidator(g)).toEqual({ expect(Validators.group(g)).toEqual({
"a" : [one], "a" : [one],
"b" : [two] "b" : [two]
}); });
@ -59,7 +59,7 @@ export function main() {
var two = new Control("two"); var two = new Control("two");
var g = new ControlGroup({"one" : one, "two" : two}); var g = new ControlGroup({"one" : one, "two" : two});
expect(controlGroupValidator(g)).toEqual({ expect(Validators.group(g)).toEqual({
"a": [one] "a": [one]
}); });
}); });
@ -69,7 +69,7 @@ export function main() {
"one" : new Control("one") "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:code_transformers/tests.dart';
import 'package:dart_style/dart_style.dart'; import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:unittest/unittest.dart'; import 'package:guinness/guinness.dart';
import 'package:unittest/vm_config.dart'; import 'package:unittest/vm_config.dart';
import '../common/read_file.dart'; import '../common/read_file.dart';
@ -17,7 +17,7 @@ var formatter = new DartFormatter();
void allTests() { void allTests() {
var reader = new TestAssetReader(); 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 { () async {
var inputPath = 'bind_generator/basic_bind_files/bar.ng_deps.dart'; var inputPath = 'bind_generator/basic_bind_files/bar.ng_deps.dart';
var expected = formatter.format( var expected = formatter.format(
@ -25,10 +25,10 @@ void allTests() {
var output = formatter var output = formatter
.format(await createNgSetters(reader, new AssetId('a', inputPath))); .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 { 'same property.', () async {
var inputPath = var inputPath =
'bind_generator/duplicate_bind_name_files/soup.ng_deps.dart'; 'bind_generator/duplicate_bind_name_files/soup.ng_deps.dart';
@ -37,6 +37,6 @@ void allTests() {
var output = formatter var output = formatter
.format(await createNgSetters(reader, new AssetId('a', inputPath))); .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/core/application.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart'; import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'bar.dart'; import 'bar.dart';
import 'a:web/bar.ng_deps.dart' as i0; import 'bar.ng_deps.dart' as i0;
bool _visited = false; bool _visited = false;
void setupReflection(reflector) { 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 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'bar.dart'; import 'bar.dart';
void main() { bool _visited = false;
reflector.reflectionCapabilities = new ReflectionCapabilities(); void setupReflection(reflector) {
bootstrap(MyComponent); 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:code_transformers/tests.dart';
import 'package:dart_style/dart_style.dart'; import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:unittest/unittest.dart'; import 'package:guinness/guinness.dart';
import 'package:unittest/vm_config.dart'; import 'package:unittest/vm_config.dart';
import '../common/read_file.dart'; import '../common/read_file.dart';
@ -15,12 +15,12 @@ import '../common/read_file.dart';
var formatter = new DartFormatter(); var formatter = new DartFormatter();
void allTests() { 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 inputPath = 'parameter_metadata/soup.dart';
var expected = _readFile('parameter_metadata/expected/soup.ng_deps.dart'); var expected = _readFile('parameter_metadata/expected/soup.ng_deps.dart');
var output = var output =
formatter.format(createNgDeps(_readFile(inputPath), inputPath)); 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)]], 'parameters': const [const [Tasty, String], const [const Inject(Salt)]],
'annotations': const [const Component(selector: '[soup]')] '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:angular2/transformer.dart';
import 'package:code_transformers/tests.dart'; import 'package:code_transformers/tests.dart';
import 'package:dart_style/dart_style.dart'; import 'package:dart_style/dart_style.dart';
import 'package:unittest/unittest.dart'; import 'package:guinness/guinness.dart';
import '../common/read_file.dart'; import '../common/read_file.dart';
@ -96,17 +96,6 @@ void allTests() {
outputs: { outputs: {
'a|web/bar.ng_deps.dart': 'a|web/bar.ng_deps.dart':
'two_annotations_files/expected/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:analyzer/analyzer.dart';
import 'package:angular2/src/transform/reflection_remover/codegen.dart'; import 'package:angular2/src/transform/reflection_remover/codegen.dart';
import 'package:angular2/src/transform/reflection_remover/rewriter.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 'reflection_remover_files/expected/index.dart' as expected;
import '../common/read_file.dart'; import '../common/read_file.dart';
@ -11,11 +11,11 @@ import '../common/read_file.dart';
void allTests() { void allTests() {
var codegen = new Codegen('web/index.dart', 'web/index.ng_deps.dart'); 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 = var code =
readFile('reflection_remover/reflection_remover_files/index.dart'); readFile('reflection_remover/reflection_remover_files/index.dart');
var output = var output =
new Rewriter(code, codegen).rewrite(parseCompilationUnit(code)); 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/common/formatter.dart';
import 'package:angular2/src/transform/template_compiler/generator.dart'; import 'package:angular2/src/transform/template_compiler/generator.dart';
import 'package:dart_style/dart_style.dart'; import 'package:dart_style/dart_style.dart';
import 'package:unittest/unittest.dart'; import 'package:guinness/guinness.dart';
import '../common/read_file.dart'; import '../common/read_file.dart';
@ -16,7 +16,7 @@ void allTests() {
Html5LibDomAdapter.makeCurrent(); Html5LibDomAdapter.makeCurrent();
AssetReader reader = new TestAssetReader(); AssetReader reader = new TestAssetReader();
test('should parse simple expressions in inline templates.', () async { it('should parse simple expressions in inline templates.', () async {
var inputPath = var inputPath =
'template_compiler/inline_expression_files/hello.ng_deps.dart'; 'template_compiler/inline_expression_files/hello.ng_deps.dart';
var expected = readFile( var expected = readFile(
@ -24,16 +24,16 @@ void allTests() {
var output = await processTemplates(reader, new AssetId('a', inputPath)); var output = await processTemplates(reader, new AssetId('a', inputPath));
output = formatter.format(output); output = formatter.format(output);
expected = formatter.format(expected); 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 inputPath = 'template_compiler/inline_method_files/hello.ng_deps.dart';
var expected = readFile( var expected = readFile(
'template_compiler/inline_method_files/expected/hello.ng_deps.dart'); 'template_compiler/inline_method_files/expected/hello.ng_deps.dart');
var output = await processTemplates(reader, new AssetId('a', inputPath)); var output = await processTemplates(reader, new AssetId('a', inputPath));
output = formatter.format(output); output = formatter.format(output);
expected = formatter.format(expected); 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 'hello.dart';
import 'package:angular2/angular2.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 'hello.dart';
import 'package:angular2/angular2.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 'hello.dart';
import 'package:angular2/angular2.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 'hello.dart';
import 'package:angular2/angular2.dart' import 'package:angular2/angular2.dart'

View File

@ -1,9 +1,11 @@
library angular2.test.transform; 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 'package:unittest/vm_config.dart';
import 'bind_generator/all_tests.dart' as bindGenerator; 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 'directive_processor/all_tests.dart' as directiveProcessor;
import 'integration/all_tests.dart' as integration; import 'integration/all_tests.dart' as integration;
import 'reflection_remover/all_tests.dart' as reflectionRemover; import 'reflection_remover/all_tests.dart' as reflectionRemover;
@ -11,9 +13,12 @@ import 'template_compiler/all_tests.dart' as templateCompiler;
main() { main() {
useVMConfiguration(); useVMConfiguration();
group('Bind Generator', bindGenerator.allTests); describe('Bind Generator', bindGenerator.allTests);
group('Directive Processor', directiveProcessor.allTests); describe('Directive Linker', directiveLinker.allTests);
group('Reflection Remover', reflectionRemover.allTests); describe('Directive Processor', directiveProcessor.allTests);
group('Template Compiler', templateCompiler.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); 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, "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, "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 "prop": (a,v) => a.prop = v
}); });
} }

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