Compare commits
28 Commits
starting
...
2.0.0-alph
Author | SHA1 | Date | |
---|---|---|---|
81e6d13241 | |||
f8e7a37c0d | |||
c686e7ea30 | |||
7e89af8190 | |||
539e8e2cce | |||
aab084866c | |||
0e61a86763 | |||
1c9938ed98 | |||
47c1a0f381 | |||
514529b5d9 | |||
a12dc7d75a | |||
41b53e71e1 | |||
0fb9f3bd6c | |||
81f3f32217 | |||
b35f288794 | |||
4e82cc0861 | |||
c735644c57 | |||
5d479fa0ae | |||
8baedca972 | |||
02aa8e7945 | |||
ee523efcb4 | |||
eef5f7e06d | |||
83402930f2 | |||
bd48c927d0 | |||
b61b8d60b7 | |||
f1fca5abb6 | |||
045ce3c77a | |||
f822066e2a |
49
DEVELOPER.md
49
DEVELOPER.md
@ -95,23 +95,22 @@ Next, install the modules and packages needed to build Angular and run tests:
|
||||
```shell
|
||||
# Install Angular project dependencies (package.json)
|
||||
npm install
|
||||
|
||||
# Ensure protractor has the latest webdriver
|
||||
$(npm bin)/webdriver-manager update
|
||||
|
||||
# Install Dart packages
|
||||
pub get
|
||||
```
|
||||
|
||||
**Optional**: In this document, we make use of project local `npm` package scripts and binaries
|
||||
(stored under `./node_modules/.bin`) by prefixing these command invocations with `$(npm bin)`; in
|
||||
particular `gulp` and `protractor` commands. If you prefer, you can drop this path prefix by
|
||||
globally installing these two packages as follows:
|
||||
particular `gulp` and `protractor` commands. If you prefer, you can drop this path prefix by either:
|
||||
|
||||
*Option 1*: globally installing these two packages as follows:
|
||||
|
||||
* `npm install -g gulp` (you might need to prefix this command with `sudo`)
|
||||
* `npm install -g protractor` (you might need to prefix this command with `sudo`)
|
||||
|
||||
Since global installs can become stale, we avoid their use in these instructions.
|
||||
Since global installs can become stale, and required versions can vary by project, we avoid their
|
||||
use in these instructions.
|
||||
|
||||
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
|
||||
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
|
||||
|
||||
## Build commands
|
||||
|
||||
@ -133,22 +132,35 @@ $(npm bin)/gulp clean
|
||||
|
||||
## Running Tests Locally
|
||||
|
||||
### Basic tests
|
||||
### Full test suite
|
||||
|
||||
1. `$(npm bin)/gulp test.unit.js`: JS tests in a browser; runs in **watch mode** (i.e. karma
|
||||
watches the test files for changes and re-runs tests when files are updated).
|
||||
2. `$(npm bin)/gulp test.unit.cjs`: JS tests in NodeJS; runs in **watch mode**
|
||||
3. `$(npm bin)/gulp test.unit.dart`: Dart tests in Dartium; runs in **watch mode**.
|
||||
* `npm test`: full test suite for both JS and Dart versions of Angular. These are the same tests as
|
||||
those run on Travis.
|
||||
|
||||
You can selectively run either the JS or Dart versions as follows:
|
||||
|
||||
* `$(npm bin)/gulp test.all.js`
|
||||
* `$(npm bin)/gulp test.all.dart`
|
||||
|
||||
### Unit tests
|
||||
|
||||
You can run just the unit tests as follows:
|
||||
|
||||
* `$(npm bin)/gulp test.unit.js`: JS tests in a browser; runs in **watch mode** (i.e. karma
|
||||
watches the test files for changes and re-runs tests when files are updated).
|
||||
* `$(npm bin)/gulp test.unit.cjs`: JS tests in NodeJS; runs in **watch mode**.
|
||||
* `$(npm bin)/gulp test.unit.dart`: Dart tests in Dartium; runs in **watch mode**.
|
||||
|
||||
If you prefer running tests in "single-run" mode rather than watch mode use
|
||||
|
||||
* `$(npm bin)/gulp test.unit.js/ci`
|
||||
* `$(npm bin)/gulp test.unit.cjs/ci`
|
||||
* `$(npm bin)/gulp test.unit.dart/ci`
|
||||
|
||||
**Note**: If you want to only run a single test you can alter the test you wish
|
||||
to run by changing `it` to `iit` or `describe` to `ddescribe`. This will only
|
||||
run that individual test and make it much easier to debug. `xit` and `xdescribe`
|
||||
can also be useful to exclude a test and a group of tests respectively.
|
||||
**Note**: If you want to only run a single test you can alter the test you wish to run by changing
|
||||
`it` to `iit` or `describe` to `ddescribe`. This will only run that individual test and make it
|
||||
much easier to debug. `xit` and `xdescribe` can also be useful to exclude a test and a group of
|
||||
tests respectively.
|
||||
|
||||
**Note** for transpiler tests: The karma preprocessor is setup in a way so that after every test
|
||||
run the transpiler is reloaded. With that it is possible to make changes to the preprocessor and
|
||||
@ -232,4 +244,3 @@ on the `debugger;` statement.
|
||||
You can then step into the code and add watches.
|
||||
The `debugger;` statement is needed because WebStorm will stop in a transpiled file. Breakpoints in
|
||||
the original source files are not supported at the moment.
|
||||
|
||||
|
@ -36,6 +36,7 @@ comments in the source `modules/examples/src/hello_world/index.js`.
|
||||
You can build this example as either JS or Dart app:
|
||||
|
||||
* JS:
|
||||
* `$(npm bin)/gulp build.js.dev`, and
|
||||
* `$(npm bin)/gulp serve.js.dev`, and
|
||||
* open `localhost:8000/examples/src/hello_world/` in Chrome.
|
||||
* Dart:
|
||||
|
@ -89,6 +89,10 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage])
|
||||
];
|
||||
})
|
||||
|
||||
// Add in a custom tag that we use when generating public docs
|
||||
.config(function(parseTagsProcessor) {
|
||||
parseTagsProcessor.tagDefinitions.push({ name: 'publicModule' });
|
||||
})
|
||||
|
||||
// Configure ids and paths
|
||||
.config(function(computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES) {
|
||||
|
@ -30,11 +30,18 @@ module.exports = function AttachCommentTreeVisitor(ParseTreeVisitor, log) {
|
||||
log.silly('tree: ' + tree.constructor.name + ' - ' + tree.location.start.line);
|
||||
while (this.currentComment &&
|
||||
this.currentComment.range.end.offset < tree.location.start.offset) {
|
||||
log.silly('comment: ' + this.currentComment.range.start.line + ' - ' +
|
||||
this.currentComment.range.end.line + ' : ' +
|
||||
this.currentComment.range.toString());
|
||||
tree.commentBefore = this.currentComment;
|
||||
this.currentComment.treeAfter = tree;
|
||||
var commentText = this.currentComment.range.toString();
|
||||
|
||||
// Only store the comment if it is JSDOC style (e.g. /** some comment */)
|
||||
if (/^\/\*\*([\w\W]*)\*\/$/.test(commentText)) {
|
||||
log.info('comment: ' + this.currentComment.range.start.line + ' - ' +
|
||||
this.currentComment.range.end.line + ' : ' +
|
||||
commentText);
|
||||
|
||||
tree.commentBefore = this.currentComment;
|
||||
this.currentComment.treeAfter = tree;
|
||||
}
|
||||
|
||||
this.index++;
|
||||
this.currentComment = this.comments[this.index];
|
||||
}
|
||||
|
8
docs/dgeni-package/templates/var.template.html
Normal file
8
docs/dgeni-package/templates/var.template.html
Normal 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 %}
|
@ -6,10 +6,6 @@ module.exports = new Package('angular-public', [basePackage])
|
||||
|
||||
.processor(require('./processors/filterPublicDocs'))
|
||||
|
||||
.config(function(parseTagsProcessor) {
|
||||
parseTagsProcessor.tagDefinitions.push({ name: 'publicModule' });
|
||||
})
|
||||
|
||||
.config(function(processClassDocs, filterPublicDocs, EXPORT_DOC_TYPES) {
|
||||
processClassDocs.ignorePrivateMembers = true;
|
||||
filterPublicDocs.docTypes = EXPORT_DOC_TYPES;
|
||||
|
24
gulpfile.js
24
gulpfile.js
@ -1,5 +1,6 @@
|
||||
var gulp = require('gulp');
|
||||
var gulpPlugins = require('gulp-load-plugins')();
|
||||
var shell = require('gulp-shell');
|
||||
var runSequence = require('run-sequence');
|
||||
var madge = require('madge');
|
||||
var merge = require('merge');
|
||||
@ -26,6 +27,7 @@ var runServerDartTests = require('./tools/build/run_server_dart_tests');
|
||||
var transformCJSTests = require('./tools/build/transformCJSTests');
|
||||
var util = require('./tools/build/util');
|
||||
|
||||
// Note: when DART_SDK is not found, all gulp tasks ending with `.dart` will be skipped.
|
||||
var DART_SDK = require('./tools/build/dartdetect')(gulp);
|
||||
// -----------------------
|
||||
// configuration
|
||||
@ -431,7 +433,11 @@ gulp.task('build/multicopy.dart', copy.multicopy(gulp, gulpPlugins, {
|
||||
// ------------
|
||||
// pubspec
|
||||
|
||||
gulp.task('build/pubspec.dart', pubget(gulp, gulpPlugins, {
|
||||
// Run a top-level `pub get` for this project.
|
||||
gulp.task('pubget.dart', pubget.dir(gulp, gulpPlugins, { dir: '.', command: DART_SDK.PUB }));
|
||||
|
||||
// Run `pub get` over CONFIG.dest.dart
|
||||
gulp.task('build/pubspec.dart', pubget.subDir(gulp, gulpPlugins, {
|
||||
dir: CONFIG.dest.dart,
|
||||
command: DART_SDK.PUB
|
||||
}));
|
||||
@ -588,6 +594,22 @@ createDocsTasks(true);
|
||||
createDocsTasks(false);
|
||||
|
||||
// ------------------
|
||||
// CI tests suites
|
||||
|
||||
gulp.task('test.js', function(done) {
|
||||
runSequence('test.transpiler.unittest', 'docs/test', 'test.unit.js/ci',
|
||||
'test.unit.cjs/ci', done);
|
||||
});
|
||||
|
||||
gulp.task('test.dart', function(done) {
|
||||
runSequence('test.transpiler.unittest', 'docs/test', 'test.unit.dart/ci', done);
|
||||
});
|
||||
|
||||
// Reuse the Travis scripts
|
||||
// TODO: rename test_*.sh to test_all_*.sh
|
||||
gulp.task('test.all.js', shell.task(['./scripts/ci/test_js.sh']))
|
||||
gulp.task('test.all.dart', shell.task(['./scripts/ci/test_dart.sh']))
|
||||
|
||||
// karma tests
|
||||
// These tests run in the browser and are allowed to access
|
||||
// HTML DOM APIs.
|
||||
|
4
modules/angular2/change_detection.js
vendored
4
modules/angular2/change_detection.js
vendored
@ -18,7 +18,7 @@ export * from './src/change_detection/pipes/pipe';
|
||||
import {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector}
|
||||
from './src/change_detection/proto_change_detector';
|
||||
import {PipeRegistry} from './src/change_detection/pipes/pipe_registry';
|
||||
import {ArrayChangesFactory} from './src/change_detection/pipes/array_changes';
|
||||
import {IterableChangesFactory} from './src/change_detection/pipes/iterable_changes';
|
||||
import {KeyValueChangesFactory} from './src/change_detection/pipes/keyvalue_changes';
|
||||
import {NullPipeFactory} from './src/change_detection/pipes/null_pipe';
|
||||
|
||||
@ -31,7 +31,7 @@ export class ChangeDetection {
|
||||
|
||||
export var defaultPipes = {
|
||||
"iterableDiff" : [
|
||||
new ArrayChangesFactory(),
|
||||
new IterableChangesFactory(),
|
||||
new NullPipeFactory()
|
||||
],
|
||||
"keyValDiff" : [
|
||||
|
1
modules/angular2/core.js
vendored
1
modules/angular2/core.js
vendored
@ -2,6 +2,7 @@ export * from './src/core/annotations/visibility';
|
||||
export * from './src/core/compiler/interfaces';
|
||||
export * from './src/core/annotations/template';
|
||||
export * from './src/core/application';
|
||||
export * from './src/core/annotations/di';
|
||||
|
||||
export * from './src/core/compiler/compiler';
|
||||
|
||||
|
2
modules/angular2/directives.js
vendored
2
modules/angular2/directives.js
vendored
@ -1,4 +1,4 @@
|
||||
export * from './src/directives/foreach';
|
||||
export * from './src/directives/for';
|
||||
export * from './src/directives/if';
|
||||
export * from './src/directives/non_bindable';
|
||||
export * from './src/directives/switch';
|
||||
|
@ -187,7 +187,7 @@ Example:
|
||||
<pre>
|
||||
```
|
||||
<ul>
|
||||
<li template="foreach: #item in items">
|
||||
<li template="for: #item of items">
|
||||
{{item}}
|
||||
</li>
|
||||
</ul>
|
||||
@ -201,8 +201,8 @@ Example:
|
||||
<pre>
|
||||
```
|
||||
<ul>
|
||||
<template def-foreach:"item"
|
||||
bind-foreach-in="items">
|
||||
<template def-for:"item"
|
||||
bind-for-in="items">
|
||||
<li>
|
||||
{{item}}
|
||||
</li>
|
||||
@ -221,8 +221,8 @@ Example:
|
||||
|
||||
<pre>
|
||||
```
|
||||
<template #foreach="item"
|
||||
[foreach-in]="items">
|
||||
<template #for="item"
|
||||
[for-in]="items">
|
||||
_some_content_to_repeat_
|
||||
</template>
|
||||
```
|
||||
@ -234,8 +234,8 @@ Example:
|
||||
Example:
|
||||
<pre>
|
||||
```
|
||||
<template def-foreach="item"
|
||||
bind-foreach-in="items">
|
||||
<template def-for="item"
|
||||
bind-for-in="items">
|
||||
_some_content_to_repeat_
|
||||
</template>
|
||||
```
|
||||
@ -408,22 +408,22 @@ NOTE: Only Viewport directives can be placed on the template element. (Decorator
|
||||
### Template Microsyntax
|
||||
|
||||
Often times it is necessary to encode a lot of different bindings into a template to control how the instantiation
|
||||
of the templates occurs. One such example is `foreach`.
|
||||
of the templates occurs. One such example is `for`.
|
||||
|
||||
```
|
||||
<form #foo=form>
|
||||
</form>
|
||||
<ul>
|
||||
<template foreach #person [in]="people" #i="index">
|
||||
<template for #person [in]="people" #i="index">
|
||||
<li>{{i}}. {{person}}<li>
|
||||
</template>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Where:
|
||||
* `foreach` triggers the foreach directive.
|
||||
* `[in]="people"` binds an iterable object to the `foreach` controller.
|
||||
* `#person` exports the implicit `foreach` item.
|
||||
* `for` triggers the for directive.
|
||||
* `[in]="people"` binds an iterable object to the `for` controller.
|
||||
* `#person` exports the implicit `for` item.
|
||||
* `#i=index` exports item index as `i`.
|
||||
|
||||
The above example is explicit but quite wordy. For this reason in most situations a short hand version of the
|
||||
@ -431,7 +431,7 @@ syntax is preferable.
|
||||
|
||||
```
|
||||
<ul>
|
||||
<li template="foreach; #person; in=people; #i=index;">{{i}}. {{person}}<li>
|
||||
<li template="for; #person; in=people; #i=index;">{{i}}. {{person}}<li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
@ -441,19 +441,28 @@ which allows us to further shorten the text.
|
||||
|
||||
```
|
||||
<ul>
|
||||
<li template="foreach #person in people #i=index">{{i}}. {{person}}<li>
|
||||
<li template="for #person of people #i=index">{{i}}. {{person}}<li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
We can also optionally use `var` instead of `#` and add `:` to `foreach` which creates the following recommended
|
||||
microsyntax for `foreach`.
|
||||
We can also optionally use `var` instead of `#` and add `:` to `for` which creates the following recommended
|
||||
microsyntax for `for`.
|
||||
|
||||
```
|
||||
<ul>
|
||||
<li template="foreach: var person in people; var i=index">{{i}}. {{person}}<li>
|
||||
<li template="for: var person of people; var i=index">{{i}}. {{person}}<li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Finally, we can move the `for` keyword to the left hand side and prefix it with `*` as so:
|
||||
|
||||
```
|
||||
<ul>
|
||||
<li *for="var person of people; var i=index">{{i}}. {{person}}<li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
|
||||
The format is intentionally defined freely, so that developers of directives can build an expressive microsyntax for
|
||||
their directives. The following code describes a more formal definition.
|
||||
|
||||
|
@ -70,7 +70,7 @@ Here is a trivial example of a tooltip decorator. The directive will log a toolt
|
||||
bind: { // List which properties need to be bound
|
||||
text: 'tooltip' // - DOM element tooltip property should be
|
||||
}, // mapped to the directive text property.
|
||||
event: { // List which events need to be mapped.
|
||||
events: { // List which events need to be mapped.
|
||||
mouseover: 'show' // - Invoke the show() method every time
|
||||
} // the mouseover event is fired.
|
||||
})
|
||||
|
@ -16,17 +16,17 @@ import {
|
||||
|
||||
import {NO_CHANGE, Pipe} from './pipe';
|
||||
|
||||
export class ArrayChangesFactory {
|
||||
export class IterableChangesFactory {
|
||||
supports(obj):boolean {
|
||||
return ArrayChanges.supportsObj(obj);
|
||||
return IterableChanges.supportsObj(obj);
|
||||
}
|
||||
|
||||
create():Pipe {
|
||||
return new ArrayChanges();
|
||||
return new IterableChanges();
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrayChanges extends Pipe {
|
||||
export class IterableChanges extends Pipe {
|
||||
_collection;
|
||||
_length:int;
|
||||
_linkedRecords:_DuplicateMap;
|
||||
@ -66,7 +66,7 @@ export class ArrayChanges extends Pipe {
|
||||
}
|
||||
|
||||
supports(obj):boolean {
|
||||
return ArrayChanges.supportsObj(obj);
|
||||
return IterableChanges.supportsObj(obj);
|
||||
}
|
||||
|
||||
get collection() {
|
513
modules/angular2/src/core/annotations/annotations.js
vendored
513
modules/angular2/src/core/annotations/annotations.js
vendored
@ -7,58 +7,62 @@ import {Injectable} from 'angular2/di';
|
||||
/**
|
||||
* Directives allow you to attach behavior to elements in the DOM.
|
||||
*
|
||||
* Directive is an abstract concept, instead use concrete directives: [Component], [DynamicComponent], [Decorator]
|
||||
* Directive is an abstract concept, instead use concrete directives: [Component], [DynamicComponent], [Decorator]
|
||||
* or [Viewport].
|
||||
*
|
||||
* A directive consists of a single directive annotation and a controller class. When the directive's [selector] matches
|
||||
* elements in the DOM, the following steps occur:
|
||||
*
|
||||
* 1. For each directive, the [ElementInjector] attempts to resolve the directive's constructor arguments.
|
||||
* 2. Angular instantiates directives for each matched element using [ElementInjector].
|
||||
* 2. Angular instantiates directives for each matched element using [ElementInjector] in a depth-first order,
|
||||
* as declared in the HTML.
|
||||
*
|
||||
* ## Understanding How Injection Works
|
||||
*
|
||||
*
|
||||
* There are three stages of injection resolution.
|
||||
* - *Pre-existing Injectors*:
|
||||
* - The terminal [Injector] cannot resolve dependencies. It either throws an error or, if the dependency was
|
||||
* - *Pre-existing Injectors*:
|
||||
* - The terminal [Injector] cannot resolve dependencies. It either throws an error or, if the dependency was
|
||||
* specified as `@Optional`, returns `null`.
|
||||
* - The primordial injector resolves browser singleton resources, such as: cookies, title, location, and others.
|
||||
* - *Component Injectors*: Each `@Component` has its own [Injector], and they follow the same parent-child hierachy
|
||||
* - *Component Injectors*: Each `@Component` has its own [Injector], and they follow the same parent-child hierachy
|
||||
* as the components in the DOM.
|
||||
* - *Element Injectors*: Each component has a Shadow DOM. Within the Shadow DOM each element has an [ElementInjector]
|
||||
* - *Element Injectors*: Each component has a Shadow DOM. Within the Shadow DOM each element has an [ElementInjector]
|
||||
* which follow the same parent-child hiercachy as the DOM elements themselves.
|
||||
*
|
||||
* When resolving dependencies, the current injector is asked to resolve the dependency first, and if it does not
|
||||
* have it, it delegates to the parent injector.
|
||||
*
|
||||
*
|
||||
* When a template is instantiated, it also must instantiate the corresponding directives in a depth-first order. The
|
||||
* current [ElementInjector] resolves the constructor dependencies for each directive.
|
||||
*
|
||||
* Angular then resolves dependencies as follows, according to the order in which they appear in the [View]:
|
||||
*
|
||||
* 1. Dependencies on element injectors and their parents until it encounters a Shadow DOM boundary
|
||||
* 2. Dependencies on component injectors and their parents until it encounters the root component
|
||||
* 3. Dependencies on pre-existing injectors
|
||||
*
|
||||
*
|
||||
* The [ElementInjector] can inject other directives, element-specific special objects, or can delegate to the parent
|
||||
*
|
||||
* 1. Dependencies on the current element
|
||||
* 2. Dependencies on element injectors and their parents until it encounters a Shadow DOM boundary
|
||||
* 3. Dependencies on component injectors and their parents until it encounters the root component
|
||||
* 4. Dependencies on pre-existing injectors
|
||||
*
|
||||
*
|
||||
* The [ElementInjector] can inject other directives, element-specific special objects, or it can delegate to the parent
|
||||
* injector.
|
||||
*
|
||||
*
|
||||
* To inject other directives, declare the constructor parameter as:
|
||||
* - `directive:DirectiveType`: a directive on the current element only
|
||||
* - `@Ancestor() d:Type`: any directive that matches the type between the current element (excluded) and the Shadow DOM root [TODO: what does (excluded) mean? Does this apply to the @Parent annotation also?]
|
||||
* - `@Parent() d:Type`: any directive that matches the type on a direct parent element only
|
||||
* - `@Children query:Query<Type>`: A live collection of direct child directives
|
||||
* - `@Descendants query:Query<Type>`: A live collection of any child directives
|
||||
*
|
||||
* - `directive:DirectiveType`: a directive on the current element only
|
||||
* - `@Ancestor() directive:DirectiveType`: any directive that matches the type between the current element and the
|
||||
* Shadow DOM root. Current Element is not included in the resolution, therefor even if it could resolve it, it will
|
||||
* be ignored.
|
||||
* - `@Parent() directive:DirectiveType`: any directive that matches the type on a direct parent element only.
|
||||
* - `@Children query:Query<DirectiveType>`: A live collection of direct child directives [TO BE IMPLEMENTED].
|
||||
* - `@Descendants query:Query<DirectiveType>`: A live collection of any child directives [TO BE IMPLEMENTED].
|
||||
*
|
||||
* To inject element-specific special objects, declare the constructor parameter as:
|
||||
* - `element: NgElement` to obtain a DOM element (DEPRECATED: replacment coming)
|
||||
* - `viewContainer: ViewContainer` to control child template instantiation, for [Viewport] directives only
|
||||
* - `bindingPropagation: BindingPropagation` to control change detection in a more granular way
|
||||
*
|
||||
* - `element: NgElement` to obtain a DOM element (DEPRECATED: replacment coming)
|
||||
* - `viewContainer: ViewContainer` to control child template instantiation, for [Viewport] directives only
|
||||
* - `bindingPropagation: BindingPropagation` to control change detection in a more granular way.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* The following example demonstrates how dependency injection resolves constructor arguments in practice.
|
||||
*
|
||||
*
|
||||
* Assume this HTML structure:
|
||||
* Assume this HTML template:
|
||||
*
|
||||
* ```
|
||||
* <div dependency="1">
|
||||
@ -93,9 +97,10 @@ import {Injectable} from 'angular2/di';
|
||||
*
|
||||
* Let's step through the different ways in which `MyDirective` could be declared...
|
||||
*
|
||||
*
|
||||
* ### No injection
|
||||
*
|
||||
* Here the constructor is declared with no arguments, so nothing is injected into `MyDirective`.
|
||||
* Here the constructor is declared with no arguments, therefore nothing is injected into `MyDirective`.
|
||||
*
|
||||
* ```
|
||||
* @Decorator({ selector: '[my-directive]' })
|
||||
@ -105,9 +110,8 @@ import {Injectable} from 'angular2/di';
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This directive would return nothing for the example code above. [TODO: True? We spent a lot of time talking about
|
||||
* errors but in this case, there's nothing to error on, right? I don't understand the diff between "returns" and "injects"
|
||||
* when the example is showing a directive not the template. Which is the correct verb?]
|
||||
* This directive would be instantiated with no dependencies.
|
||||
*
|
||||
*
|
||||
* ### Component-level injection
|
||||
*
|
||||
@ -124,8 +128,9 @@ import {Injectable} from 'angular2/di';
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This directive would return `dependency=3` for the example code above. [TODO: True? Is "return" the right verb?]
|
||||
*
|
||||
* This directive would be instantiated with a dependency on `SomeService`.
|
||||
*
|
||||
*
|
||||
* ### Injecting a directive from the current element
|
||||
*
|
||||
* Directives can inject other directives declared on the current element.
|
||||
@ -134,18 +139,18 @@ import {Injectable} from 'angular2/di';
|
||||
* @Decorator({ selector: '[my-directive]' })
|
||||
* class MyDirective {
|
||||
* constructor(dependency: Dependency) {
|
||||
* expect(dependency.id).toEqual(2);
|
||||
* expect(dependency.id).toEqual(3);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* This directive would also return `dependency=3` for the example code above. [TODO: True? Why is this the same?]
|
||||
*
|
||||
* This directive would be instantiated with `Dependency` declared at the same element, in this case `dependency="3"`.
|
||||
*
|
||||
*
|
||||
* ### Injecting a directive from a direct parent element
|
||||
*
|
||||
* Directives can inject other directives declared on a direct parent element. By definition, a directive with a
|
||||
* Directives can inject other directives declared on a direct parent element. By definition, a directive with a
|
||||
* `@Parent` annotation does not attempt to resolve dependencies for the current element, even if this would satisfy
|
||||
* the dependency. [TODO: did I get the subject/verb right?]
|
||||
* the dependency.
|
||||
*
|
||||
* ```
|
||||
* @Decorator({ selector: '[my-directive]' })
|
||||
@ -155,17 +160,15 @@ import {Injectable} from 'angular2/di';
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* This directive would return `dependency=2` for the example code above. [TODO: True?]
|
||||
*
|
||||
* This directive would be instantiated with `Dependency` declared at the parent element, in this case `dependency="2"`.
|
||||
*
|
||||
*
|
||||
* ### Injecting a directive from any ancestor elements
|
||||
*
|
||||
* Directives can inject other directives declared on any ancestor element, i.e. on the parent element and its parents.
|
||||
* By definition, a directive with an `@Ancestor` annotation does not attempt to resolve dependencies for the current
|
||||
* element, even if this would satisfy the dependency. [TODO: did I get the subject/verb right? ]
|
||||
* Directives can inject other directives declared on any ancestor element (in the current Shadow DOM), i.e. on the
|
||||
* parent element and its parents. By definition, a directive with an `@Ancestor` annotation does not attempt to
|
||||
* resolve dependencies for the current element, even if this would satisfy the dependency.
|
||||
*
|
||||
* Unlike the `@Parent` which only checks the parent `@Ancestor` checks the parent, as well as its
|
||||
* parents recursivly. If `dependency="2"` would not be present this injection would return `dependency="1"`.
|
||||
|
||||
* ```
|
||||
* @Decorator({ selector: '[my-directive]' })
|
||||
* class MyDirective {
|
||||
@ -174,59 +177,61 @@ import {Injectable} from 'angular2/di';
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This directive would also return `dependency=2` for the example code above. If `dependency=2` hadn't been declared
|
||||
* on the parent `div`, this directive would return `d[TODO: True?]
|
||||
*
|
||||
* ### Injecting query of child directives. [PENDING IMPLEMENTATION]
|
||||
* Unlike the `@Parent` which only checks the parent, `@Ancestor` checks the parent, as well as its
|
||||
* parents recursively. If `dependency="2"` didn't exist on the direct parent, this injection would have returned
|
||||
* `dependency="1"`.
|
||||
*
|
||||
* In some cases the directive may be interersted in injecting its child directives. This is not directly possible
|
||||
* since parent directives are guarteed to be created before child directives. Instead we can injecto a container
|
||||
* which can than be filled once the data is needed.
|
||||
*
|
||||
* ### Injecting a live collection of direct child directives [PENDING IMPLEMENTATION]
|
||||
*
|
||||
* A directive can also query for other child directives. Since parent directives are instantiated before child
|
||||
* directives, a directive can't simply inject the list of child directives. Instead, the directive asynchronously
|
||||
* injects a [Query], which updates as children are added, removed, or moved by any [ViewPort] directive such as a
|
||||
* `for`, an `if`, or a `switch`.
|
||||
*
|
||||
* ```
|
||||
* @Decorator({ selector: '[my-directive]' })
|
||||
* class MyDirective {
|
||||
* constructor(@Children() dependencys:Query<Maker>) {
|
||||
* // dependencys will eventuall contain: [4, 6]
|
||||
* // this will upbate if children are added/removed/moved,
|
||||
* // for example by having for or if.
|
||||
* constructor(@Children() dependencies:Query<Maker>) {
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This directive would be instantiated with a [Query] which contains `Dependency` 4 and 6. Here, `Dependency` 5 would
|
||||
* not be included, because it is not a direct child.
|
||||
*
|
||||
* ### Injecting query of descendant directives. [PENDING IMPLEMENTATION]
|
||||
* ### Injecting a live collection of direct descendant directives [PENDING IMPLEMENTATION]
|
||||
*
|
||||
* Similar to `@Children` but also includ childre of those children.
|
||||
* Similar to `@Children` above, but also includes the children of the child elements.
|
||||
*
|
||||
* ```
|
||||
* @Decorator({ selector: '[my-directive]' })
|
||||
* class MyDirective {
|
||||
* constructor(@Children() dependencys:Query<Maker>) {
|
||||
* // dependencys will eventuall contain: [4, 5, 6]
|
||||
* // this will upbate if children are added/removed/moved,
|
||||
* // for example by having for or if.
|
||||
* constructor(@Children() dependencies:Query<Maker>) {
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This directive would be instantiated with a Query which would contain `Dependency` 4, 5 and 6.
|
||||
*
|
||||
* ### Optional injection
|
||||
*
|
||||
* Finally there may be times when we would like to inject a component which may or may not be there. For this
|
||||
* use case angular supports `@Optional` injection.
|
||||
* The normal behavior of directives is to return an error when a specified dependency cannot be resolved. If you
|
||||
* would like to inject `null` on unresolved dependency instead, you can annotate that dependency with `@Optional()`.
|
||||
* This explicitly permits the author of a template to treat some of the surrounding directives as optional.
|
||||
*
|
||||
* ```
|
||||
* @Decorator({ selector: '[my-directive]' })
|
||||
* class MyDirective {
|
||||
* constructor(@Optional() @Ancestor() form:Form) {
|
||||
* // this will search for a Form directive above itself,
|
||||
* // and inject null if not found
|
||||
* constructor(@Optional() dependency:Dependency) {
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This directive would be instantiated with a `Dependency` directive found on the current element. If none can be
|
||||
* found, the injector supplies `null` instead of throwing an error.
|
||||
*
|
||||
* @publicModule angular2/annotations
|
||||
*/
|
||||
@ABSTRACT()
|
||||
@ -235,13 +240,16 @@ export class Directive extends Injectable {
|
||||
* The CSS selector that triggers the instantiation of a directive.
|
||||
*
|
||||
* Angular only allows directives to trigger on CSS selectors that do not cross element boundaries.
|
||||
* The supported selectors are:
|
||||
*
|
||||
* - `element-name` select by element name.
|
||||
* - `.class` select by class name.
|
||||
* - `[attribute]` select by attribute name.
|
||||
* - `[attribute=value]` select by attribute name and value.
|
||||
* - `:not(sub_selector)` select only if the element does not match the `sub_selector`.
|
||||
* `selector` may be declared as one of the following:
|
||||
*
|
||||
* - `element-name`: select by element name.
|
||||
* - `.class`: select by class name.
|
||||
* - `[attribute]`: select by attribute name.
|
||||
* - `[attribute=value]`: select by attribute name and value.
|
||||
* - `:not(sub_selector)`: select only if the element does not match the `sub_selector`.
|
||||
* - `selector1, selector2`: select if either `selector1` or `selector2` matches.
|
||||
*
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
@ -270,7 +278,8 @@ export class Directive extends Injectable {
|
||||
* - `bindingProperty` specifies the DOM property where the value is read from.
|
||||
*
|
||||
* You can include [Pipes] when specifying a `bindingProperty` to allow for data transformation and structural
|
||||
* change detection of the value.
|
||||
* change detection of the value. These pipes will be evaluated in the context of this component.
|
||||
*
|
||||
*
|
||||
* ## Syntax
|
||||
*
|
||||
@ -285,43 +294,45 @@ export class Directive extends Injectable {
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* ## Basic Property Binding:
|
||||
* ## Basic Property Binding
|
||||
*
|
||||
* We can easily build a simple `Tooltip` directive that exposes a `tooltip` property, which can be used in templates
|
||||
* with standard Angular syntax. For example:
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
* selector: '[tooltip]',
|
||||
* bind: {
|
||||
* 'tooltipText': 'tooltip'
|
||||
* 'text': 'tooltip'
|
||||
* }
|
||||
* })
|
||||
* class Tooltip {
|
||||
* set tooltipText(text) {
|
||||
* set text(text) {
|
||||
* // This will get called every time the 'tooltip' binding changes with the new value.
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* As used in this example:
|
||||
* We can then bind to the `tooltip' property as either an expression (`someExpression`) or as a string literal, as
|
||||
* shown in the HTML template below:
|
||||
*
|
||||
* ```html
|
||||
* <div [tooltip]="someExpression">
|
||||
* <div [tooltip]="someExpression">...</div>
|
||||
* <div tooltip="Some Text">...</div>
|
||||
* ```
|
||||
*
|
||||
* Whenever the `someExpression` expression changes, the `bind` declaration instructs Angular to update the
|
||||
* `Tooltip`'s `tooltipText` property.
|
||||
*
|
||||
*
|
||||
* Similarly in this example:
|
||||
*
|
||||
* ```html
|
||||
* <div tooltip="Some Text">
|
||||
* ```
|
||||
*
|
||||
* The `Tooltip`'s `tooltipText` property gets initialized to the `Some Text` literal.
|
||||
*
|
||||
*
|
||||
* ## Bindings With Pipes:
|
||||
*
|
||||
* You can also use pipes when writing binding definitions for a directive.
|
||||
*
|
||||
* For example, we could write a binding that updates the directive on structural changes, rather than on reference
|
||||
* changes, as normally occurs in change detection. (See: [Pipe] and [keyValueDiff] documentaition for more details.)
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
* selector: '[class-set]',
|
||||
@ -336,29 +347,30 @@ export class Directive extends Injectable {
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* As used in this example:
|
||||
* The template that this directive is used in may also contain its own pipes. For example:
|
||||
*
|
||||
* ```html
|
||||
* <div [class-set]="someExpression">
|
||||
* <div [class-set]="someExpression | somePipe">
|
||||
* ```
|
||||
*
|
||||
* In the above example, the `ClassSet` uses the `keyValDiff` [Pipe] for watching structural changes. This means that
|
||||
* the `classChanges` setter gets invoked if the expression changes to a different reference, or if the
|
||||
* structure of the expression changes. (Shallow property watching of the object)
|
||||
*
|
||||
* NOTE: The `someExpression` can also contain its own [Pipe]s. In this case, the two pipes compose as if they were
|
||||
* inlined.
|
||||
* In this case, the two pipes compose as if they were inlined: `someExpression | somePipe | keyValDiff`.
|
||||
*
|
||||
*/
|
||||
bind:any; // StringMap
|
||||
|
||||
/**
|
||||
* Specifies which DOM events the directive listens to and what the action should be when they occur.
|
||||
* Specifies which DOM events a directive listens to.
|
||||
*
|
||||
* The `events` property defines a set of `event` to `method` key-value pairs:
|
||||
*
|
||||
* - `event1` specifies the DOM event that the directive listens to.
|
||||
* - `onMethod1` specifies the method to execute when the event occurs.
|
||||
* - `event1`: the DOM event that the directive listens to.
|
||||
* - `statement`: the statment to execute when the event occurs.
|
||||
*
|
||||
*
|
||||
* When writing a directive event binding, you can also refer to the following local variables:
|
||||
* - `$event`: Current event object which triggerd the event.
|
||||
* - `$target`: The source of the event. This will be either a DOM element or an Angular directive.
|
||||
* [TO BE IMPLEMENTED]
|
||||
*
|
||||
*
|
||||
* ## Syntax
|
||||
@ -366,7 +378,7 @@ export class Directive extends Injectable {
|
||||
* ```
|
||||
* @Directive({
|
||||
* events: {
|
||||
* 'event1': 'onMethod1',
|
||||
* 'event1': 'onMethod1(arguments)',
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
@ -374,20 +386,24 @@ export class Directive extends Injectable {
|
||||
*
|
||||
* ## Basic Event Binding:
|
||||
*
|
||||
* Suppose you want to write a directive that triggers on `change` events in the DOM. You would define the event
|
||||
* binding as follows:
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
* selector: 'input',
|
||||
* events: {
|
||||
* 'change': 'onChange'
|
||||
* 'change': 'onChange($event)'
|
||||
* }
|
||||
* })
|
||||
* class InputDecorator {
|
||||
* onChange(event:Event) {
|
||||
* // invoked whenever the DOM element fires the 'change' event.
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Here `InputDecorator` is invoked whenever the DOM element fires the 'change' event.
|
||||
*
|
||||
*/
|
||||
events:any; // StringMap
|
||||
|
||||
@ -420,6 +436,8 @@ export class Directive extends Injectable {
|
||||
|
||||
/**
|
||||
* Returns true if a directive participates in a given [LifecycleEvent].
|
||||
*
|
||||
* See: [onChange], [onDestroy] for details.
|
||||
*/
|
||||
hasLifecycleHook(hook:string):boolean {
|
||||
return isPresent(this.lifecycle) ? ListWrapper.contains(this.lifecycle, hook) : false;
|
||||
@ -427,98 +445,87 @@ export class Directive extends Injectable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Components are angular directives with Shadow DOM views.
|
||||
* Declare template views for an Angular application.
|
||||
*
|
||||
* Componests are used to encapsulate state and template into reusable building blocks. An angular component requires
|
||||
* an `@Component` and at least one `@Template` annotation (see [Template] for more datails.) Components instances are
|
||||
* used as the context for evaluation of the Shadow DOM view.
|
||||
* Each angular component requires a single `@Component` and at least one `@Template` annotation. This allows Angular to
|
||||
* encapsulate state information and templates. These form the fundamental reusable building blocks for developing an
|
||||
* application. There can only be one component per DOM element.
|
||||
*
|
||||
* Restrictions:
|
||||
* - Thre can anly be one component per DOM element.
|
||||
* When a component is instantiated, Angular
|
||||
* - creates a shadow DOM for the component.
|
||||
* - loads the selected template into the shadow DOM.
|
||||
* - creates a child [Injector] which is configured with the [Component.services].
|
||||
*
|
||||
* All template expressions and statments are then evaluted against the component instance.
|
||||
*
|
||||
* For details on the `@Template` annotation, see [Template].
|
||||
*
|
||||
* ## Example
|
||||
* @Component({
|
||||
* selector: 'greet'
|
||||
* })
|
||||
* @Template({
|
||||
* inline: 'Hello {{name}}'
|
||||
* })
|
||||
* class Greet {
|
||||
* name: string;
|
||||
*
|
||||
* constructor() {
|
||||
* this.name = 'World';
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @Component({
|
||||
* selector: 'greet'
|
||||
* })
|
||||
* @Template({
|
||||
* inline: 'Hello {{name}}!'
|
||||
* })
|
||||
* class Greet {
|
||||
* name: string;
|
||||
*
|
||||
* constructor() {
|
||||
* this.name = 'World';
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @publicModule angular2/annotations
|
||||
*/
|
||||
export class Component extends Directive {
|
||||
/**
|
||||
* Defines the set of injectables that are visible to a Component and its children.
|
||||
* Defines the set of injectable objects that are visible to a Component and its children.
|
||||
*
|
||||
* When a [Component] defines [injectables], Angular creates a new application-level [Injector] for the component
|
||||
* and its children. Injectables are defined as a list of [Binding]s, (or as [Type]s as short hand). These bindings
|
||||
* are passed to the [Injector] constructor when making a new child [Injector]. The injectables are available for
|
||||
* all child directives of the Component (but not the declaring component's light DOM directives).
|
||||
* The [services] defined in the Component annotation allow you to configure a set of bindings for the component's
|
||||
* injector.
|
||||
*
|
||||
* ## Example
|
||||
* // Example of a class which we would like to inject.
|
||||
* class Greeter {
|
||||
* salutation:string;
|
||||
*
|
||||
* constructor(salutation:string) {
|
||||
* this.salutation = salutation;
|
||||
* }
|
||||
*
|
||||
* greet(name:string) {
|
||||
* return this.salutation + ' ' + name + '!';
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'greet',
|
||||
* services: [
|
||||
* bind(String).toValue('Hello'), // Configure injection of string
|
||||
* Greeter // Make Greeter available for injection
|
||||
* ]
|
||||
* })
|
||||
* @Template({
|
||||
* inline: '<child></child>',
|
||||
* directives: Child
|
||||
* })
|
||||
* class Greet {
|
||||
* greeter: Greeter;
|
||||
*
|
||||
* constructor(greeter: Greeter) {
|
||||
* // Greeter can be injected here becouse it was declared as injectable
|
||||
* // in this component, or parent component.
|
||||
* this.greeter = greeter;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @Decorator({
|
||||
* selector: 'child'
|
||||
* })
|
||||
* class Child {
|
||||
* greeter: Greeter;
|
||||
*
|
||||
* constructor(greeter: Greeter) {
|
||||
* // Greeter can be injected here becouse it was declared as injectable
|
||||
* // in a an ancestor component.
|
||||
* this.greeter = greeter;
|
||||
* }
|
||||
* }
|
||||
* When a component is instantiated, Angular creates a new child Injector, which is configured with the bindings in
|
||||
* the Component [services] annotation. The injectable objects then become available for injection to the component
|
||||
* itself and any of the directives in the component's template, i.e. they are not available to the directives which
|
||||
* are children in the component's light DOM.
|
||||
*
|
||||
*
|
||||
* Let's look at the [services] part of the example above.
|
||||
* The syntax for configuring the [services] injectable is identical to [Injector] injectable configuration. See
|
||||
* [Injector] for additional detail.
|
||||
*
|
||||
* services: [
|
||||
* bind(String).toValue('Hello'),
|
||||
* Greeter
|
||||
* ]
|
||||
*
|
||||
* Here the `Greeter` is a short hand for `bind(Greeter).toClass(Greeter)`. See [bind] DSL for more details.
|
||||
* ## Simple Example
|
||||
*
|
||||
* Here is an example of a class that can be injected:
|
||||
*
|
||||
* ```
|
||||
* class Greeter {
|
||||
* greet(name:string) {
|
||||
* return 'Hello ' + name + '!';
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'greet',
|
||||
* services: [
|
||||
* Greeter
|
||||
* ]
|
||||
* })
|
||||
* @Template({
|
||||
* inline: `{{greeter.greet('world')}}!`,
|
||||
* directives: Child
|
||||
* })
|
||||
* class HelloWorld {
|
||||
* greeter:Greeter;
|
||||
*
|
||||
* constructor(greeter:Greeter) {
|
||||
* this.greeter = greeter;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
services:List;
|
||||
|
||||
@ -549,27 +556,34 @@ export class Component extends Directive {
|
||||
}
|
||||
|
||||
/**
|
||||
* DynamicComponents allow loading child components impretivly.
|
||||
*
|
||||
* A Component can be made of other compontents. This recursive nature must be resolved synchronously during the
|
||||
* component template processing. This means that all templates are resolved synchronously. This prevents lazy loading
|
||||
* of code or delayed binding of views to the components.
|
||||
*
|
||||
* A DynamicComponent is a placeholder into which a regular component can be loaded imperativly and thus breaking
|
||||
* the all components must be resolved synchronously restriction. Once loaded the component is premanent.
|
||||
*
|
||||
*
|
||||
* Directive used for dynamically loading components.
|
||||
*
|
||||
* Regular angular components are statically resolved. DynamicComponent allows to you resolve a component at runtime
|
||||
* instead by providing a placeholder into which a regular angular component can be dynamically loaded. Once loaded,
|
||||
* the dynamically-loaded component becomes permanent and cannot be changed.
|
||||
*
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* Here we have `DynamicComp` which acts as the placeholder for `HelloCmp`. At runtime, the dynamic component
|
||||
* `DynamicComp` requests loading of the `HelloCmp` component.
|
||||
*
|
||||
* There is nothing special about `HelloCmp`, which is a regular angular component. It can also be used in other static
|
||||
* locations.
|
||||
*
|
||||
* ```
|
||||
* @DynamicComponent({
|
||||
* selector: 'dynamic-comp'
|
||||
* })
|
||||
* class DynamicComp {
|
||||
* done;
|
||||
* helloCmp:HelloCmp;
|
||||
* constructor(loader:PrivateComponentLoader, location:PrivateComponentLocation) {
|
||||
* this.done = loader.load(HelloCmp, location);
|
||||
* loader.load(HelloCmp, location).then((helloCmp) => {
|
||||
* this.helloCmp = helloCmp;
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'hello-cmp'
|
||||
* })
|
||||
@ -582,11 +596,17 @@ export class Component extends Directive {
|
||||
* this.greeting = "hello";
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* ```
|
||||
*
|
||||
*
|
||||
*
|
||||
* @publicModule angular2/annotations
|
||||
*/
|
||||
export class DynamicComponent extends Directive {
|
||||
/**
|
||||
* Same as [Component.services].
|
||||
*/
|
||||
// TODO(vsankin): Please extract into AbstractComponent
|
||||
services:any; //List;
|
||||
|
||||
@CONST()
|
||||
@ -615,26 +635,24 @@ export class DynamicComponent extends Directive {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorators allow attaching behavior to DOM elements in a composable manner.
|
||||
* Directive that attaches behavior to DOM elements.
|
||||
*
|
||||
* A decorator directive attaches behavior to a DOM element in a composable manner.
|
||||
* (see: http://en.wikipedia.org/wiki/Composition_over_inheritance)
|
||||
*
|
||||
* Decorators:
|
||||
* - are simplest form of [Directive]s.
|
||||
* - are besed used as compostinion pattern ()
|
||||
* - are best used as a composition pattern ()
|
||||
*
|
||||
* Decoraters differ from [Component]s in that they:
|
||||
* - can have any number of decorators per element
|
||||
* Decorators differ from [Component]s in that they:
|
||||
* - can have multiple decorators per element
|
||||
* - do not create their own evaluation context
|
||||
* - do not have template (and therefor do not create Shadow DOM)
|
||||
*
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* Let's say we would like to add tool-tip behavior to any alement.
|
||||
*
|
||||
* ```
|
||||
* <div tooltip="some text here"></div>
|
||||
* ```
|
||||
*
|
||||
* We could have a decorator directive like so:
|
||||
* Here we use a decorator directive to simply define basic tool-tip behavior.
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
@ -643,8 +661,8 @@ export class DynamicComponent extends Directive {
|
||||
* 'text': 'tooltip'
|
||||
* },
|
||||
* event: {
|
||||
* 'onmouseenter': 'onMouseEnter',
|
||||
* 'onmouseleave': 'onMouseLeave'
|
||||
* 'onmouseenter': 'onMouseEnter()',
|
||||
* 'onmouseleave': 'onMouseLeave()'
|
||||
* }
|
||||
* })
|
||||
* class Tooltip{
|
||||
@ -667,10 +685,23 @@ export class DynamicComponent extends Directive {
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* In our HTML template, we can then add this behavior to a `<div>` or any other element with the `tooltip` selector,
|
||||
* like so:
|
||||
*
|
||||
* ```
|
||||
* <div tooltip="some text here"></div>
|
||||
* ```
|
||||
*
|
||||
* @publicModule angular2/annotations
|
||||
*/
|
||||
export class Decorator extends Directive {
|
||||
|
||||
/**
|
||||
* If set to true the compiler does not compile the children of this directive.
|
||||
*/
|
||||
//TODO(vsavkin): This would better fall under the Macro directive concept.
|
||||
compileChildren: boolean;
|
||||
|
||||
@CONST()
|
||||
constructor({
|
||||
selector,
|
||||
@ -697,22 +728,45 @@ export class Decorator extends Directive {
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport is used for controlling the instatiation of inline templates.
|
||||
* Directive that controls the instantiation, destruction, and positioning of inline template elements.
|
||||
*
|
||||
* Viewport consist of a controller which can inject [ViewContainer]. A [ViewContainer] rerpsents a location in the
|
||||
* current view where child views can be inserted.
|
||||
* A viewport directive uses a [ViewContainer] to instantiate, insert, move, and destroy views at runtime.
|
||||
* The [ViewContainer] is created as a result of `<template>` element, and represents a location in the current view
|
||||
* where these actions are performed.
|
||||
*
|
||||
* ## Example
|
||||
* Views are always created as children of the current [View], and as siblings of the `<template>` element. Thus a
|
||||
* directive in a child view cannot inject the viewport directive that created it.
|
||||
*
|
||||
* Given folowing inline template, let's implement the `unless` behavior.
|
||||
* Since viewport directives are common in Angular, and using the full `<template>` element syntax is wordy, Angular
|
||||
* also supports a shorthand notation: `<li *foo="bar">` and `<li template="foo: bar">` are equivalent.
|
||||
*
|
||||
* Thus,
|
||||
*
|
||||
* ```
|
||||
* <ul>
|
||||
* <li *unless="expr"></li>
|
||||
* <li *foo="bar" title="text"></li>
|
||||
* </ul>
|
||||
* ```
|
||||
*
|
||||
* Can be implemented using:
|
||||
* Expands in use to:
|
||||
*
|
||||
* ```
|
||||
* <ul>
|
||||
* <template [foo]="bar">
|
||||
* <li title="text"></li>
|
||||
* </template>
|
||||
* </ul>
|
||||
* ```
|
||||
*
|
||||
* Notice that although the shorthand places `*foo="bar"` within the `<li>` element, the binding for the `Viewport`
|
||||
* controller is correctly instantiated on the `<template>` element rather than the `<li>` element.
|
||||
*
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* Let's suppose we want to implement the `unless` behavior, to conditionally include a template.
|
||||
*
|
||||
* Here is a simple viewport directive that triggers on an `unless` selector:
|
||||
*
|
||||
* ```
|
||||
* @Viewport({
|
||||
@ -721,7 +775,7 @@ export class Decorator extends Directive {
|
||||
* 'condition': 'unless'
|
||||
* }
|
||||
* })
|
||||
* export class If {
|
||||
* export class Unless {
|
||||
* viewContainer: ViewContainer;
|
||||
* prevCondition: boolean;
|
||||
*
|
||||
@ -742,6 +796,27 @@ export class Decorator extends Directive {
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* We can then use this `unless` selector in a template:
|
||||
* ```
|
||||
* <ul>
|
||||
* <li *unless="expr"></li>
|
||||
* </ul>
|
||||
* ```
|
||||
*
|
||||
* Once the viewport instantiates the child view, the shorthand notation for the template expands and the result is:
|
||||
*
|
||||
* ```
|
||||
* <ul>
|
||||
* <template [unless]="exp">
|
||||
* <li></li>
|
||||
* </template>
|
||||
* <li></li>
|
||||
* </ul>
|
||||
* ```
|
||||
*
|
||||
* Note also that although the `<li></li>` template still exists inside the `<template></template>`, the instantiated
|
||||
* view occurs on the second `<li></li>` which is a sibling to the `<template>` element.
|
||||
*
|
||||
*
|
||||
* @publicModule angular2/annotations
|
||||
*/
|
||||
@ -770,7 +845,7 @@ export class Viewport extends Directive {
|
||||
//TODO(misko): turn into LifecycleEvent class once we switch to TypeScript;
|
||||
|
||||
/**
|
||||
* Specify that a directive should be notified whenever a [View] that contains it is destroyed.
|
||||
* Notify a directive whenever a [View] that contains it is destroyed.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
@ -791,7 +866,7 @@ export const onDestroy = "onDestroy";
|
||||
|
||||
|
||||
/**
|
||||
* Specify that a directive should be notified when any of its bindings have changed.
|
||||
* Notify a directive when any of its bindings have changed.
|
||||
*
|
||||
* ## Example:
|
||||
*
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {ABSTRACT, CONST, Type} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
* @publicModule angular2/annotations
|
||||
*/
|
||||
export class Template {
|
||||
url:any; //string;
|
||||
|
@ -2,8 +2,40 @@ import {CONST} from 'angular2/src/facade/lang';
|
||||
import {DependencyAnnotation} from 'angular2/di';
|
||||
|
||||
/**
|
||||
* The directive can only be injected from the current element
|
||||
* or from its parent.
|
||||
* The directive can only be injected from the parent element.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```
|
||||
* <div dependency="1">
|
||||
* <div dependency="2" my-directive></div>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
* selector: '[dependency]',
|
||||
* bind: {
|
||||
* 'id':'dependency'
|
||||
* }
|
||||
* })
|
||||
* class Dependency {
|
||||
* id:string;
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @Decorator({
|
||||
* selector: '[my-directive]'
|
||||
* })
|
||||
* class Dependency {
|
||||
* constructor(@Parent() dependency:Dependency) {
|
||||
* expect(dependency.id).toEqual(1);
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* In the above example the `@Parent()` annotation forces the injector to retrieve the dependency from the
|
||||
* parent element (even thought the current element could resolve it).
|
||||
*
|
||||
* @publicModule angular2/annotations
|
||||
*/
|
||||
@ -15,8 +47,48 @@ export class Parent extends DependencyAnnotation {
|
||||
}
|
||||
|
||||
/**
|
||||
* The directive can only be injected from the current element
|
||||
* or from its ancestor.
|
||||
* The directive can only be injected from the ancestor (any element between parent element and shadow root).
|
||||
*
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```
|
||||
* <div dependency="1">
|
||||
* <div dependency="2">
|
||||
* <div>
|
||||
* <div dependency="3" my-directive></div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
* selector: '[dependency]',
|
||||
* bind: {
|
||||
* 'id':'dependency'
|
||||
* }
|
||||
* })
|
||||
* class Dependency {
|
||||
* id:string;
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @Decorator({
|
||||
* selector: '[my-directive]'
|
||||
* })
|
||||
* class Dependency {
|
||||
* constructor(@Ancestor() dependency:Dependency) {
|
||||
* expect(dependency.id).toEqual(2);
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* In the above example the `@Ancestor()` annotation forces the injector to retrieve the dependency from the
|
||||
* first ancestor.
|
||||
* - The current element `dependency="3"` is skipped
|
||||
* - Next parent has no directives `<div>`
|
||||
* - Next parent has the `Dependency` directive and so the dependency is satisfied.
|
||||
*
|
||||
* @publicModule angular2/annotations
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {isPresent, isBlank, BaseException, assertionsEnabled, RegExpWrapper} from 'angular2/src/facade/lang';
|
||||
import {List, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {List, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {SelectorMatcher} from '../selector';
|
||||
import {CssSelector} from '../selector';
|
||||
@ -10,9 +10,6 @@ import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
import {isSpecialProperty} from './element_binder_builder';
|
||||
import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
|
||||
|
||||
var PROPERTY_BINDING_REGEXP = RegExpWrapper.create('^ *([^\\s\\|]+)');
|
||||
|
||||
/**
|
||||
@ -39,8 +36,8 @@ export class DirectiveParser extends CompileStep {
|
||||
this._selectorMatcher = new SelectorMatcher();
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
var directiveMetadata = directives[i];
|
||||
selector=CssSelector.parse(directiveMetadata.annotation.selector);
|
||||
this._selectorMatcher.addSelectable(selector, directiveMetadata);
|
||||
selector = CssSelector.parse(directiveMetadata.annotation.selector);
|
||||
this._selectorMatcher.addSelectables(selector, directiveMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,53 +57,15 @@ export class DirectiveParser extends CompileStep {
|
||||
});
|
||||
|
||||
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
|
||||
// only be present on <template> elements any more!
|
||||
// only be present on <template> elements!
|
||||
var isTemplateElement = DOM.isTemplateElement(current.element);
|
||||
var matchedProperties; // StringMap - used in dev mode to store all properties that have been matched
|
||||
|
||||
this._selectorMatcher.match(cssSelector, (selector, directive) => {
|
||||
matchedProperties = updateMatchedProperties(matchedProperties, selector, directive);
|
||||
checkDirectiveValidity(directive, current, isTemplateElement);
|
||||
current.addDirective(directive);
|
||||
current.addDirective(checkDirectiveValidity(directive, current, isTemplateElement));
|
||||
});
|
||||
|
||||
// raise error if some directives are missing
|
||||
checkMissingDirectives(current, matchedProperties, isTemplateElement);
|
||||
}
|
||||
}
|
||||
|
||||
// calculate all the properties that are used or interpreted by all directives
|
||||
// those properties correspond to the directive selectors and the directive bindings
|
||||
function updateMatchedProperties(matchedProperties, selector, directive) {
|
||||
if (assertionsEnabled()) {
|
||||
var attrs = selector.attrs;
|
||||
if (!isPresent(matchedProperties)) {
|
||||
matchedProperties = StringMapWrapper.create();
|
||||
}
|
||||
if (isPresent(attrs)) {
|
||||
for (var idx = 0; idx<attrs.length; idx+=2) {
|
||||
// attribute name is stored on even indexes
|
||||
StringMapWrapper.set(matchedProperties, dashCaseToCamelCase(attrs[idx]), true);
|
||||
}
|
||||
}
|
||||
// some properties can be used by the directive, so we need to register them
|
||||
if (isPresent(directive.annotation) && isPresent(directive.annotation.bind)) {
|
||||
var bindMap = directive.annotation.bind;
|
||||
StringMapWrapper.forEach(bindMap, (value, key) => {
|
||||
// value is the name of the property that is interpreted
|
||||
// e.g. 'myprop' or 'myprop | double' when a pipe is used to transform the property
|
||||
|
||||
// keep the property name and remove the pipe
|
||||
var bindProp = RegExpWrapper.firstMatch(PROPERTY_BINDING_REGEXP, value);
|
||||
if (isPresent(bindProp) && isPresent(bindProp[1])) {
|
||||
StringMapWrapper.set(matchedProperties, dashCaseToCamelCase(bindProp[1]), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return matchedProperties;
|
||||
}
|
||||
|
||||
// check if the directive is compatible with the current element
|
||||
function checkDirectiveValidity(directive, current, isTemplateElement) {
|
||||
var isComponent = directive.annotation instanceof Component || directive.annotation instanceof DynamicComponent;
|
||||
@ -125,26 +84,6 @@ function checkDirectiveValidity(directive, current, isTemplateElement) {
|
||||
} else if (isComponent && alreadyHasComponent) {
|
||||
throw new BaseException(`Multiple component directives not allowed on the same element - check ${current.elementDescription}`);
|
||||
}
|
||||
}
|
||||
|
||||
// validates that there is no missing directive - dev mode only
|
||||
function checkMissingDirectives(current, matchedProperties, isTemplateElement) {
|
||||
if (assertionsEnabled()) {
|
||||
var ppBindings=current.propertyBindings;
|
||||
if (isPresent(ppBindings)) {
|
||||
// check that each property corresponds to a real property or has been matched by a directive
|
||||
MapWrapper.forEach(ppBindings, (expression, prop) => {
|
||||
if (!DOM.hasProperty(current.element, prop) && !isSpecialProperty(prop)) {
|
||||
if (!isPresent(matchedProperties) || !isPresent(StringMapWrapper.get(matchedProperties, prop))) {
|
||||
throw new BaseException(`Missing directive to handle '${camelCaseToDashCase(prop)}' in ${current.elementDescription}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// template only store directives as attribute when they are not bound to expressions
|
||||
// so we have to validate the expression case too (e.g. !if="condition")
|
||||
if (isTemplateElement && !current.isViewRoot && !isPresent(current.viewportDirective)) {
|
||||
throw new BaseException(`Missing directive to handle: ${current.elementDescription}`);
|
||||
}
|
||||
}
|
||||
return directive;
|
||||
}
|
||||
|
@ -15,23 +15,34 @@ import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
|
||||
|
||||
var DOT_REGEXP = RegExpWrapper.create('\\.');
|
||||
|
||||
const ARIA_PREFIX = 'aria';
|
||||
var ariaSettersCache = StringMapWrapper.create();
|
||||
const ATTRIBUTE_PREFIX = 'attr.';
|
||||
var attributeSettersCache = StringMapWrapper.create();
|
||||
|
||||
function ariaSetterFactory(attrName:string) {
|
||||
var setterFn = StringMapWrapper.get(ariaSettersCache, attrName);
|
||||
var ariaAttrName;
|
||||
function _isValidAttributeValue(attrName:string, value: any) {
|
||||
if (attrName == "role") {
|
||||
return isString(value);
|
||||
} else {
|
||||
return isPresent(value);
|
||||
}
|
||||
}
|
||||
|
||||
function attributeSetterFactory(attrName:string) {
|
||||
var setterFn = StringMapWrapper.get(attributeSettersCache, attrName);
|
||||
var dashCasedAttributeName;
|
||||
|
||||
if (isBlank(setterFn)) {
|
||||
ariaAttrName = camelCaseToDashCase(attrName);
|
||||
dashCasedAttributeName = camelCaseToDashCase(attrName);
|
||||
setterFn = function(element, value) {
|
||||
if (isPresent(value)) {
|
||||
DOM.setAttribute(element, ariaAttrName, stringify(value));
|
||||
if (_isValidAttributeValue(dashCasedAttributeName, value)) {
|
||||
DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
|
||||
} else {
|
||||
DOM.removeAttribute(element, ariaAttrName);
|
||||
DOM.removeAttribute(element, dashCasedAttributeName);
|
||||
if (isPresent(value)) {
|
||||
throw new BaseException("Invalid " + dashCasedAttributeName + " attribute, only string values are allowed, got '" + stringify(value) + "'");
|
||||
}
|
||||
}
|
||||
};
|
||||
StringMapWrapper.set(ariaSettersCache, attrName, setterFn);
|
||||
StringMapWrapper.set(attributeSettersCache, attrName, setterFn);
|
||||
}
|
||||
|
||||
return setterFn;
|
||||
@ -82,25 +93,6 @@ function styleSetterFactory(styleName:string, stylesuffix:string) {
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
const ROLE_ATTR = 'role';
|
||||
function roleSetter(element, value) {
|
||||
if (isString(value)) {
|
||||
DOM.setAttribute(element, ROLE_ATTR, value);
|
||||
} else {
|
||||
DOM.removeAttribute(element, ROLE_ATTR);
|
||||
if (isPresent(value)) {
|
||||
throw new BaseException("Invalid role attribute, only string values are allowed, got '" + stringify(value) + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tells if an attribute is handled by the ElementBinderBuilder step
|
||||
export function isSpecialProperty(propName:string) {
|
||||
return StringWrapper.startsWith(propName, ARIA_PREFIX)
|
||||
|| StringWrapper.startsWith(propName, CLASS_PREFIX)
|
||||
|| StringWrapper.startsWith(propName, STYLE_PREFIX)
|
||||
|| StringMapWrapper.contains(DOM.attrToPropMap, propName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the ElementBinders and adds watches to the
|
||||
@ -188,29 +180,27 @@ export class ElementBinderBuilder extends CompileStep {
|
||||
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
|
||||
var setterFn, styleParts, styleSuffix;
|
||||
|
||||
if (StringWrapper.startsWith(property, ARIA_PREFIX)) {
|
||||
setterFn = ariaSetterFactory(property);
|
||||
} else if (StringWrapper.equals(property, ROLE_ATTR)) {
|
||||
setterFn = roleSetter;
|
||||
if (StringWrapper.startsWith(property, ATTRIBUTE_PREFIX)) {
|
||||
setterFn = attributeSetterFactory(StringWrapper.substring(property, ATTRIBUTE_PREFIX.length));
|
||||
} else if (StringWrapper.startsWith(property, CLASS_PREFIX)) {
|
||||
setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length));
|
||||
} else if (StringWrapper.startsWith(property, STYLE_PREFIX)) {
|
||||
styleParts = StringWrapper.split(property, DOT_REGEXP);
|
||||
styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : '';
|
||||
setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix);
|
||||
} else if (StringWrapper.equals(property, 'innerHtml')) {
|
||||
setterFn = (element, value) => DOM.setInnerHTML(element, value);
|
||||
} else {
|
||||
property = this._resolvePropertyName(property);
|
||||
//TODO(pk): special casing innerHtml, see: https://github.com/angular/angular/issues/789
|
||||
if (StringWrapper.equals(property, 'innerHTML')) {
|
||||
setterFn = (element, value) => DOM.setInnerHTML(element, value);
|
||||
} else if (DOM.hasProperty(compileElement.element, property) || StringWrapper.equals(property, 'innerHtml')) {
|
||||
setterFn = reflector.setter(property);
|
||||
var propertySetterFn = reflector.setter(property);
|
||||
setterFn = function(receiver, value) {
|
||||
if (DOM.hasProperty(receiver, property)) {
|
||||
return propertySetterFn(receiver, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isPresent(setterFn)) {
|
||||
protoView.bindElementProperty(expression.ast, property, setterFn);
|
||||
}
|
||||
protoView.bindElementProperty(expression.ast, property, setterFn);
|
||||
});
|
||||
}
|
||||
|
||||
|
71
modules/angular2/src/core/compiler/selector.js
vendored
71
modules/angular2/src/core/compiler/selector.js
vendored
@ -9,7 +9,9 @@ var _SELECTOR_REGEXP =
|
||||
RegExpWrapper.create('(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])'); // "[name]", "[name=value]" or "[name*=value]"
|
||||
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]"
|
||||
'(?:\\))|' + // ")"
|
||||
'(\\s*,\\s*)'); // ","
|
||||
|
||||
/**
|
||||
* A css selector contains an element name,
|
||||
@ -21,7 +23,15 @@ export class CssSelector {
|
||||
classNames:List;
|
||||
attrs:List;
|
||||
notSelector: CssSelector;
|
||||
static parse(selector:string): CssSelector {
|
||||
static parse(selector:string): List<CssSelector> {
|
||||
var results = ListWrapper.create();
|
||||
var _addResult = (res, cssSel) => {
|
||||
if (isPresent(cssSel.notSelector) && isBlank(cssSel.element)
|
||||
&& ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
|
||||
cssSel.element = "*";
|
||||
}
|
||||
ListWrapper.push(res, cssSel);
|
||||
}
|
||||
var cssSelector = new CssSelector();
|
||||
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
|
||||
var match;
|
||||
@ -43,13 +53,13 @@ export class CssSelector {
|
||||
if (isPresent(match[4])) {
|
||||
current.addAttribute(match[4], match[5]);
|
||||
}
|
||||
if (isPresent(match[6])) {
|
||||
_addResult(results, cssSelector);
|
||||
cssSelector = current = new CssSelector();
|
||||
}
|
||||
}
|
||||
if (isPresent(cssSelector.notSelector) && isBlank(cssSelector.element)
|
||||
&& ListWrapper.isEmpty(cssSelector.classNames) && ListWrapper.isEmpty(cssSelector.attrs)) {
|
||||
cssSelector.element = "*";
|
||||
}
|
||||
|
||||
return cssSelector;
|
||||
_addResult(results, cssSelector);
|
||||
return results;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
@ -119,6 +129,7 @@ export class SelectorMatcher {
|
||||
_classPartialMap:Map;
|
||||
_attrValueMap:Map;
|
||||
_attrValuePartialMap:Map;
|
||||
_listContexts:List;
|
||||
constructor() {
|
||||
this._elementMap = MapWrapper.create();
|
||||
this._elementPartialMap = MapWrapper.create();
|
||||
@ -128,6 +139,19 @@ export class SelectorMatcher {
|
||||
|
||||
this._attrValueMap = MapWrapper.create();
|
||||
this._attrValuePartialMap = MapWrapper.create();
|
||||
|
||||
this._listContexts = ListWrapper.create();
|
||||
}
|
||||
|
||||
addSelectables(cssSelectors:List<CssSelector>, callbackCtxt) {
|
||||
var listContext = null;
|
||||
if (cssSelectors.length > 1) {
|
||||
listContext= new SelectorListContext(cssSelectors);
|
||||
ListWrapper.push(this._listContexts, listContext);
|
||||
}
|
||||
for (var i = 0; i < cssSelectors.length; i++) {
|
||||
this.addSelectable(cssSelectors[i], callbackCtxt, listContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -135,12 +159,12 @@ export class SelectorMatcher {
|
||||
* @param cssSelector A css selector
|
||||
* @param callbackCtxt An opaque object that will be given to the callback of the `match` function
|
||||
*/
|
||||
addSelectable(cssSelector:CssSelector, callbackCtxt) {
|
||||
addSelectable(cssSelector, callbackCtxt, listContext: SelectorListContext) {
|
||||
var matcher = this;
|
||||
var element = cssSelector.element;
|
||||
var classNames = cssSelector.classNames;
|
||||
var attrs = cssSelector.attrs;
|
||||
var selectable = new SelectorContext(cssSelector, callbackCtxt);
|
||||
var selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
|
||||
|
||||
|
||||
if (isPresent(element)) {
|
||||
@ -215,6 +239,10 @@ export class SelectorMatcher {
|
||||
var classNames = cssSelector.classNames;
|
||||
var attrs = cssSelector.attrs;
|
||||
|
||||
for (var i = 0; i < this._listContexts.length; i++) {
|
||||
this._listContexts[i].alreadyMatched = false;
|
||||
}
|
||||
|
||||
result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
|
||||
result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) || result;
|
||||
|
||||
@ -282,26 +310,41 @@ export class SelectorMatcher {
|
||||
}
|
||||
|
||||
|
||||
class SelectorListContext {
|
||||
selectors: List<CssSelector>;
|
||||
alreadyMatched: boolean;
|
||||
|
||||
constructor(selectors:List<CssSelector>) {
|
||||
this.selectors = selectors;
|
||||
this.alreadyMatched = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Store context to pass back selector and context when a selector is matched
|
||||
class SelectorContext {
|
||||
selector:CssSelector;
|
||||
notSelector:CssSelector;
|
||||
cbContext; // callback context
|
||||
listContext: SelectorListContext;
|
||||
|
||||
constructor(selector:CssSelector, cbContext) {
|
||||
constructor(selector:CssSelector, cbContext, listContext: SelectorListContext) {
|
||||
this.selector = selector;
|
||||
this.notSelector = selector.notSelector;
|
||||
this.cbContext = cbContext;
|
||||
this.listContext = listContext;
|
||||
}
|
||||
|
||||
finalize(cssSelector: CssSelector, callback) {
|
||||
var result = true;
|
||||
if (isPresent(this.notSelector)) {
|
||||
if (isPresent(this.notSelector) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
|
||||
var notMatcher = new SelectorMatcher();
|
||||
notMatcher.addSelectable(this.notSelector, null);
|
||||
notMatcher.addSelectable(this.notSelector, null, null);
|
||||
result = !notMatcher.match(cssSelector, null);
|
||||
}
|
||||
if (result && isPresent(callback)) {
|
||||
if (result && isPresent(callback) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
|
||||
if (isPresent(this.listContext)) {
|
||||
this.listContext.alreadyMatched = true;
|
||||
}
|
||||
callback(this.selector, this.cbContext);
|
||||
}
|
||||
return result;
|
||||
|
@ -512,11 +512,13 @@ var _cssColonHostRe = RegExpWrapper.create('(' + _polyfillHost + _parenSuffix, '
|
||||
var _cssColonHostContextRe = RegExpWrapper.create('(' + _polyfillHostContext + _parenSuffix, 'im');
|
||||
var _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
|
||||
var _shadowDOMSelectorsRe = [
|
||||
RegExpWrapper.create('/shadow/'),
|
||||
RegExpWrapper.create('/shadow-deep/'),
|
||||
RegExpWrapper.create('>>>'),
|
||||
RegExpWrapper.create('::shadow'),
|
||||
RegExpWrapper.create('/deep/'),
|
||||
RegExpWrapper.create('::content'),
|
||||
// Deprecated selectors
|
||||
RegExpWrapper.create('/deep/'), // former >>>
|
||||
RegExpWrapper.create('/shadow-deep/'), // former /deep/
|
||||
RegExpWrapper.create('/shadow/'), // former ::shadow
|
||||
];
|
||||
var _selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$';
|
||||
var _polyfillHostRe = RegExpWrapper.create(_polyfillHost, 'im');
|
||||
|
2
modules/angular2/src/core/compiler/view.js
vendored
2
modules/angular2/src/core/compiler/view.js
vendored
@ -449,7 +449,7 @@ export class ProtoView {
|
||||
lightDom = strategy.constructLightDom(view, childView, element);
|
||||
strategy.attachTemplate(element, childView);
|
||||
|
||||
bindingPropagationConfig = new BindingPropagationConfig(changeDetector);
|
||||
bindingPropagationConfig = new BindingPropagationConfig(childView.changeDetector);
|
||||
|
||||
ListWrapper.push(componentChildViews, childView);
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ export class ViewContainer {
|
||||
var detachedView = this.get(atIndex);
|
||||
ListWrapper.removeAt(this._views, atIndex);
|
||||
if (isBlank(this._lightDom)) {
|
||||
ViewContainer.removeViewNodesFromParent(this.templateElement.parentNode, detachedView);
|
||||
ViewContainer.removeViewNodes(detachedView);
|
||||
} else {
|
||||
this._lightDom.redistribute();
|
||||
}
|
||||
@ -173,8 +173,11 @@ export class ViewContainer {
|
||||
}
|
||||
}
|
||||
|
||||
static removeViewNodesFromParent(parent, view) {
|
||||
for (var i = view.nodes.length - 1; i >= 0; --i) {
|
||||
static removeViewNodes(view) {
|
||||
var len = view.nodes.length;
|
||||
if (len == 0) return;
|
||||
var parent = view.nodes[0].parentNode;
|
||||
for (var i = len - 1; i >= 0; --i) {
|
||||
DOM.removeChild(parent, view.nodes[i]);
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,12 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
@Viewport({
|
||||
selector: '[foreach][in]',
|
||||
selector: '[for][of]',
|
||||
bind: {
|
||||
'iterableChanges': 'in | iterableDiff'
|
||||
'iterableChanges': 'of | iterableDiff'
|
||||
}
|
||||
})
|
||||
export class Foreach {
|
||||
export class For {
|
||||
viewContainer: ViewContainer;
|
||||
constructor(viewContainer:ViewContainer) {
|
||||
super();
|
||||
@ -34,13 +34,13 @@ export class Foreach {
|
||||
(movedRecord) => ListWrapper.push(recordViewTuples, new RecordViewTuple(movedRecord, null))
|
||||
);
|
||||
|
||||
var insertTuples = Foreach.bulkRemove(recordViewTuples, this.viewContainer);
|
||||
var insertTuples = For.bulkRemove(recordViewTuples, this.viewContainer);
|
||||
|
||||
changes.forEachAddedItem(
|
||||
(addedRecord) => ListWrapper.push(insertTuples, new RecordViewTuple(addedRecord, null))
|
||||
);
|
||||
|
||||
Foreach.bulkInsert(insertTuples, this.viewContainer);
|
||||
For.bulkInsert(insertTuples, this.viewContainer);
|
||||
|
||||
for (var i = 0; i < insertTuples.length; i++) {
|
||||
this.perViewChange(insertTuples[i].view, insertTuples[i].record);
|
@ -52,7 +52,7 @@ export class Parse5DomAdapter extends DomAdapter {
|
||||
}
|
||||
};
|
||||
var matcher = new SelectorMatcher();
|
||||
matcher.addSelectable(CssSelector.parse(selector));
|
||||
matcher.addSelectables(CssSelector.parse(selector));
|
||||
_recursive(res, el, selector, matcher);
|
||||
return res;
|
||||
}
|
||||
@ -64,7 +64,7 @@ export class Parse5DomAdapter extends DomAdapter {
|
||||
var result = false;
|
||||
if (matcher == null) {
|
||||
matcher = new SelectorMatcher();
|
||||
matcher.addSelectable(CssSelector.parse(selector));
|
||||
matcher.addSelectables(CssSelector.parse(selector));
|
||||
}
|
||||
|
||||
var cssSelector = new CssSelector();
|
||||
|
109
modules/angular2/src/forms/directives.js
vendored
109
modules/angular2/src/forms/directives.js
vendored
@ -1,58 +1,57 @@
|
||||
import {Template, Component, Decorator, NgElement, Ancestor, onChange} from 'angular2/angular2';
|
||||
import {Template, Component, Decorator, Ancestor, onChange, PropertySetter} from 'angular2/angular2';
|
||||
import {Optional} from 'angular2/di';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {isBlank, isPresent, isString, CONST} from 'angular2/src/facade/lang';
|
||||
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {ControlGroup, Control} from './model';
|
||||
import * as validators from './validators';
|
||||
import {Validators} from './validators';
|
||||
|
||||
@CONST()
|
||||
export class ControlValueAccessor {
|
||||
readValue(el){}
|
||||
writeValue(el, value):void {}
|
||||
}
|
||||
//export interface ControlValueAccessor {
|
||||
// writeValue(value):void{}
|
||||
// set onChange(fn){}
|
||||
//}
|
||||
|
||||
@CONST()
|
||||
class DefaultControlValueAccessor extends ControlValueAccessor {
|
||||
constructor() {
|
||||
@Decorator({
|
||||
selector: '[control]',
|
||||
events: {
|
||||
'change' : 'onChange($event.target.value)',
|
||||
'input' : 'onChange($event.target.value)'
|
||||
}
|
||||
})
|
||||
export class DefaultValueAccessor {
|
||||
_setValueProperty:Function;
|
||||
onChange:Function;
|
||||
|
||||
constructor(@PropertySetter('value') setValueProperty:Function) {
|
||||
super();
|
||||
this._setValueProperty = setValueProperty;
|
||||
this.onChange = (_) => {};
|
||||
}
|
||||
|
||||
readValue(el) {
|
||||
return DOM.getValue(el);
|
||||
}
|
||||
|
||||
writeValue(el, value):void {
|
||||
DOM.setValue(el,value);
|
||||
writeValue(value) {
|
||||
this._setValueProperty(value);
|
||||
}
|
||||
}
|
||||
|
||||
@CONST()
|
||||
class CheckboxControlValueAccessor extends ControlValueAccessor {
|
||||
constructor() {
|
||||
@Decorator({
|
||||
selector: 'input[type=checkbox]', //should be input[type=checkbox][control]
|
||||
// change the selector once https://github.com/angular/angular/issues/1025 is fixed
|
||||
events: {
|
||||
'change' : 'onChange($event.target.checked)'
|
||||
}
|
||||
})
|
||||
export class CheckboxControlValueAccessor {
|
||||
_setCheckedProperty:Function;
|
||||
onChange:Function;
|
||||
|
||||
constructor(cd:ControlDirective, @PropertySetter('checked') setCheckedProperty:Function) {
|
||||
super();
|
||||
this._setCheckedProperty = setCheckedProperty;
|
||||
this.onChange = (_) => {};
|
||||
cd.valueAccessor = this; //ControlDirective should inject CheckboxControlDirective
|
||||
}
|
||||
|
||||
readValue(el):boolean {
|
||||
return DOM.getChecked(el);
|
||||
}
|
||||
|
||||
writeValue(el, value:boolean):void {
|
||||
DOM.setChecked(el, value);
|
||||
}
|
||||
}
|
||||
|
||||
var controlValueAccessors = {
|
||||
"checkbox" : new CheckboxControlValueAccessor(),
|
||||
"text" : new DefaultControlValueAccessor()
|
||||
};
|
||||
|
||||
function controlValueAccessorFor(controlType:string):ControlValueAccessor {
|
||||
var accessor = StringMapWrapper.get(controlValueAccessors, controlType);
|
||||
if (isPresent(accessor)) {
|
||||
return accessor;
|
||||
} else {
|
||||
return StringMapWrapper.get(controlValueAccessors, "text");
|
||||
writeValue(value) {
|
||||
this._setCheckedProperty(value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,26 +59,22 @@ function controlValueAccessorFor(controlType:string):ControlValueAccessor {
|
||||
lifecycle: [onChange],
|
||||
selector: '[control]',
|
||||
bind: {
|
||||
'controlName' : 'control',
|
||||
'type' : 'type'
|
||||
'controlName' : 'control'
|
||||
}
|
||||
})
|
||||
export class ControlDirective {
|
||||
_groupDirective:ControlGroupDirective;
|
||||
_el:NgElement;
|
||||
|
||||
controlName:string;
|
||||
type:string;
|
||||
valueAccessor:ControlValueAccessor;
|
||||
valueAccessor:any; //ControlValueAccessor
|
||||
|
||||
validator:Function;
|
||||
|
||||
constructor(@Ancestor() groupDirective:ControlGroupDirective, el:NgElement) {
|
||||
constructor(@Ancestor() groupDirective:ControlGroupDirective, valueAccessor:DefaultValueAccessor) {
|
||||
this._groupDirective = groupDirective;
|
||||
this._el = el;
|
||||
this.controlName = null;
|
||||
this.type = null;
|
||||
this.validator = validators.nullValidator;
|
||||
this.valueAccessor = valueAccessor;
|
||||
this.validator = Validators.nullValidator;
|
||||
}
|
||||
|
||||
// TODO: vsavkin this should be moved into the constructor once static bindings
|
||||
@ -92,22 +87,18 @@ export class ControlDirective {
|
||||
this._groupDirective.addDirective(this);
|
||||
|
||||
var c = this._control();
|
||||
c.validator = validators.compose([c.validator, this.validator]);
|
||||
|
||||
if (isBlank(this.valueAccessor)) {
|
||||
this.valueAccessor = controlValueAccessorFor(this.type);
|
||||
}
|
||||
c.validator = Validators.compose([c.validator, this.validator]);
|
||||
|
||||
this._updateDomValue();
|
||||
DOM.on(this._el.domElement, "change", (_) => this._updateControlValue());
|
||||
this._setUpUpdateControlValue();
|
||||
}
|
||||
|
||||
_updateDomValue() {
|
||||
this.valueAccessor.writeValue(this._el.domElement, this._control().value);
|
||||
this.valueAccessor.writeValue(this._control().value);
|
||||
}
|
||||
|
||||
_updateControlValue() {
|
||||
this._control().updateValue(this.valueAccessor.readValue(this._el.domElement));
|
||||
_setUpUpdateControlValue() {
|
||||
this.valueAccessor.onChange = (newValue) => this._control().updateValue(newValue);
|
||||
}
|
||||
|
||||
_control() {
|
||||
@ -165,5 +156,5 @@ export class ControlGroupDirective {
|
||||
}
|
||||
|
||||
export var FormDirectives = [
|
||||
ControlGroupDirective, ControlDirective
|
||||
ControlGroupDirective, ControlDirective, CheckboxControlValueAccessor, DefaultValueAccessor
|
||||
];
|
||||
|
8
modules/angular2/src/forms/model.js
vendored
8
modules/angular2/src/forms/model.js
vendored
@ -1,6 +1,6 @@
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {nullValidator, controlGroupValidator} from './validators';
|
||||
import {Validators} from './validators';
|
||||
|
||||
export const VALID = "VALID";
|
||||
export const INVALID = "INVALID";
|
||||
@ -26,7 +26,7 @@ export class AbstractControl {
|
||||
_parent:ControlGroup;
|
||||
validator:Function;
|
||||
|
||||
constructor(validator:Function = nullValidator) {
|
||||
constructor(validator:Function) {
|
||||
this.validator = validator;
|
||||
this._updateNeeded = true;
|
||||
this._pristine = true;
|
||||
@ -76,7 +76,7 @@ export class AbstractControl {
|
||||
}
|
||||
|
||||
export class Control extends AbstractControl {
|
||||
constructor(value:any, validator:Function = nullValidator) {
|
||||
constructor(value:any, validator:Function = Validators.nullValidator) {
|
||||
super(validator);
|
||||
this._value = value;
|
||||
}
|
||||
@ -101,7 +101,7 @@ export class ControlGroup extends AbstractControl {
|
||||
controls;
|
||||
optionals;
|
||||
|
||||
constructor(controls, optionals = null, validator:Function = controlGroupValidator) {
|
||||
constructor(controls, optionals = null, validator:Function = Validators.group) {
|
||||
super(validator);
|
||||
this.controls = controls;
|
||||
this.optionals = isPresent(optionals) ? optionals : {};
|
||||
|
@ -1,13 +1,12 @@
|
||||
import {Decorator} from 'angular2/angular2';
|
||||
|
||||
import {ControlDirective} from 'angular2/forms';
|
||||
import * as validators from 'angular2/forms';
|
||||
import {ControlDirective, Validators} from 'angular2/forms';
|
||||
|
||||
@Decorator({
|
||||
selector: '[required]'
|
||||
})
|
||||
export class RequiredValidatorDirective {
|
||||
constructor(c:ControlDirective) {
|
||||
c.validator = validators.compose([c.validator, validators.required]);
|
||||
c.validator = Validators.compose([c.validator, Validators.required]);
|
||||
}
|
||||
}
|
56
modules/angular2/src/forms/validators.js
vendored
56
modules/angular2/src/forms/validators.js
vendored
@ -3,35 +3,37 @@ import {List, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collectio
|
||||
|
||||
import * as modelModule from './model';
|
||||
|
||||
export function required(c:modelModule.Control) {
|
||||
return isBlank(c.value) || c.value == "" ? {"required" : true} : null;
|
||||
}
|
||||
export class Validators {
|
||||
static required(c:modelModule.Control) {
|
||||
return isBlank(c.value) || c.value == "" ? {"required": true} : null;
|
||||
}
|
||||
|
||||
export function nullValidator(c:modelModule.Control) {
|
||||
return null;
|
||||
}
|
||||
static nullValidator(c:modelModule.Control) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function compose(validators:List<Function>):Function {
|
||||
return function(c:modelModule.Control) {
|
||||
var res = ListWrapper.reduce(validators, (res, validator) => {
|
||||
var errors = validator(c);
|
||||
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
|
||||
}, {});
|
||||
static compose(validators:List<Function>):Function {
|
||||
return function (c:modelModule.Control) {
|
||||
var res = ListWrapper.reduce(validators, (res, validator) => {
|
||||
var errors = validator(c);
|
||||
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
|
||||
}, {});
|
||||
return StringMapWrapper.isEmpty(res) ? null : res;
|
||||
}
|
||||
}
|
||||
|
||||
static group(c:modelModule.ControlGroup) {
|
||||
var res = {};
|
||||
StringMapWrapper.forEach(c.controls, (control, name) => {
|
||||
if (c.contains(name) && isPresent(control.errors)) {
|
||||
StringMapWrapper.forEach(control.errors, (value, error) => {
|
||||
if (!StringMapWrapper.contains(res, error)) {
|
||||
res[error] = [];
|
||||
}
|
||||
ListWrapper.push(res[error], control);
|
||||
});
|
||||
}
|
||||
});
|
||||
return StringMapWrapper.isEmpty(res) ? null : res;
|
||||
}
|
||||
}
|
||||
|
||||
export function controlGroupValidator(c:modelModule.ControlGroup) {
|
||||
var res = {};
|
||||
StringMapWrapper.forEach(c.controls, (control, name) => {
|
||||
if (c.contains(name) && isPresent(control.errors)) {
|
||||
StringMapWrapper.forEach(control.errors, (value, error) => {
|
||||
if (! StringMapWrapper.contains(res, error)) {
|
||||
res[error] = [];
|
||||
}
|
||||
ListWrapper.push(res[error], control);
|
||||
});
|
||||
}
|
||||
});
|
||||
return StringMapWrapper.isEmpty(res) ? null : res;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.bind_generator.generator;
|
||||
library angular2.transform.bind_generator.generator;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.bind_generator.transformer;
|
||||
library angular2.transform.bind_generator.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.bind_generator.visitor;
|
||||
library angular2.transform.bind_generator.visitor;
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.common.asset_reader;
|
||||
library angular2.transform.common.asset_reader;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.common.formatter;
|
||||
library angular2.transform.common.formatter;
|
||||
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.common.logging;
|
||||
library angular2.transform.common.logging;
|
||||
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:code_transformers/messages/build_logger.dart';
|
||||
@ -10,6 +10,11 @@ void init(Transform t) {
|
||||
_logger = new BuildLogger(t);
|
||||
}
|
||||
|
||||
/// Sets [logger] directly. Used for testing - in general use [init].
|
||||
void setLogger(BuildLogger logger) {
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// The logger the transformer should use for messaging.
|
||||
BuildLogger get logger {
|
||||
if (_logger == null) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.common.names;
|
||||
library angular2.transform.common.names;
|
||||
|
||||
const SETUP_METHOD_NAME = 'setupReflection';
|
||||
const REFLECTOR_VAR_NAME = 'reflector';
|
||||
|
@ -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()}]';
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.common.options;
|
||||
library angular2.transform.common.options;
|
||||
|
||||
const ENTRY_POINT_PARAM = 'entry_point';
|
||||
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_point';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.common.parser;
|
||||
library angular2.transform.common.parser;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.common.registered_type;
|
||||
library angular2.transform.common.registered_type;
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.common;
|
||||
library angular2.transform.common;
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:analyzer/src/generated/java_core.dart';
|
||||
|
@ -1,35 +1,45 @@
|
||||
library angular2.src.transform.directive_linker.linker;
|
||||
library angular2.transform.directive_linker.linker;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/ngdata.dart';
|
||||
import 'package:angular2/src/transform/common/parser.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:code_transformers/assets.dart';
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
Future<String> linkNgDeps(Transform transform, String code, String path) async {
|
||||
var commentIdx = code.lastIndexOf('//');
|
||||
if (commentIdx < 0) return code;
|
||||
Future<String> linkNgDeps(AssetReader reader, AssetId entryPoint) async {
|
||||
var parser = new Parser(reader);
|
||||
NgDeps ngDeps = await parser.parse(entryPoint);
|
||||
|
||||
var ngData = new NgData.fromJson(code.substring(commentIdx + 2));
|
||||
if (ngDeps == null) return null;
|
||||
if (ngDeps.imports.isEmpty) return ngDeps.code;
|
||||
|
||||
StringBuffer importBuf =
|
||||
new StringBuffer(code.substring(0, ngData.importOffset));
|
||||
StringBuffer declarationBuf = new StringBuffer(
|
||||
code.substring(ngData.importOffset, ngData.registerOffset));
|
||||
String tail = code.substring(ngData.registerOffset, commentIdx);
|
||||
var allDeps = ngDeps.imports.toList()..addAll(ngDeps.exports);
|
||||
var depList = await _processNgImports(
|
||||
reader, entryPoint, allDeps.map((node) => node.uri.stringValue));
|
||||
|
||||
var ngDeps = await _processNgImports(transform, ngData.imports);
|
||||
if (depList.isEmpty) return ngDeps.code;
|
||||
|
||||
for (var i = 0; i < ngDeps.length; ++i) {
|
||||
importBuf.write('import \'${ngDeps[i]}\' as i${i};');
|
||||
var importBuf = new StringBuffer();
|
||||
var declarationBuf = new StringBuffer();
|
||||
for (var i = 0; i < depList.length; ++i) {
|
||||
importBuf.write('''
|
||||
import '${depList[i]}' as i${i};
|
||||
''');
|
||||
declarationBuf.write('i${i}.${SETUP_METHOD_NAME}(${REFLECTOR_VAR_NAME});');
|
||||
}
|
||||
|
||||
return '${importBuf}${declarationBuf}${tail}';
|
||||
var code = ngDeps.code;
|
||||
var importSeamIdx = ngDeps.imports.last.end;
|
||||
var declarationSeamIdx = ngDeps.setupMethod.end - 1;
|
||||
return '${code.substring(0, importSeamIdx)}'
|
||||
'$importBuf'
|
||||
'${code.substring(importSeamIdx, declarationSeamIdx)}'
|
||||
'$declarationBuf'
|
||||
'${code.substring(declarationSeamIdx)}';
|
||||
}
|
||||
|
||||
String _toDepsUri(String importUri) =>
|
||||
@ -40,17 +50,16 @@ bool _isNotDartImport(String importUri) {
|
||||
}
|
||||
|
||||
Future<List<String>> _processNgImports(
|
||||
Transform transform, List<String> imports) async {
|
||||
AssetReader reader, AssetId entryPoint, Iterable<String> imports) {
|
||||
final nullFuture = new Future.value(null);
|
||||
var retVal = <String>[];
|
||||
|
||||
return Future
|
||||
.wait(imports.where(_isNotDartImport).map(_toDepsUri).map((ngDepsUri) {
|
||||
var importAsset = uriToAssetId(
|
||||
transform.primaryInput.id, ngDepsUri, logger, null /* span */);
|
||||
return transform.hasInput(importAsset).then((hasInput) {
|
||||
if (hasInput) {
|
||||
retVal.add(ngDepsUri);
|
||||
}
|
||||
var importAsset =
|
||||
uriToAssetId(entryPoint, ngDepsUri, logger, null /* span */);
|
||||
if (importAsset == entryPoint) return nullFuture;
|
||||
return reader.hasInput(importAsset).then((hasInput) {
|
||||
if (hasInput) retVal.add(ngDepsUri);
|
||||
});
|
||||
})).then((_) => retVal);
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
library angular2.src.transform.directive_linker.transformer;
|
||||
library angular2.transform.directive_linker.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/formatter.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart' as log;
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
@ -27,12 +28,11 @@ class DirectiveLinker extends Transformer {
|
||||
log.init(transform);
|
||||
|
||||
try {
|
||||
var assetCode = await transform.primaryInput.readAsString();
|
||||
var assetPath = transform.primaryInput.id.path;
|
||||
var transformedCode = await linkNgDeps(transform, assetCode, assetPath);
|
||||
var formattedCode = formatter.format(transformedCode, uri: assetPath);
|
||||
transform.addOutput(
|
||||
new Asset.fromString(transform.primaryInput.id, formattedCode));
|
||||
var assetId = transform.primaryInput.id;
|
||||
var transformedCode =
|
||||
await linkNgDeps(new AssetReader.fromTransform(transform), assetId);
|
||||
var formattedCode = formatter.format(transformedCode, uri: assetId.path);
|
||||
transform.addOutput(new Asset.fromString(assetId, formattedCode));
|
||||
} catch (ex, stackTrace) {
|
||||
log.logger.error('Linking ng directives failed.\n'
|
||||
'Exception: $ex\n'
|
||||
|
@ -1,10 +1,9 @@
|
||||
library angular2.src.transform.directive_processor;
|
||||
library angular2.transform.directive_processor;
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:analyzer/src/generated/java_core.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/ngdata.dart';
|
||||
import 'package:angular2/src/transform/common/visitor_mixin.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
@ -42,7 +41,6 @@ class CreateNgDepsVisitor extends Object
|
||||
final FactoryTransformVisitor _factoryVisitor;
|
||||
final ParameterTransformVisitor _paramsVisitor;
|
||||
final AnnotationsTransformVisitor _metaVisitor;
|
||||
final NgData _ngData = new NgData();
|
||||
|
||||
/// The path to the file which we are parsing.
|
||||
final String importPath;
|
||||
@ -73,19 +71,16 @@ class CreateNgDepsVisitor extends Object
|
||||
_writeImport();
|
||||
wroteImport = true;
|
||||
}
|
||||
_ngData.imports.add(node.uri.stringValue);
|
||||
return node.accept(_copyVisitor);
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitExportDirective(ExportDirective node) {
|
||||
_ngData.imports.add(node.uri.stringValue);
|
||||
return node.accept(_copyVisitor);
|
||||
}
|
||||
|
||||
void _openFunctionWrapper() {
|
||||
// TODO(kegluneq): Use a [PrintWriter] with a length getter.
|
||||
_ngData.importOffset = writer.toString().length;
|
||||
writer.print('bool _visited = false;'
|
||||
'void ${SETUP_METHOD_NAME}(${REFLECTOR_VAR_NAME}) {'
|
||||
'if (_visited) return; _visited = true;');
|
||||
@ -95,10 +90,7 @@ class CreateNgDepsVisitor extends Object
|
||||
if (foundNgDirectives) {
|
||||
writer.print(';');
|
||||
}
|
||||
// TODO(kegluneq): Use a [PrintWriter] with a length getter.
|
||||
_ngData.registerOffset = writer.toString().length;
|
||||
writer.print('}');
|
||||
writer.print('// ${_ngData.toJson()}');
|
||||
}
|
||||
|
||||
ConstructorDeclaration _getCtor(ClassDeclaration node) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.directive_processor.transformer;
|
||||
library angular2.transform.directive_processor.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.directive_processor;
|
||||
library angular2.transform.directive_processor;
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:analyzer/src/generated/java_core.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform;
|
||||
library angular2.transform;
|
||||
|
||||
import 'dart:collection' show Queue;
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform;
|
||||
library angular2.transform;
|
||||
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:analyzer/src/generated/element.dart';
|
||||
@ -55,7 +55,7 @@ abstract class DirectiveRegistry {
|
||||
const setupReflectionMethodName = 'setupReflection';
|
||||
|
||||
const _libraryDeclaration = '''
|
||||
library angular2.src.transform.generated;
|
||||
library angular2.transform.generated;
|
||||
''';
|
||||
|
||||
const _reflectorImport = '''
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform;
|
||||
library angular2.transform;
|
||||
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:analyzer/src/generated/element.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform;
|
||||
library angular2.transform;
|
||||
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:analyzer/src/generated/element.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform;
|
||||
library angular2.transform;
|
||||
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:analyzer/src/generated/element.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform;
|
||||
library angular2.transform;
|
||||
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:analyzer/src/generated/element.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.reflection_remover.ast_tester;
|
||||
library angular2.transform.reflection_remover.ast_tester;
|
||||
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:analyzer/src/generated/element.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.reflection_remover.codegen;
|
||||
library angular2.transform.reflection_remover.codegen;
|
||||
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
@ -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';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.reflection_remover.rewriter;
|
||||
library angular2.transform.reflection_remover.rewriter;
|
||||
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.reflection_remover.transformer;
|
||||
library angular2.transform.reflection_remover.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angular2/src/transform/common/logging.dart' as log;
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.template_compiler.generator;
|
||||
library angular2.transform.template_compiler.generator;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.template_compiler.recording_reflection_capabilities;
|
||||
library angular2.transform.template_compiler.recording_reflection_capabilities;
|
||||
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
import 'package:angular2/src/reflection/types.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.template_compiler.transformer;
|
||||
library angular2.transform.template_compiler.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform;
|
||||
library angular2.transform;
|
||||
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
|
@ -1,11 +1,11 @@
|
||||
import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
|
||||
import {ArrayChanges} from 'angular2/src/change_detection/pipes/array_changes';
|
||||
import {IterableChanges} from 'angular2/src/change_detection/pipes/iterable_changes';
|
||||
|
||||
import {NumberWrapper} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {TestIterable} from './iterable';
|
||||
import {arrayChangesAsString} from './util';
|
||||
import {iterableChangesAsString} from './util';
|
||||
|
||||
// todo(vicb): UnmodifiableListView / frozen object when implemented
|
||||
export function main() {
|
||||
@ -15,7 +15,7 @@ export function main() {
|
||||
var l;
|
||||
|
||||
beforeEach(() => {
|
||||
changes = new ArrayChanges();
|
||||
changes = new IterableChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -23,30 +23,30 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should support list and iterables', () => {
|
||||
expect(ArrayChanges.supportsObj([])).toBeTruthy();
|
||||
expect(ArrayChanges.supportsObj(new TestIterable())).toBeTruthy();
|
||||
expect(ArrayChanges.supportsObj(MapWrapper.create())).toBeFalsy();
|
||||
expect(ArrayChanges.supportsObj(null)).toBeFalsy();
|
||||
expect(IterableChanges.supportsObj([])).toBeTruthy();
|
||||
expect(IterableChanges.supportsObj(new TestIterable())).toBeTruthy();
|
||||
expect(IterableChanges.supportsObj(MapWrapper.create())).toBeFalsy();
|
||||
expect(IterableChanges.supportsObj(null)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should support iterables', () => {
|
||||
l = new TestIterable();
|
||||
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: []
|
||||
}));
|
||||
|
||||
l.list = [1];
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['1[null->0]'],
|
||||
additions: ['1[null->0]']
|
||||
}));
|
||||
|
||||
l.list = [2, 1];
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['2[null->0]', '1[0->1]'],
|
||||
previous: ['1[0->1]'],
|
||||
additions: ['2[null->0]'],
|
||||
@ -57,20 +57,20 @@ export function main() {
|
||||
it('should detect additions', () => {
|
||||
l = [];
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: []
|
||||
}));
|
||||
|
||||
ListWrapper.push(l, 'a');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a[null->0]'],
|
||||
additions: ['a[null->0]']
|
||||
}));
|
||||
|
||||
ListWrapper.push(l, 'b');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a', 'b[null->1]'],
|
||||
previous: ['a'],
|
||||
additions: ['b[null->1]']
|
||||
@ -83,7 +83,7 @@ export function main() {
|
||||
|
||||
l = [1, 0];
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['1[null->0]', '0[0->1]'],
|
||||
previous: ['0[0->1]'],
|
||||
additions: ['1[null->0]'],
|
||||
@ -92,7 +92,7 @@ export function main() {
|
||||
|
||||
l = [2, 1, 0];
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['2[null->0]', '1[0->1]', '0[1->2]'],
|
||||
previous: ['1[0->1]', '0[1->2]'],
|
||||
additions: ['2[null->0]'],
|
||||
@ -108,7 +108,7 @@ export function main() {
|
||||
ListWrapper.push(l, 2);
|
||||
ListWrapper.push(l, 1);
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['2[1->0]', '1[0->1]'],
|
||||
previous: ['1[0->1]', '2[1->0]'],
|
||||
moves: ['2[1->0]', '1[0->1]']
|
||||
@ -122,7 +122,7 @@ export function main() {
|
||||
ListWrapper.removeAt(l, 1);
|
||||
ListWrapper.insert(l, 0, 'b');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['b[1->0]', 'a[0->1]', 'c'],
|
||||
previous: ['a[0->1]', 'b[1->0]', 'c'],
|
||||
moves: ['b[1->0]', 'a[0->1]']
|
||||
@ -131,7 +131,7 @@ export function main() {
|
||||
ListWrapper.removeAt(l, 1);
|
||||
ListWrapper.push(l, 'a');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['b', 'c[2->1]', 'a[1->2]'],
|
||||
previous: ['b', 'a[1->2]', 'c[2->1]'],
|
||||
moves: ['c[2->1]', 'a[1->2]']
|
||||
@ -144,14 +144,14 @@ export function main() {
|
||||
|
||||
ListWrapper.push(l, 'a');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a[null->0]'],
|
||||
additions: ['a[null->0]']
|
||||
}));
|
||||
|
||||
ListWrapper.push(l, 'b');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a', 'b[null->1]'],
|
||||
previous: ['a'],
|
||||
additions: ['b[null->1]']
|
||||
@ -160,7 +160,7 @@ export function main() {
|
||||
ListWrapper.push(l, 'c');
|
||||
ListWrapper.push(l, 'd');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a', 'b', 'c[null->2]', 'd[null->3]'],
|
||||
previous: ['a', 'b'],
|
||||
additions: ['c[null->2]', 'd[null->3]']
|
||||
@ -168,7 +168,7 @@ export function main() {
|
||||
|
||||
ListWrapper.removeAt(l, 2);
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a', 'b', 'd[3->2]'],
|
||||
previous: ['a', 'b', 'c[2->null]', 'd[3->2]'],
|
||||
moves: ['d[3->2]'],
|
||||
@ -181,7 +181,7 @@ export function main() {
|
||||
ListWrapper.push(l, 'b');
|
||||
ListWrapper.push(l, 'a');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['d[2->0]', 'c[null->1]', 'b[1->2]', 'a[0->3]'],
|
||||
previous: ['a[0->3]', 'b[1->2]', 'd[2->0]'],
|
||||
additions: ['c[null->1]'],
|
||||
@ -197,7 +197,7 @@ export function main() {
|
||||
var oo = 'oo';
|
||||
ListWrapper.set(l, 1, b + oo);
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a', 'boo'],
|
||||
previous: ['a', 'boo']
|
||||
}));
|
||||
@ -207,7 +207,7 @@ export function main() {
|
||||
l = [NumberWrapper.NaN];
|
||||
changes.check(l);
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: [NumberWrapper.NaN],
|
||||
previous: [NumberWrapper.NaN]
|
||||
}));
|
||||
@ -219,7 +219,7 @@ export function main() {
|
||||
|
||||
ListWrapper.insert(l, 0, 'foo');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['foo[null->0]', 'NaN[0->1]', 'NaN[1->2]'],
|
||||
previous: ['NaN[0->1]', 'NaN[1->2]'],
|
||||
additions: ['foo[null->0]'],
|
||||
@ -233,7 +233,7 @@ export function main() {
|
||||
|
||||
ListWrapper.removeAt(l, 1);
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a', 'c[2->1]'],
|
||||
previous: ['a', 'b[1->null]', 'c[2->1]'],
|
||||
moves: ['c[2->1]'],
|
||||
@ -242,7 +242,7 @@ export function main() {
|
||||
|
||||
ListWrapper.insert(l, 1, 'b');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a', 'b[null->1]', 'c[1->2]'],
|
||||
previous: ['a', 'c[1->2]'],
|
||||
additions: ['b[null->1]'],
|
||||
@ -256,7 +256,7 @@ export function main() {
|
||||
|
||||
ListWrapper.removeAt(l, 0);
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a', 'a', 'b[3->2]', 'b[4->3]'],
|
||||
previous: ['a', 'a', 'a[2->null]', 'b[3->2]', 'b[4->3]'],
|
||||
moves: ['b[3->2]', 'b[4->3]'],
|
||||
@ -270,7 +270,7 @@ export function main() {
|
||||
|
||||
ListWrapper.insert(l, 0, 'b');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['b[2->0]', 'a[0->1]', 'a[1->2]', 'b', 'b[null->4]'],
|
||||
previous: ['a[0->1]', 'a[1->2]', 'b[2->0]', 'b'],
|
||||
additions: ['b[null->4]'],
|
||||
@ -287,7 +287,7 @@ export function main() {
|
||||
ListWrapper.push(l, 'a');
|
||||
ListWrapper.push(l, 'c');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
expect(changes.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['b[1->0]', 'a[0->1]', 'c'],
|
||||
previous: ['a[0->1]', 'b[1->0]', 'c'],
|
||||
moves: ['b[1->0]', 'a[0->1]']
|
@ -1,6 +1,6 @@
|
||||
import {isBlank} from 'angular2/src/facade/lang';
|
||||
|
||||
export function arrayChangesAsString({collection, previous, additions, moves, removals}) {
|
||||
export function iterableChangesAsString({collection, previous, additions, moves, removals}) {
|
||||
if (isBlank(collection)) collection = [];
|
||||
if (isBlank(previous)) previous = [];
|
||||
if (isBlank(additions)) additions = [];
|
||||
|
@ -2,6 +2,7 @@ import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
el,
|
||||
expect,
|
||||
@ -116,7 +117,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should consume binding to aria-* attributes', inject([AsyncTestCompleter], (async) => {
|
||||
tplResolver.setTemplate(MyComp, new Template({inline: '<div [aria-label]="ctxProp"></div>'}));
|
||||
tplResolver.setTemplate(MyComp, new Template({inline: '<div [attr.aria-label]="ctxProp"></div>'}));
|
||||
|
||||
compiler.compile(MyComp).then((pv) => {
|
||||
createView(pv);
|
||||
@ -185,6 +186,20 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should ignore bindings to unknown properties', inject([AsyncTestCompleter], (async) => {
|
||||
tplResolver.setTemplate(MyComp, new Template({inline: '<div unknown="{{ctxProp}}"></div>'}));
|
||||
|
||||
compiler.compile(MyComp).then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
ctx.ctxProp = 'Some value';
|
||||
cd.detectChanges();
|
||||
expect(DOM.hasProperty(view.nodes[0], 'unknown')).toBeFalsy();
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should consume directive watch expression change.', inject([AsyncTestCompleter], (async) => {
|
||||
var tpl =
|
||||
'<div>' +
|
||||
@ -436,30 +451,57 @@ export function main() {
|
||||
})
|
||||
}));
|
||||
|
||||
it('should provide binding configuration config to the component', inject([AsyncTestCompleter], (async) => {
|
||||
tplResolver.setTemplate(MyComp, new Template({
|
||||
inline: '<push-cmp #cmp></push-cmp>',
|
||||
directives: [[[PushBasedComp]]]
|
||||
describe("BindingPropagationConfig", () => {
|
||||
it("can be used to disable the change detection of the component's template",
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
|
||||
tplResolver.setTemplate(MyComp, new Template({
|
||||
inline: '<push-cmp #cmp></push-cmp>',
|
||||
directives: [[[PushBasedComp]]]
|
||||
}));
|
||||
|
||||
compiler.compile(MyComp).then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
var cmp = view.locals.get('cmp');
|
||||
|
||||
cd.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
|
||||
cd.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
|
||||
cmp.propagate();
|
||||
|
||||
cd.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(2);
|
||||
async.done();
|
||||
})
|
||||
}));
|
||||
|
||||
compiler.compile(MyComp).then((pv) => {
|
||||
createView(pv);
|
||||
it('should not affect updating properties on the component', inject([AsyncTestCompleter], (async) => {
|
||||
tplResolver.setTemplate(MyComp, new Template({
|
||||
inline: '<push-cmp [prop]="ctxProp" #cmp></push-cmp>',
|
||||
directives: [[[PushBasedComp]]]
|
||||
}));
|
||||
|
||||
var cmp = view.locals.get('cmp');
|
||||
compiler.compile(MyComp).then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
cd.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
var cmp = view.locals.get('cmp');
|
||||
|
||||
cd.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
ctx.ctxProp = "one";
|
||||
cd.detectChanges();
|
||||
expect(cmp.prop).toEqual("one");
|
||||
|
||||
cmp.propagate();
|
||||
ctx.ctxProp = "two";
|
||||
cd.detectChanges();
|
||||
expect(cmp.prop).toEqual("two");
|
||||
|
||||
cd.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(2);
|
||||
async.done();
|
||||
})
|
||||
}));
|
||||
async.done();
|
||||
})
|
||||
}));
|
||||
});
|
||||
|
||||
it('should create a component that injects a @Parent', inject([AsyncTestCompleter], (async) => {
|
||||
tplResolver.setTemplate(MyComp, new Template({
|
||||
@ -566,61 +608,60 @@ export function main() {
|
||||
}));
|
||||
});
|
||||
|
||||
if (assertionsEnabled()) {
|
||||
// Disabled until a solution is found, refs:
|
||||
// - https://github.com/angular/angular/issues/776
|
||||
// - https://github.com/angular/angular/commit/81f3f32
|
||||
xdescribe('Missing directive checks', () => {
|
||||
|
||||
function expectCompileError(inlineTpl, errMessage, done) {
|
||||
tplResolver.setTemplate(MyComp, new Template({inline: inlineTpl}));
|
||||
PromiseWrapper.then(compiler.compile(MyComp),
|
||||
(value) => {
|
||||
throw new BaseException("Test failure: should not have come here as an exception was expected");
|
||||
},
|
||||
(err) => {
|
||||
expect(err.message).toEqual(errMessage);
|
||||
done();
|
||||
}
|
||||
);
|
||||
if (assertionsEnabled()) {
|
||||
|
||||
function expectCompileError(inlineTpl, errMessage, done) {
|
||||
tplResolver.setTemplate(MyComp, new Template({inline: inlineTpl}));
|
||||
PromiseWrapper.then(compiler.compile(MyComp),
|
||||
(value) => {
|
||||
throw new BaseException("Test failure: should not have come here as an exception was expected");
|
||||
},
|
||||
(err) => {
|
||||
expect(err.message).toEqual(errMessage);
|
||||
done();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
it('should raise an error if no directive is registered for a template with template bindings', inject([AsyncTestCompleter], (async) => {
|
||||
expectCompileError(
|
||||
'<div><div template="if: foo"></div></div>',
|
||||
'Missing directive to handle \'if\' in <div template="if: foo">',
|
||||
() => async.done()
|
||||
);
|
||||
}));
|
||||
|
||||
it('should raise an error for missing template directive (1)', inject([AsyncTestCompleter], (async) => {
|
||||
expectCompileError(
|
||||
'<div><template foo></template></div>',
|
||||
'Missing directive to handle: <template foo>',
|
||||
() => async.done()
|
||||
);
|
||||
}));
|
||||
|
||||
it('should raise an error for missing template directive (2)', inject([AsyncTestCompleter], (async) => {
|
||||
expectCompileError(
|
||||
'<div><template *if="condition"></template></div>',
|
||||
'Missing directive to handle: <template *if="condition">',
|
||||
() => async.done()
|
||||
);
|
||||
}));
|
||||
|
||||
it('should raise an error for missing template directive (3)', inject([AsyncTestCompleter], (async) => {
|
||||
expectCompileError(
|
||||
'<div *if="condition"></div>',
|
||||
'Missing directive to handle \'if\' in MyComp: <div *if="condition">',
|
||||
() => async.done()
|
||||
);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
it('should raise an error if no directive is registered for an unsupported DOM property', inject([AsyncTestCompleter], (async) => {
|
||||
expectCompileError(
|
||||
'<div [some-prop]="foo"></div>',
|
||||
'Missing directive to handle \'some-prop\' in MyComp: <div [some-prop]="foo">',
|
||||
() => async.done()
|
||||
);
|
||||
}));
|
||||
|
||||
it('should raise an error if no directive is registered for a template with template bindings', inject([AsyncTestCompleter], (async) => {
|
||||
expectCompileError(
|
||||
'<div><div template="if: foo"></div></div>',
|
||||
'Missing directive to handle \'if\' in <div template="if: foo">',
|
||||
() => async.done()
|
||||
);
|
||||
}));
|
||||
|
||||
it('should raise an error for missing template directive (1)', inject([AsyncTestCompleter], (async) => {
|
||||
expectCompileError(
|
||||
'<div><template foo></template></div>',
|
||||
'Missing directive to handle: <template foo>',
|
||||
() => async.done()
|
||||
);
|
||||
}));
|
||||
|
||||
it('should raise an error for missing template directive (2)', inject([AsyncTestCompleter], (async) => {
|
||||
expectCompileError(
|
||||
'<div><template *if="condition"></template></div>',
|
||||
'Missing directive to handle: <template *if="condition">',
|
||||
() => async.done()
|
||||
);
|
||||
}));
|
||||
|
||||
it('should raise an error for missing template directive (3)', inject([AsyncTestCompleter], (async) => {
|
||||
expectCompileError(
|
||||
'<div *if="condition"></div>',
|
||||
'Missing directive to handle \'if\' in MyComp: <div *if="condition">',
|
||||
() => async.done()
|
||||
);
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -659,11 +700,17 @@ class MyDir {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'push-cmp'})
|
||||
@Component({
|
||||
selector: 'push-cmp',
|
||||
bind: {
|
||||
'prop': 'prop'
|
||||
}
|
||||
})
|
||||
@Template({inline: '{{field}}'})
|
||||
class PushBasedComp {
|
||||
numberOfChecks:number;
|
||||
bpc:BindingPropagationConfig;
|
||||
prop;
|
||||
|
||||
constructor(bpc:BindingPropagationConfig) {
|
||||
this.numberOfChecks = 0;
|
||||
|
@ -218,7 +218,7 @@ export function main() {
|
||||
|
||||
it('should bind to aria-* attributes when exp evaluates to strings', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'aria-label': 'prop1'
|
||||
'attr.aria-label': 'prop1'
|
||||
});
|
||||
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
||||
@ -243,7 +243,7 @@ export function main() {
|
||||
|
||||
it('should bind to aria-* attributes when exp evaluates to booleans', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'aria-busy': 'prop1'
|
||||
'attr.aria-busy': 'prop1'
|
||||
});
|
||||
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
||||
@ -264,7 +264,7 @@ export function main() {
|
||||
|
||||
it('should bind to ARIA role attribute', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'role': 'prop1'
|
||||
'attr.role': 'prop1'
|
||||
});
|
||||
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
||||
@ -289,7 +289,7 @@ export function main() {
|
||||
|
||||
it('should throw for a non-string ARIA role', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'role': 'prop1'
|
||||
'attr.role': 'prop1'
|
||||
});
|
||||
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
||||
@ -303,6 +303,31 @@ export function main() {
|
||||
}).toThrowError("Invalid role attribute, only string values are allowed, got '1'");
|
||||
});
|
||||
|
||||
it('should bind to any attribute', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'attr.foo-bar': 'prop1'
|
||||
});
|
||||
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders[0].hasElementPropertyBindings).toBe(true);
|
||||
|
||||
instantiateView(pv);
|
||||
|
||||
evalContext.prop1 = 'baz';
|
||||
changeDetector.detectChanges();
|
||||
expect(DOM.getAttribute(view.nodes[0], 'foo-bar')).toEqual('baz');
|
||||
|
||||
evalContext.prop1 = 123;
|
||||
changeDetector.detectChanges();
|
||||
expect(DOM.getAttribute(view.nodes[0], 'foo-bar')).toEqual('123');
|
||||
|
||||
evalContext.prop1 = null;
|
||||
changeDetector.detectChanges();
|
||||
expect(DOM.getAttribute(view.nodes[0], 'foo-bar')).toBeNull();
|
||||
});
|
||||
|
||||
it('should bind class with a dot', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'class.bar': 'prop1',
|
||||
|
172
modules/angular2/test/core/compiler/selector_spec.js
vendored
172
modules/angular2/test/core/compiler/selector_spec.js
vendored
@ -23,162 +23,181 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should select by element name case insensitive', () => {
|
||||
matcher.addSelectable(s1 = CssSelector.parse('someTag'), 1);
|
||||
matcher.addSelectables(s1 = CssSelector.parse('someTag'), 1);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('SOMEOTHERTAG'), selectableCollector)).toEqual(false);
|
||||
expect(matcher.match(CssSelector.parse('SOMEOTHERTAG')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('SOMETAG'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1,1]);
|
||||
expect(matcher.match(CssSelector.parse('SOMETAG')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0],1]);
|
||||
});
|
||||
|
||||
it('should select by class name case insensitive', () => {
|
||||
matcher.addSelectable(s1 = CssSelector.parse('.someClass'), 1);
|
||||
matcher.addSelectable(s2 = CssSelector.parse('.someClass.class2'), 2);
|
||||
matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1);
|
||||
matcher.addSelectables(s2 = CssSelector.parse('.someClass.class2'), 2);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS'), selectableCollector)).toEqual(false);
|
||||
expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('.SOMECLASS'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1,1]);
|
||||
expect(matcher.match(CssSelector.parse('.SOMECLASS')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0],1]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(CssSelector.parse('.someClass.class2'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1,1,s2,2]);
|
||||
expect(matcher.match(CssSelector.parse('.someClass.class2')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0],1,s2[0],2]);
|
||||
});
|
||||
|
||||
it('should select by attr name case insensitive independent of the value', () => {
|
||||
matcher.addSelectable(s1 = CssSelector.parse('[someAttr]'), 1);
|
||||
matcher.addSelectable(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
|
||||
matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1);
|
||||
matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]'), selectableCollector)).toEqual(false);
|
||||
expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('[SOMEATTR]'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1,1]);
|
||||
expect(matcher.match(CssSelector.parse('[SOMEATTR]')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0],1]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1,1]);
|
||||
expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0],1]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1,1,s2,2]);
|
||||
expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0],1,s2[0],2]);
|
||||
});
|
||||
|
||||
it('should select by attr name only once if the value is from the DOM', () => {
|
||||
matcher.addSelectable(s1 = CssSelector.parse('[some-decor]'), 1);
|
||||
matcher.addSelectables(s1 = CssSelector.parse('[some-decor]'), 1);
|
||||
|
||||
var elementSelector = new CssSelector();
|
||||
var element = el('<div attr></div>');
|
||||
var empty = DOM.getAttribute(element, 'attr');
|
||||
elementSelector.addAttribute('some-decor', empty);
|
||||
matcher.match(elementSelector, selectableCollector);
|
||||
expect(matched).toEqual([s1,1]);
|
||||
expect(matched).toEqual([s1[0],1]);
|
||||
});
|
||||
|
||||
it('should select by attr name and value case insensitive', () => {
|
||||
matcher.addSelectable(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
|
||||
matcher.addSelectables(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]'), selectableCollector)).toEqual(false);
|
||||
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1,1]);
|
||||
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0],1]);
|
||||
});
|
||||
|
||||
it('should select by element name, class name and attribute name with value', () => {
|
||||
matcher.addSelectable(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
|
||||
matcher.addSelectables(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]'), selectableCollector)).toEqual(false);
|
||||
expect(matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('someTag.someOtherClass[someOtherAttr]'), selectableCollector)).toEqual(false);
|
||||
expect(matcher.match(CssSelector.parse('someTag.someOtherClass[someOtherAttr]')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('someTag.someClass[someOtherAttr]'), selectableCollector)).toEqual(false);
|
||||
expect(matcher.match(CssSelector.parse('someTag.someClass[someOtherAttr]')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr]'), selectableCollector)).toEqual(false);
|
||||
expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr]')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1,1]);
|
||||
expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0],1]);
|
||||
});
|
||||
|
||||
it('should select independent of the order in the css selector', () => {
|
||||
matcher.addSelectable(s1 = CssSelector.parse('[someAttr].someClass'), 1);
|
||||
matcher.addSelectable(s2 = CssSelector.parse('.someClass[someAttr]'), 2);
|
||||
matcher.addSelectable(s3 = CssSelector.parse('.class1.class2'), 3);
|
||||
matcher.addSelectable(s4 = CssSelector.parse('.class2.class1'), 4);
|
||||
matcher.addSelectables(s1 = CssSelector.parse('[someAttr].someClass'), 1);
|
||||
matcher.addSelectables(s2 = CssSelector.parse('.someClass[someAttr]'), 2);
|
||||
matcher.addSelectables(s3 = CssSelector.parse('.class1.class2'), 3);
|
||||
matcher.addSelectables(s4 = CssSelector.parse('.class2.class1'), 4);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('[someAttr].someClass'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1,1,s2,2]);
|
||||
expect(matcher.match(CssSelector.parse('[someAttr].someClass')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0],1,s2[0],2]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(CssSelector.parse('.someClass[someAttr]'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1,1,s2,2]);
|
||||
expect(matcher.match(CssSelector.parse('.someClass[someAttr]')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0],1,s2[0],2]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(CssSelector.parse('.class1.class2'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s3,3,s4,4]);
|
||||
expect(matcher.match(CssSelector.parse('.class1.class2')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s3[0],3,s4[0],4]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(CssSelector.parse('.class2.class1'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s4,4,s3,3]);
|
||||
expect(matcher.match(CssSelector.parse('.class2.class1')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s4[0],4,s3[0],3]);
|
||||
});
|
||||
|
||||
it('should not select with a matching :not selector', () => {
|
||||
matcher.addSelectable(CssSelector.parse('p:not(.someClass)'), 1);
|
||||
matcher.addSelectable(CssSelector.parse('p:not([someAttr])'), 2);
|
||||
matcher.addSelectable(CssSelector.parse(':not(.someClass)'), 3);
|
||||
matcher.addSelectable(CssSelector.parse(':not(p)'), 4);
|
||||
matcher.addSelectable(CssSelector.parse(':not(p[someAttr])'), 5);
|
||||
matcher.addSelectables(CssSelector.parse('p:not(.someClass)'), 1);
|
||||
matcher.addSelectables(CssSelector.parse('p:not([someAttr])'), 2);
|
||||
matcher.addSelectables(CssSelector.parse(':not(.someClass)'), 3);
|
||||
matcher.addSelectables(CssSelector.parse(':not(p)'), 4);
|
||||
matcher.addSelectables(CssSelector.parse(':not(p[someAttr])'), 5);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('p.someClass[someAttr]'), selectableCollector)).toEqual(false);
|
||||
expect(matcher.match(CssSelector.parse('p.someClass[someAttr]')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
});
|
||||
|
||||
it('should select with a non matching :not selector', () => {
|
||||
matcher.addSelectable(s1 = CssSelector.parse('p:not(.someClass)'), 1);
|
||||
matcher.addSelectable(s2 = CssSelector.parse('p:not(.someOtherClass[someAttr])'), 2);
|
||||
matcher.addSelectable(s3 = CssSelector.parse(':not(.someClass)'), 3);
|
||||
matcher.addSelectable(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4);
|
||||
matcher.addSelectables(s1 = CssSelector.parse('p:not(.someClass)'), 1);
|
||||
matcher.addSelectables(s2 = CssSelector.parse('p:not(.someOtherClass[someAttr])'), 2);
|
||||
matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3);
|
||||
matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('p[someOtherAttr].someOtherClass'), selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1,1,s2,2,s3,3,s4,4]);
|
||||
expect(matcher.match(CssSelector.parse('p[someOtherAttr].someOtherClass')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0],1,s2[0],2,s3[0],3,s4[0],4]);
|
||||
});
|
||||
|
||||
it('should select with one match in a list', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('textbox')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[1],1]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(CssSelector.parse('input[type=text]')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0],1]);
|
||||
});
|
||||
|
||||
it('should not select twice with two matches in a list', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('input, .someClass'), 1);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('input.someclass')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched.length).toEqual(2);
|
||||
expect(matched).toEqual([s1[0],1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CssSelector.parse', () => {
|
||||
it('should detect element names', () => {
|
||||
var cssSelector = CssSelector.parse('sometag');
|
||||
var cssSelector = CssSelector.parse('sometag')[0];
|
||||
expect(cssSelector.element).toEqual('sometag');
|
||||
expect(cssSelector.toString()).toEqual('sometag');
|
||||
});
|
||||
|
||||
it('should detect class names', () => {
|
||||
var cssSelector = CssSelector.parse('.someClass');
|
||||
var cssSelector = CssSelector.parse('.someClass')[0];
|
||||
expect(cssSelector.classNames).toEqual(['someclass']);
|
||||
|
||||
expect(cssSelector.toString()).toEqual('.someclass');
|
||||
});
|
||||
|
||||
it('should detect attr names', () => {
|
||||
var cssSelector = CssSelector.parse('[attrname]');
|
||||
var cssSelector = CssSelector.parse('[attrname]')[0];
|
||||
expect(cssSelector.attrs).toEqual(['attrname', '']);
|
||||
|
||||
expect(cssSelector.toString()).toEqual('[attrname]');
|
||||
});
|
||||
|
||||
it('should detect attr values', () => {
|
||||
var cssSelector = CssSelector.parse('[attrname=attrvalue]');
|
||||
var cssSelector = CssSelector.parse('[attrname=attrvalue]')[0];
|
||||
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
|
||||
expect(cssSelector.toString()).toEqual('[attrname=attrvalue]');
|
||||
});
|
||||
|
||||
it('should detect multiple parts', () => {
|
||||
var cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass');
|
||||
var cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass')[0];
|
||||
expect(cssSelector.element).toEqual('sometag');
|
||||
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
|
||||
expect(cssSelector.classNames).toEqual(['someclass']);
|
||||
@ -187,7 +206,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should detect :not', () => {
|
||||
var cssSelector = CssSelector.parse('sometag:not([attrname=attrvalue].someclass)');
|
||||
var cssSelector = CssSelector.parse('sometag:not([attrname=attrvalue].someclass)')[0];
|
||||
expect(cssSelector.element).toEqual('sometag');
|
||||
expect(cssSelector.attrs.length).toEqual(0);
|
||||
expect(cssSelector.classNames.length).toEqual(0);
|
||||
@ -201,7 +220,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should detect :not without truthy', () => {
|
||||
var cssSelector = CssSelector.parse(':not([attrname=attrvalue].someclass)');
|
||||
var cssSelector = CssSelector.parse(':not([attrname=attrvalue].someclass)')[0];
|
||||
expect(cssSelector.element).toEqual("*");
|
||||
|
||||
var notSelector = cssSelector.notSelector;
|
||||
@ -213,8 +232,31 @@ export function main() {
|
||||
|
||||
it('should throw when nested :not', () => {
|
||||
expect(() => {
|
||||
CssSelector.parse('sometag:not(:not([attrname=attrvalue].someclass))')
|
||||
CssSelector.parse('sometag:not(:not([attrname=attrvalue].someclass))')[0];
|
||||
}).toThrowError('Nesting :not is not allowed in a selector');
|
||||
});
|
||||
|
||||
it('should detect lists of selectors', () => {
|
||||
var cssSelectors = CssSelector.parse('.someclass,[attrname=attrvalue], sometag');
|
||||
expect(cssSelectors.length).toEqual(3);
|
||||
|
||||
expect(cssSelectors[0].classNames).toEqual(['someclass']);
|
||||
expect(cssSelectors[1].attrs).toEqual(['attrname', 'attrvalue']);
|
||||
expect(cssSelectors[2].element).toEqual('sometag');
|
||||
});
|
||||
|
||||
it('should detect lists of selectors with :not', () => {
|
||||
var cssSelectors = CssSelector.parse('input[type=text], :not(textarea), textbox:not(.special)');
|
||||
expect(cssSelectors.length).toEqual(3);
|
||||
|
||||
expect(cssSelectors[0].element).toEqual('input');
|
||||
expect(cssSelectors[0].attrs).toEqual(['type', 'text']);
|
||||
|
||||
expect(cssSelectors[1].element).toEqual('*');
|
||||
expect(cssSelectors[1].notSelector.element).toEqual('textarea');
|
||||
|
||||
expect(cssSelectors[2].element).toEqual('textbox');
|
||||
expect(cssSelectors[2].notSelector.classNames).toEqual(['special']);
|
||||
});
|
||||
});
|
||||
}
|
@ -108,5 +108,10 @@ export function main() {
|
||||
var css = s('x /deep/ y {}', 'a');
|
||||
expect(css).toEqual('x[a] y[a] {}');
|
||||
});
|
||||
|
||||
it('should handle >>>', () => {
|
||||
var css = s('x >>> y {}', 'a');
|
||||
expect(css).toEqual('x[a] y[a] {}');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -25,12 +25,12 @@ import {Decorator, Component, Viewport} from 'angular2/src/core/annotations/anno
|
||||
|
||||
import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock';
|
||||
|
||||
import {Foreach} from 'angular2/src/directives/foreach';
|
||||
import {For} from 'angular2/src/directives/for';
|
||||
|
||||
import {bind} from 'angular2/di';
|
||||
|
||||
export function main() {
|
||||
describe('foreach', () => {
|
||||
describe('for', () => {
|
||||
var view, cd, compiler, component, tplResolver;
|
||||
|
||||
beforeEachBindings(() => [
|
||||
@ -52,13 +52,13 @@ export function main() {
|
||||
function compileWithTemplate(html) {
|
||||
var template = new Template({
|
||||
inline: html,
|
||||
directives: [Foreach]
|
||||
directives: [For]
|
||||
});
|
||||
tplResolver.setTemplate(TestComponent, template);
|
||||
return compiler.compile(TestComponent);
|
||||
}
|
||||
|
||||
var TEMPLATE = '<div><copy-me template="foreach #item in items">{{item.toString()}};</copy-me></div>';
|
||||
var TEMPLATE = '<div><copy-me template="for #item of items">{{item.toString()}};</copy-me></div>';
|
||||
|
||||
it('should reflect initial elements', inject([AsyncTestCompleter], (async) => {
|
||||
compileWithTemplate(TEMPLATE).then((pv) => {
|
||||
@ -124,8 +124,8 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should iterate over an array of objects', () => {
|
||||
compileWithTemplate('<ul><li template="foreach #item in items">{{item["name"]}};</li></ul>').then((pv) => {
|
||||
it('should iterate over an array of objects', inject([AsyncTestCompleter], (async) => {
|
||||
compileWithTemplate('<ul><li template="for #item of items">{{item["name"]}};</li></ul>').then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
// INIT
|
||||
@ -145,11 +145,12 @@ export function main() {
|
||||
cd.detectChanges();
|
||||
|
||||
expect(DOM.getText(view.nodes[0])).toEqual('shyam;');
|
||||
async.done();
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should gracefully handle nulls', inject([AsyncTestCompleter], (async) => {
|
||||
compileWithTemplate('<ul><li template="foreach #item in null">{{item}};</li></ul>').then((pv) => {
|
||||
compileWithTemplate('<ul><li template="for #item of null">{{item}};</li></ul>').then((pv) => {
|
||||
createView(pv);
|
||||
cd.detectChanges();
|
||||
expect(DOM.getText(view.nodes[0])).toEqual('');
|
||||
@ -199,10 +200,13 @@ export function main() {
|
||||
|
||||
it('should repeat over nested arrays', inject([AsyncTestCompleter], (async) => {
|
||||
compileWithTemplate(
|
||||
'<div><div template="foreach #item in items">' +
|
||||
'<div template="foreach #subitem in item">' +
|
||||
'{{subitem}}-{{item.length}};' +
|
||||
'</div>|</div></div>'
|
||||
'<div>'+
|
||||
'<div template="for #item of items">' +
|
||||
'<div template="for #subitem of item">' +
|
||||
'{{subitem}}-{{item.length}};' +
|
||||
'</div>|'+
|
||||
'</div>'+
|
||||
'</div>'
|
||||
).then((pv) => {
|
||||
createView(pv);
|
||||
component.items = [['a', 'b'], ['c']];
|
||||
@ -210,13 +214,38 @@ export function main() {
|
||||
cd.detectChanges();
|
||||
cd.detectChanges();
|
||||
expect(DOM.getText(view.nodes[0])).toEqual('a-2;b-2;|c-1;|');
|
||||
|
||||
component.items = [['e'], ['f', 'g']];
|
||||
cd.detectChanges();
|
||||
expect(DOM.getText(view.nodes[0])).toEqual('e-1;|f-2;g-2;|');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should repeat over nested arrays with no intermediate element', inject([AsyncTestCompleter], (async) => {
|
||||
compileWithTemplate(
|
||||
'<div><template [for] #item [of]="items">' +
|
||||
'<div template="for #subitem of item">' +
|
||||
'{{subitem}}-{{item.length}};' +
|
||||
'</div></template></div>'
|
||||
).then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
component.items = [['a', 'b'], ['c']];
|
||||
cd.detectChanges();
|
||||
expect(DOM.getText(view.nodes[0])).toEqual('a-2;b-2;c-1;');
|
||||
|
||||
component.items = [['e'], ['f', 'g']];
|
||||
cd.detectChanges();
|
||||
expect(DOM.getText(view.nodes[0])).toEqual('e-1;f-2;g-2;');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display indices correctly', inject([AsyncTestCompleter], (async) => {
|
||||
var INDEX_TEMPLATE =
|
||||
'<div><copy-me template="foreach: var item in items; var i=index">{{i.toString()}}</copy-me></div>';
|
||||
'<div><copy-me template="for: var item of items; var i=index">{{i.toString()}}</copy-me></div>';
|
||||
compileWithTemplate(INDEX_TEMPLATE).then((pv) => {
|
||||
createView(pv);
|
||||
component.items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
35
modules/angular2/test/directives/if_spec.js
vendored
35
modules/angular2/test/directives/if_spec.js
vendored
@ -116,6 +116,39 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should handle nested if correctly', inject([AsyncTestCompleter], (async) => {
|
||||
compileWithTemplate('<div><template [if]="booleanCondition"><copy-me *if="nestedBooleanCondition">hello</copy-me></template></div>').then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
component.booleanCondition = false;
|
||||
cd.detectChanges();
|
||||
expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(0);
|
||||
expect(DOM.getText(view.nodes[0])).toEqual('');
|
||||
|
||||
component.booleanCondition = true;
|
||||
cd.detectChanges();
|
||||
expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1);
|
||||
expect(DOM.getText(view.nodes[0])).toEqual('hello');
|
||||
|
||||
component.nestedBooleanCondition = false;
|
||||
cd.detectChanges();
|
||||
expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(0);
|
||||
expect(DOM.getText(view.nodes[0])).toEqual('');
|
||||
|
||||
component.nestedBooleanCondition = true;
|
||||
cd.detectChanges();
|
||||
expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(1);
|
||||
expect(DOM.getText(view.nodes[0])).toEqual('hello');
|
||||
|
||||
component.booleanCondition = false;
|
||||
cd.detectChanges();
|
||||
expect(DOM.querySelectorAll(view.nodes[0], 'copy-me').length).toEqual(0);
|
||||
expect(DOM.getText(view.nodes[0])).toEqual('');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should update several nodes with if', inject([AsyncTestCompleter], (async) => {
|
||||
var templateString =
|
||||
'<div>' +
|
||||
@ -195,11 +228,13 @@ export function main() {
|
||||
@Component({selector: 'test-cmp'})
|
||||
class TestComponent {
|
||||
booleanCondition: boolean;
|
||||
nestedBooleanCondition: boolean;
|
||||
numberCondition: number;
|
||||
stringCondition: string;
|
||||
functionCondition: Function;
|
||||
constructor() {
|
||||
this.booleanCondition = true;
|
||||
this.nestedBooleanCondition = true;
|
||||
this.numberCondition = 1;
|
||||
this.stringCondition = "foo";
|
||||
this.functionCondition = function(s, n){
|
||||
|
19
modules/angular2/test/forms/form_builder_spec.js
vendored
19
modules/angular2/test/forms/form_builder_spec.js
vendored
@ -1,6 +1,5 @@
|
||||
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib';
|
||||
import {Control, FormBuilder} from 'angular2/forms';
|
||||
import * as validations from 'angular2/forms';
|
||||
import {Control, FormBuilder, Validators} from 'angular2/forms';
|
||||
|
||||
export function main() {
|
||||
describe("Form Builder", () => {
|
||||
@ -21,21 +20,21 @@ export function main() {
|
||||
it("should create controls from an array", () => {
|
||||
var g = b.group({
|
||||
"login": ["some value"],
|
||||
"password": ["some value", validations.required]
|
||||
"password": ["some value", Validators.required]
|
||||
});
|
||||
|
||||
expect(g.controls["login"].value).toEqual("some value");
|
||||
expect(g.controls["password"].value).toEqual("some value");
|
||||
expect(g.controls["password"].validator).toEqual(validations.required);
|
||||
expect(g.controls["password"].validator).toEqual(Validators.required);
|
||||
});
|
||||
|
||||
it("should use controls", () => {
|
||||
var g = b.group({
|
||||
"login": b.control("some value", validations.required)
|
||||
"login": b.control("some value", Validators.required)
|
||||
});
|
||||
|
||||
expect(g.controls["login"].value).toEqual("some value");
|
||||
expect(g.controls["login"].validator).toBe(validations.required);
|
||||
expect(g.controls["login"].validator).toBe(Validators.required);
|
||||
});
|
||||
|
||||
it("should create groups with optional controls", () => {
|
||||
@ -49,17 +48,17 @@ export function main() {
|
||||
it("should create groups with a custom validator", () => {
|
||||
var g = b.group({
|
||||
"login": "some value"
|
||||
}, {"validator": validations.nullValidator});
|
||||
}, {"validator": Validators.nullValidator});
|
||||
|
||||
expect(g.validator).toBe(validations.nullValidator);
|
||||
expect(g.validator).toBe(Validators.nullValidator);
|
||||
});
|
||||
|
||||
it("should use default validators when no validators are provided", () => {
|
||||
var g = b.group({
|
||||
"login": "some value"
|
||||
});
|
||||
expect(g.controls["login"].validator).toBe(validations.nullValidator);
|
||||
expect(g.validator).toBe(validations.controlGroupValidator);
|
||||
expect(g.controls["login"].validator).toBe(Validators.nullValidator);
|
||||
expect(g.validator).toBe(Validators.group);
|
||||
});
|
||||
});
|
||||
}
|
276
modules/angular2/test/forms/integration_spec.js
vendored
276
modules/angular2/test/forms/integration_spec.js
vendored
@ -24,16 +24,17 @@ import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mappe
|
||||
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
|
||||
import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver';
|
||||
import {CssProcessor} from 'angular2/src/core/compiler/css_processor';
|
||||
import {EventManager, DomEventsPlugin} from 'angular2/src/core/events/event_manager';
|
||||
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
|
||||
|
||||
import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock';
|
||||
|
||||
import {Injector} from 'angular2/di';
|
||||
|
||||
import {Component, Decorator, Template} from 'angular2/angular2';
|
||||
import {Component, Decorator, Template, PropertySetter} from 'angular2/angular2';
|
||||
import {ControlGroupDirective, ControlDirective, Control, ControlGroup, OptionalControl,
|
||||
ControlValueAccessor, RequiredValidatorDirective} from 'angular2/forms';
|
||||
|
||||
import * as validators from 'angular2/src/forms/validators';
|
||||
ControlValueAccessor, RequiredValidatorDirective, CheckboxControlValueAccessor,
|
||||
DefaultValueAccessor, Validators} from 'angular2/forms';
|
||||
|
||||
export function main() {
|
||||
function detectChanges(view) {
|
||||
@ -58,35 +59,37 @@ export function main() {
|
||||
|
||||
tplResolver.setTemplate(componentType, new Template({
|
||||
inline: template,
|
||||
directives: [ControlGroupDirective, ControlDirective, WrappedValue, RequiredValidatorDirective]
|
||||
directives: [ControlGroupDirective, ControlDirective, WrappedValue, RequiredValidatorDirective,
|
||||
CheckboxControlValueAccessor, DefaultValueAccessor]
|
||||
}));
|
||||
|
||||
compiler.compile(componentType).then((pv) => {
|
||||
var view = pv.instantiate(null, null);
|
||||
var eventManager = new EventManager([new DomEventsPlugin()], new FakeVmTurnZone());
|
||||
var view = pv.instantiate(null, eventManager);
|
||||
view.hydrate(new Injector([]), null, null, context, null);
|
||||
detectChanges(view);
|
||||
callback(view);
|
||||
});
|
||||
}
|
||||
|
||||
describe("integration tests", () => {
|
||||
it("should initialize DOM elements with the given form object", inject([AsyncTestCompleter], (async) => {
|
||||
var ctx = new MyComp(new ControlGroup({
|
||||
"login": new Control("loginValue")
|
||||
}));
|
||||
if (DOM.supportsDOMEvents()) {
|
||||
describe("integration tests", () => {
|
||||
it("should initialize DOM elements with the given form object", inject([AsyncTestCompleter], (async) => {
|
||||
var ctx = new MyComp(new ControlGroup({
|
||||
"login": new Control("loginValue")
|
||||
}));
|
||||
|
||||
var t = `<div [control-group]="form">
|
||||
var t = `<div [control-group]="form">
|
||||
<input type="text" control="login">
|
||||
</div>`;
|
||||
|
||||
compile(MyComp, t, ctx, (view) => {
|
||||
var input = queryView(view, "input")
|
||||
expect(input.value).toEqual("loginValue");
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
compile(MyComp, t, ctx, (view) => {
|
||||
var input = queryView(view, "input")
|
||||
expect(input.value).toEqual("loginValue");
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
if (DOM.supportsDOMEvents()) {
|
||||
it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => {
|
||||
var form = new ControlGroup({
|
||||
"login": new Control("oldValue")
|
||||
@ -107,55 +110,110 @@ export function main() {
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
it("should update DOM elements when rebinding the control group", inject([AsyncTestCompleter], (async) => {
|
||||
var form = new ControlGroup({
|
||||
"login": new Control("oldValue")
|
||||
});
|
||||
var ctx = new MyComp(form);
|
||||
it("should update DOM elements when rebinding the control group", inject([AsyncTestCompleter], (async) => {
|
||||
var form = new ControlGroup({
|
||||
"login": new Control("oldValue")
|
||||
});
|
||||
var ctx = new MyComp(form);
|
||||
|
||||
var t = `<div [control-group]="form">
|
||||
var t = `<div [control-group]="form">
|
||||
<input type="text" control="login">
|
||||
</div>`;
|
||||
|
||||
compile(MyComp, t, ctx, (view) => {
|
||||
ctx.form = new ControlGroup({
|
||||
"login": new Control("newValue")
|
||||
compile(MyComp, t, ctx, (view) => {
|
||||
ctx.form = new ControlGroup({
|
||||
"login": new Control("newValue")
|
||||
});
|
||||
detectChanges(view);
|
||||
|
||||
var input = queryView(view, "input")
|
||||
expect(input.value).toEqual("newValue");
|
||||
async.done();
|
||||
});
|
||||
detectChanges(view);
|
||||
}));
|
||||
|
||||
var input = queryView(view, "input")
|
||||
expect(input.value).toEqual("newValue");
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
it("should update DOM element when rebinding the control name", inject([AsyncTestCompleter], (async) => {
|
||||
var ctx = new MyComp(new ControlGroup({
|
||||
"one": new Control("one"),
|
||||
"two": new Control("two")
|
||||
}), "one");
|
||||
|
||||
it("should update DOM element when rebinding the control name", inject([AsyncTestCompleter], (async) => {
|
||||
var ctx = new MyComp(new ControlGroup({
|
||||
"one": new Control("one"),
|
||||
"two": new Control("two")
|
||||
}), "one");
|
||||
|
||||
var t = `<div [control-group]="form">
|
||||
var t = `<div [control-group]="form">
|
||||
<input type="text" [control]="name">
|
||||
</div>`;
|
||||
|
||||
compile(MyComp, t, ctx, (view) => {
|
||||
var input = queryView(view, "input")
|
||||
expect(input.value).toEqual("one");
|
||||
compile(MyComp, t, ctx, (view) => {
|
||||
var input = queryView(view, "input")
|
||||
expect(input.value).toEqual("one");
|
||||
|
||||
ctx.name = "two";
|
||||
detectChanges(view);
|
||||
ctx.name = "two";
|
||||
detectChanges(view);
|
||||
|
||||
expect(input.value).toEqual("two");
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
expect(input.value).toEqual("two");
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
if (DOM.supportsDOMEvents()) {
|
||||
describe("different control types", () => {
|
||||
it("should support type=checkbox", inject([AsyncTestCompleter], (async) => {
|
||||
it("should support <input type=text>", inject([AsyncTestCompleter], (async) => {
|
||||
var ctx = new MyComp(new ControlGroup({"text": new Control("old")}));
|
||||
|
||||
var t = `<div [control-group]="form">
|
||||
<input type="text" control="text">
|
||||
</div>`;
|
||||
|
||||
compile(MyComp, t, ctx, (view) => {
|
||||
var input = queryView(view, "input")
|
||||
expect(input.value).toEqual("old");
|
||||
|
||||
input.value = "new";
|
||||
dispatchEvent(input, "input");
|
||||
|
||||
expect(ctx.form.value).toEqual({"text": "new"});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it("should support <input> without type", inject([AsyncTestCompleter], (async) => {
|
||||
var ctx = new MyComp(new ControlGroup({"text": new Control("old")}));
|
||||
|
||||
var t = `<div [control-group]="form">
|
||||
<input control="text">
|
||||
</div>`;
|
||||
|
||||
compile(MyComp, t, ctx, (view) => {
|
||||
var input = queryView(view, "input")
|
||||
expect(input.value).toEqual("old");
|
||||
|
||||
input.value = "new";
|
||||
dispatchEvent(input, "input");
|
||||
|
||||
expect(ctx.form.value).toEqual({"text": "new"});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it("should support <textarea>", inject([AsyncTestCompleter], (async) => {
|
||||
var ctx = new MyComp(new ControlGroup({"text": new Control('old')}));
|
||||
|
||||
var t = `<div [control-group]="form">
|
||||
<textarea control="text"></textarea>
|
||||
</div>`;
|
||||
|
||||
compile(MyComp, t, ctx, (view) => {
|
||||
var textarea = queryView(view, "textarea")
|
||||
expect(textarea.value).toEqual("old");
|
||||
|
||||
textarea.value = "new";
|
||||
dispatchEvent(textarea, "input");
|
||||
|
||||
expect(ctx.form.value).toEqual({"text": 'new'});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it("should support <type=checkbox>", inject([AsyncTestCompleter], (async) => {
|
||||
var ctx = new MyComp(new ControlGroup({"checkbox": new Control(true)}));
|
||||
|
||||
var t = `<div [control-group]="form">
|
||||
@ -169,31 +227,12 @@ export function main() {
|
||||
input.checked = false;
|
||||
dispatchEvent(input, "change");
|
||||
|
||||
expect(ctx.form.value).toEqual({"checkbox" : false});
|
||||
expect(ctx.form.value).toEqual({"checkbox": false});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it("should support textarea", inject([AsyncTestCompleter], (async) => {
|
||||
var ctx = new MyComp(new ControlGroup({"text": new Control('old')}));
|
||||
|
||||
var t = `<div [control-group]="form">
|
||||
<textarea control="text"></textarea>
|
||||
</div>`;
|
||||
|
||||
compile(MyComp, t, ctx, (view) => {
|
||||
var textarea = queryView(view, "textarea")
|
||||
expect(textarea.value).toEqual("old");
|
||||
|
||||
textarea.value = "new";
|
||||
dispatchEvent(textarea, "change");
|
||||
|
||||
expect(ctx.form.value).toEqual({"text" : 'new'});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it("should support select", inject([AsyncTestCompleter], (async) => {
|
||||
it("should support <select>", inject([AsyncTestCompleter], (async) => {
|
||||
var ctx = new MyComp(new ControlGroup({"city": new Control("SF")}));
|
||||
|
||||
var t = `<div [control-group]="form">
|
||||
@ -212,7 +251,7 @@ export function main() {
|
||||
select.value = 'NYC';
|
||||
dispatchEvent(select, "change");
|
||||
|
||||
expect(ctx.form.value).toEqual({"city" : 'NYC'});
|
||||
expect(ctx.form.value).toEqual({"city": 'NYC'});
|
||||
expect(sfOption.selected).toBe(false);
|
||||
async.done();
|
||||
});
|
||||
@ -232,7 +271,7 @@ export function main() {
|
||||
input.value = "!bb!";
|
||||
dispatchEvent(input, "change");
|
||||
|
||||
expect(ctx.form.value).toEqual({"name" : "bb"});
|
||||
expect(ctx.form.value).toEqual({"name": "bb"});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
@ -261,7 +300,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it("should use validators defined in the model", inject([AsyncTestCompleter], (async) => {
|
||||
var form = new ControlGroup({"login": new Control("aa", validators.required)});
|
||||
var form = new ControlGroup({"login": new Control("aa", Validators.required)});
|
||||
var ctx = new MyComp(form);
|
||||
|
||||
var t = `<div [control-group]="form">
|
||||
@ -281,31 +320,29 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
describe("nested forms", () => {
|
||||
it("should init DOM with the given form object",inject([AsyncTestCompleter], (async) => {
|
||||
var form = new ControlGroup({
|
||||
"nested": new ControlGroup({
|
||||
"login": new Control("value")
|
||||
})
|
||||
});
|
||||
var ctx = new MyComp(form);
|
||||
describe("nested forms", () => {
|
||||
it("should init DOM with the given form object", inject([AsyncTestCompleter], (async) => {
|
||||
var form = new ControlGroup({
|
||||
"nested": new ControlGroup({
|
||||
"login": new Control("value")
|
||||
})
|
||||
});
|
||||
var ctx = new MyComp(form);
|
||||
|
||||
var t = `<div [control-group]="form">
|
||||
var t = `<div [control-group]="form">
|
||||
<div control-group="nested">
|
||||
<input type="text" control="login">
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
compile(MyComp, t, ctx, (view) => {
|
||||
var input = queryView(view, "input")
|
||||
expect(input.value).toEqual("value");
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
compile(MyComp, t, ctx, (view) => {
|
||||
var input = queryView(view, "input")
|
||||
expect(input.value).toEqual("value");
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
if (DOM.supportsDOMEvents()) {
|
||||
it("should update the control group values on DOM change", inject([AsyncTestCompleter], (async) => {
|
||||
var form = new ControlGroup({
|
||||
"nested": new ControlGroup({
|
||||
@ -326,13 +363,13 @@ export function main() {
|
||||
input.value = "updatedValue";
|
||||
dispatchEvent(input, "change");
|
||||
|
||||
expect(form.value).toEqual({"nested" : {"login" : "updatedValue"}});
|
||||
expect(form.value).toEqual({"nested": {"login": "updatedValue"}});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
@ -348,21 +385,42 @@ class MyComp {
|
||||
}
|
||||
}
|
||||
|
||||
class WrappedValueAccessor extends ControlValueAccessor {
|
||||
readValue(el){
|
||||
return el.value.substring(1, el.value.length - 1);
|
||||
}
|
||||
|
||||
writeValue(el, value):void {
|
||||
el.value = `!${value}!`;
|
||||
}
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
selector:'[wrapped-value]'
|
||||
selector:'[wrapped-value]',
|
||||
events: {
|
||||
'change' : 'handleOnChange($event.target.value)'
|
||||
}
|
||||
})
|
||||
class WrappedValue {
|
||||
constructor(cd:ControlDirective) {
|
||||
cd.valueAccessor = new WrappedValueAccessor();
|
||||
_setProperty:Function;
|
||||
onChange:Function;
|
||||
|
||||
constructor(cd:ControlDirective, @PropertySetter('value') setProperty:Function) {
|
||||
super();
|
||||
this._setProperty = setProperty;
|
||||
cd.valueAccessor = this;
|
||||
}
|
||||
|
||||
writeValue(value) {
|
||||
this._setProperty(`!${value}!`);
|
||||
}
|
||||
|
||||
handleOnChange(value) {
|
||||
this.onChange(value.substring(1, value.length - 1));
|
||||
}
|
||||
}
|
||||
|
||||
class FakeVmTurnZone extends VmTurnZone {
|
||||
constructor() {
|
||||
super({enableLongStackTrace: false});
|
||||
}
|
||||
|
||||
run(fn) {
|
||||
fn();
|
||||
}
|
||||
|
||||
runOutsideAngular(fn) {
|
||||
fn();
|
||||
}
|
||||
}
|
21
modules/angular2/test/forms/model_spec.js
vendored
21
modules/angular2/test/forms/model_spec.js
vendored
@ -1,24 +1,23 @@
|
||||
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib';
|
||||
import {ControlGroup, Control, OptionalControl} from 'angular2/forms';
|
||||
import * as validations from 'angular2/forms';
|
||||
import {ControlGroup, Control, OptionalControl, Validators} from 'angular2/forms';
|
||||
|
||||
export function main() {
|
||||
describe("Form Model", () => {
|
||||
describe("Control", () => {
|
||||
describe("validator", () => {
|
||||
it("should run validator with the initial value", () => {
|
||||
var c = new Control("value", validations.required);
|
||||
var c = new Control("value", Validators.required);
|
||||
expect(c.valid).toEqual(true);
|
||||
});
|
||||
|
||||
it("should rerun the validator when the value changes", () => {
|
||||
var c = new Control("value", validations.required);
|
||||
var c = new Control("value", Validators.required);
|
||||
c.updateValue(null);
|
||||
expect(c.valid).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return errors", () => {
|
||||
var c = new Control(null, validations.required);
|
||||
var c = new Control(null, Validators.required);
|
||||
expect(c.errors).toEqual({"required" : true});
|
||||
});
|
||||
});
|
||||
@ -83,7 +82,7 @@ export function main() {
|
||||
describe("validator", () => {
|
||||
it("should run the validator with the initial value (valid)", () => {
|
||||
var g = new ControlGroup({
|
||||
"one": new Control('value', validations.required)
|
||||
"one": new Control('value', Validators.required)
|
||||
});
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
@ -92,7 +91,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it("should run the validator with the initial value (invalid)", () => {
|
||||
var one = new Control(null, validations.required);
|
||||
var one = new Control(null, Validators.required);
|
||||
var g = new ControlGroup({"one": one});
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
@ -101,7 +100,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it("should run the validator with the value changes", () => {
|
||||
var c = new Control(null, validations.required);
|
||||
var c = new Control(null, Validators.required);
|
||||
var g = new ControlGroup({"one": c});
|
||||
|
||||
c.updateValue("some value");
|
||||
@ -174,10 +173,10 @@ export function main() {
|
||||
expect(group.value).toEqual({"required" : "requiredValue", "optional" : "optionalValue"});
|
||||
});
|
||||
|
||||
it("should not run validations on an inactive component", () => {
|
||||
it("should not run Validators on an inactive component", () => {
|
||||
var group = new ControlGroup({
|
||||
"required": new Control("requiredValue", validations.required),
|
||||
"optional": new Control("", validations.required)
|
||||
"required": new Control("requiredValue", Validators.required),
|
||||
"optional": new Control("", Validators.required)
|
||||
}, {
|
||||
"optional": false
|
||||
});
|
||||
|
20
modules/angular2/test/forms/validators_spec.js
vendored
20
modules/angular2/test/forms/validators_spec.js
vendored
@ -1,5 +1,5 @@
|
||||
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, el} from 'angular2/test_lib';
|
||||
import {ControlGroup, Control, required, compose, controlGroupValidator, nullValidator} from 'angular2/forms';
|
||||
import {ControlGroup, Control, Validators} from 'angular2/forms';
|
||||
|
||||
export function main() {
|
||||
function validator(key:string, error:any){
|
||||
@ -13,31 +13,31 @@ export function main() {
|
||||
describe("Validators", () => {
|
||||
describe("required", () => {
|
||||
it("should error on an empty string", () => {
|
||||
expect(required(new Control(""))).toEqual({"required" : true});
|
||||
expect(Validators.required(new Control(""))).toEqual({"required" : true});
|
||||
});
|
||||
|
||||
it("should error on null", () => {
|
||||
expect(required(new Control(null))).toEqual({"required" : true});
|
||||
expect(Validators.required(new Control(null))).toEqual({"required" : true});
|
||||
});
|
||||
|
||||
it("should not error on a non-empty string", () => {
|
||||
expect(required(new Control("not empty"))).toEqual(null);
|
||||
expect(Validators.required(new Control("not empty"))).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("compose", () => {
|
||||
it("should collect errors from all the validators", () => {
|
||||
var c = compose([validator("a", true), validator("b", true)]);
|
||||
var c = Validators.compose([validator("a", true), validator("b", true)]);
|
||||
expect(c(new Control(""))).toEqual({"a" : true, "b" : true});
|
||||
});
|
||||
|
||||
it("should run validators left to right", () => {
|
||||
var c = compose([validator("a", 1), validator("a", 2)]);
|
||||
var c = Validators.compose([validator("a", 1), validator("a", 2)]);
|
||||
expect(c(new Control(""))).toEqual({"a" : 2});
|
||||
});
|
||||
|
||||
it("should return null when no errors", () => {
|
||||
var c = compose([nullValidator, nullValidator]);
|
||||
var c = Validators.compose([Validators.nullValidator, Validators.nullValidator]);
|
||||
expect(c(new Control(""))).toEqual(null);
|
||||
});
|
||||
});
|
||||
@ -48,7 +48,7 @@ export function main() {
|
||||
var two = new Control("one", validator("b", true));
|
||||
var g = new ControlGroup({"one" : one, "two" : two});
|
||||
|
||||
expect(controlGroupValidator(g)).toEqual({
|
||||
expect(Validators.group(g)).toEqual({
|
||||
"a" : [one],
|
||||
"b" : [two]
|
||||
});
|
||||
@ -59,7 +59,7 @@ export function main() {
|
||||
var two = new Control("two");
|
||||
var g = new ControlGroup({"one" : one, "two" : two});
|
||||
|
||||
expect(controlGroupValidator(g)).toEqual({
|
||||
expect(Validators.group(g)).toEqual({
|
||||
"a": [one]
|
||||
});
|
||||
});
|
||||
@ -69,7 +69,7 @@ export function main() {
|
||||
"one" : new Control("one")
|
||||
});
|
||||
|
||||
expect(controlGroupValidator(g)).toEqual(null);
|
||||
expect(Validators.group(g)).toEqual(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -7,7 +7,7 @@ import 'package:angular2/src/transform/common/formatter.dart';
|
||||
import 'package:code_transformers/tests.dart';
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:guinness/guinness.dart';
|
||||
import 'package:unittest/vm_config.dart';
|
||||
|
||||
import '../common/read_file.dart';
|
||||
@ -17,7 +17,7 @@ var formatter = new DartFormatter();
|
||||
void allTests() {
|
||||
var reader = new TestAssetReader();
|
||||
|
||||
test('should generate a setter for a `bind` property in an annotation.',
|
||||
it('should generate a setter for a `bind` property in an annotation.',
|
||||
() async {
|
||||
var inputPath = 'bind_generator/basic_bind_files/bar.ng_deps.dart';
|
||||
var expected = formatter.format(
|
||||
@ -25,10 +25,10 @@ void allTests() {
|
||||
|
||||
var output = formatter
|
||||
.format(await createNgSetters(reader, new AssetId('a', inputPath)));
|
||||
expect(output, equals(expected));
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
test('should generate a single setter when multiple annotations bind to the '
|
||||
it('should generate a single setter when multiple annotations bind to the '
|
||||
'same property.', () async {
|
||||
var inputPath =
|
||||
'bind_generator/duplicate_bind_name_files/soup.ng_deps.dart';
|
||||
@ -37,6 +37,6 @@ void allTests() {
|
||||
|
||||
var output = formatter
|
||||
.format(await createNgSetters(reader, new AssetId('a', inputPath)));
|
||||
expect(output, equals(expected));
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
}
|
||||
|
23
modules/angular2/test/transform/common/logger.dart
Normal file
23
modules/angular2/test/transform/common/logger.dart
Normal 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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
@ -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]')]
|
||||
});
|
||||
}
|
@ -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);
|
||||
}
|
@ -3,7 +3,7 @@ library web_foo;
|
||||
import 'package:angular2/src/core/application.dart';
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
import 'bar.dart';
|
||||
import 'a:web/bar.ng_deps.dart' as i0;
|
||||
import 'bar.ng_deps.dart' as i0;
|
||||
|
||||
bool _visited = false;
|
||||
void setupReflection(reflector) {
|
@ -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]')]
|
||||
});
|
||||
}
|
@ -4,7 +4,8 @@ import 'package:angular2/src/core/application.dart';
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
import 'bar.dart';
|
||||
|
||||
void main() {
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||
bootstrap(MyComponent);
|
||||
bool _visited = false;
|
||||
void setupReflection(reflector) {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
}
|
@ -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])
|
||||
]
|
||||
});
|
||||
}
|
@ -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]')]
|
||||
});
|
||||
}
|
@ -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);
|
||||
}
|
@ -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]')]
|
||||
});
|
||||
}
|
@ -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;
|
||||
}
|
@ -7,7 +7,7 @@ import 'package:angular2/src/transform/common/formatter.dart';
|
||||
import 'package:code_transformers/tests.dart';
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:guinness/guinness.dart';
|
||||
import 'package:unittest/vm_config.dart';
|
||||
|
||||
import '../common/read_file.dart';
|
||||
@ -15,12 +15,12 @@ import '../common/read_file.dart';
|
||||
var formatter = new DartFormatter();
|
||||
|
||||
void allTests() {
|
||||
test('should preserve parameter annotations as const instances.', () {
|
||||
it('should preserve parameter annotations as const instances.', () {
|
||||
var inputPath = 'parameter_metadata/soup.dart';
|
||||
var expected = _readFile('parameter_metadata/expected/soup.ng_deps.dart');
|
||||
var output =
|
||||
formatter.format(createNgDeps(_readFile(inputPath), inputPath));
|
||||
expect(output, equals(expected));
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -14,4 +14,4 @@ void setupReflection(reflector) {
|
||||
'parameters': const [const [Tasty, String], const [const Inject(Salt)]],
|
||||
'annotations': const [const Component(selector: '[soup]')]
|
||||
});
|
||||
} // {"version":1,"importOffset":104,"registerOffset":451,"imports":["package:angular2/src/core/annotations/annotations.dart"]}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import 'package:angular2/src/dom/html5lib_adapter.dart';
|
||||
import 'package:angular2/transformer.dart';
|
||||
import 'package:code_transformers/tests.dart';
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:guinness/guinness.dart';
|
||||
|
||||
import '../common/read_file.dart';
|
||||
|
||||
@ -96,17 +96,6 @@ void allTests() {
|
||||
outputs: {
|
||||
'a|web/bar.ng_deps.dart':
|
||||
'two_annotations_files/expected/bar.ng_deps.dart'
|
||||
}),
|
||||
new IntegrationTestConfig(
|
||||
'should ensure that dependencies are property chained.',
|
||||
inputs: {
|
||||
'a|web/index.dart': 'chained_deps_files/index.dart',
|
||||
'a|web/foo.dart': 'chained_deps_files/foo.dart',
|
||||
'a|web/bar.dart': 'chained_deps_files/bar.dart'
|
||||
},
|
||||
outputs: {
|
||||
'a|web/bar.ng_deps.dart': 'chained_deps_files/expected/bar.ng_deps.dart',
|
||||
'a|web/foo.ng_deps.dart': 'chained_deps_files/expected/foo.ng_deps.dart'
|
||||
})
|
||||
];
|
||||
|
||||
|
@ -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();
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
library foo;
|
||||
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
|
||||
@Component(selector: '[salad]')
|
||||
class DependencyComponent {
|
||||
DependencyComponent();
|
||||
}
|
@ -3,7 +3,7 @@ library angular2.test.transform.reflection_remover;
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:angular2/src/transform/reflection_remover/codegen.dart';
|
||||
import 'package:angular2/src/transform/reflection_remover/rewriter.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:guinness/guinness.dart';
|
||||
|
||||
import 'reflection_remover_files/expected/index.dart' as expected;
|
||||
import '../common/read_file.dart';
|
||||
@ -11,11 +11,11 @@ import '../common/read_file.dart';
|
||||
void allTests() {
|
||||
var codegen = new Codegen('web/index.dart', 'web/index.ng_deps.dart');
|
||||
|
||||
test('should remove uses of mirrors & insert calls to generated code.', () {
|
||||
it('should remove uses of mirrors & insert calls to generated code.', () {
|
||||
var code =
|
||||
readFile('reflection_remover/reflection_remover_files/index.dart');
|
||||
var output =
|
||||
new Rewriter(code, codegen).rewrite(parseCompilationUnit(code));
|
||||
expect(output, equals(expected.code));
|
||||
expect(output).toEqual(expected.code);
|
||||
});
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/formatter.dart';
|
||||
import 'package:angular2/src/transform/template_compiler/generator.dart';
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:guinness/guinness.dart';
|
||||
|
||||
import '../common/read_file.dart';
|
||||
|
||||
@ -16,7 +16,7 @@ void allTests() {
|
||||
Html5LibDomAdapter.makeCurrent();
|
||||
AssetReader reader = new TestAssetReader();
|
||||
|
||||
test('should parse simple expressions in inline templates.', () async {
|
||||
it('should parse simple expressions in inline templates.', () async {
|
||||
var inputPath =
|
||||
'template_compiler/inline_expression_files/hello.ng_deps.dart';
|
||||
var expected = readFile(
|
||||
@ -24,16 +24,16 @@ void allTests() {
|
||||
var output = await processTemplates(reader, new AssetId('a', inputPath));
|
||||
output = formatter.format(output);
|
||||
expected = formatter.format(expected);
|
||||
expect(output, equals(expected));
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
test('should parse simple methods in inline templates.', () async {
|
||||
it('should parse simple methods in inline templates.', () async {
|
||||
var inputPath = 'template_compiler/inline_method_files/hello.ng_deps.dart';
|
||||
var expected = readFile(
|
||||
'template_compiler/inline_method_files/expected/hello.ng_deps.dart');
|
||||
var output = await processTemplates(reader, new AssetId('a', inputPath));
|
||||
output = formatter.format(output);
|
||||
expected = formatter.format(expected);
|
||||
expect(output, equals(expected));
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
library examples.src.hello_world.index_common_dart;
|
||||
library examples.hello_world.index_common_dart;
|
||||
|
||||
import 'hello.dart';
|
||||
import 'package:angular2/angular2.dart'
|
||||
|
@ -1,4 +1,4 @@
|
||||
library examples.src.hello_world.index_common_dart;
|
||||
library examples.hello_world.index_common_dart;
|
||||
|
||||
import 'hello.dart';
|
||||
import 'package:angular2/angular2.dart'
|
||||
|
@ -1,4 +1,4 @@
|
||||
library examples.src.hello_world.index_common_dart;
|
||||
library examples.hello_world.index_common_dart;
|
||||
|
||||
import 'hello.dart';
|
||||
import 'package:angular2/angular2.dart'
|
||||
|
@ -1,4 +1,4 @@
|
||||
library examples.src.hello_world.index_common_dart;
|
||||
library examples.hello_world.index_common_dart;
|
||||
|
||||
import 'hello.dart';
|
||||
import 'package:angular2/angular2.dart'
|
||||
|
@ -1,9 +1,11 @@
|
||||
library angular2.test.transform;
|
||||
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:guinness/guinness.dart';
|
||||
import 'package:unittest/unittest.dart' hide expect;
|
||||
import 'package:unittest/vm_config.dart';
|
||||
|
||||
import 'bind_generator/all_tests.dart' as bindGenerator;
|
||||
import 'directive_linker/all_tests.dart' as directiveLinker;
|
||||
import 'directive_processor/all_tests.dart' as directiveProcessor;
|
||||
import 'integration/all_tests.dart' as integration;
|
||||
import 'reflection_remover/all_tests.dart' as reflectionRemover;
|
||||
@ -11,9 +13,12 @@ import 'template_compiler/all_tests.dart' as templateCompiler;
|
||||
|
||||
main() {
|
||||
useVMConfiguration();
|
||||
group('Bind Generator', bindGenerator.allTests);
|
||||
group('Directive Processor', directiveProcessor.allTests);
|
||||
group('Reflection Remover', reflectionRemover.allTests);
|
||||
group('Template Compiler', templateCompiler.allTests);
|
||||
describe('Bind Generator', bindGenerator.allTests);
|
||||
describe('Directive Linker', directiveLinker.allTests);
|
||||
describe('Directive Processor', directiveProcessor.allTests);
|
||||
describe('Reflection Remover', reflectionRemover.allTests);
|
||||
describe('Template Compiler', templateCompiler.allTests);
|
||||
// NOTE(kegluneq): These use `code_transformers#testPhases`, which is not
|
||||
// designed to work with `guinness`.
|
||||
group('Transformer Pipeline', integration.allTests);
|
||||
}
|
||||
|
@ -78,6 +78,9 @@ function setupReflector() {
|
||||
"value0": (a,v) => a.value0 = v, "value1": (a,v) => a.value1 = v,
|
||||
"value2": (a,v) => a.value2 = v, "value3": (a,v) => a.value3 = v, "value4": (a,v) => a.value4 = v,
|
||||
|
||||
"attr0": (a,v) => a.attr0 = v, "attr1": (a,v) => a.attr1 = v,
|
||||
"attr2": (a,v) => a.attr2 = v, "attr3": (a,v) => a.attr3 = v, "attr4": (a,v) => a.attr4 = v,
|
||||
|
||||
"prop": (a,v) => a.prop = v
|
||||
});
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user